Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CSV exports #1448

Merged
merged 1 commit into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ USER root

# Setup environment variables.
ENV PYTHONUNBUFFERED=1 \
PYTHONIOENCODING=utf-8 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONFAULTHANDLER=1 \
PYTHONHASHSEED=random
Expand Down
24 changes: 17 additions & 7 deletions tests/test_csv_exporters/helpers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from dataclasses import dataclass, field
import dataclasses
from collections.abc import Generator
from contextlib import contextmanager
from typing import Any, NamedTuple
from unittest import mock


@dataclass
@dataclasses.dataclass
class Missing:
deleted: list[str] = field(default_factory=list)
null: list[str] = field(default_factory=list)
empty: list[str] = field(default_factory=list)
deleted: list[str] = dataclasses.field(default_factory=list)
null: list[str] = dataclasses.field(default_factory=list)
empty: list[str] = dataclasses.field(default_factory=list)

def remove_from_data(self, data: dict[str, Any]) -> None:
self._delete(data)
Expand Down Expand Up @@ -49,5 +51,13 @@ class MissingParams(NamedTuple):
column_value_mapping: dict[str, Any]


def get_writes(mock_file: mock.MagicMock) -> list[list[str]]:
return [call[1][0] for call in mock_file.mock_calls if call[0] == "().writerow"]
class CSVWriterMock(mock.MagicMock):
def get_writes(self) -> list[list[str]]:
return [call[1][0] for call in self.mock_calls if call[0] == "().writerow"]


@contextmanager
def mock_csv_writer() -> Generator[CSVWriterMock, None, None]:
path = "tilavarauspalvelu.services.csv_export._base_exporter.csv.writer"
with mock.patch(path, new_callable=CSVWriterMock) as mock_file:
yield mock_file
187 changes: 91 additions & 96 deletions tests/test_csv_exporters/test_application_round_applications_export.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import datetime
from typing import TYPE_CHECKING
from unittest import mock

import pytest
from django.utils import timezone
Expand All @@ -9,13 +8,11 @@
from tests.factories import ApplicationFactory, ApplicationRoundFactory
from tests.factories.application import ApplicationBuilder
from tilavarauspalvelu.enums import ApplicantTypeChoice, Priority, Weekday
from tilavarauspalvelu.utils.exporter.application_round_applications_exporter import (
ApplicationExportRow,
ApplicationRoundApplicationsCSVExporter,
)
from tilavarauspalvelu.services.csv_export import ApplicationRoundApplicationsCSVExporter
from tilavarauspalvelu.services.csv_export.application_round_applications_exporter import ApplicationExportRow
from utils.date_utils import local_date_string, local_timedelta_string

from .helpers import Missing, MissingParams, get_writes
from .helpers import Missing, MissingParams, mock_csv_writer

if TYPE_CHECKING:
from tilavarauspalvelu.models import ApplicationSection
Expand All @@ -25,8 +22,6 @@
pytest.mark.django_db,
]

CSV_WRITER_MOCK_PATH = "tilavarauspalvelu.utils.exporter.application_round_applications_exporter.csv.writer"


def test_application_round_applications_export__multiple_applications(graphql):
application_round = ApplicationRoundFactory.create_in_status_in_allocation()
Expand Down Expand Up @@ -60,100 +55,96 @@ def test_application_round_applications_export__multiple_applications(graphql):
section_2: ApplicationSection = application_2.application_sections.first()

exporter = ApplicationRoundApplicationsCSVExporter(application_round_id=application_round.id)
with mock.patch(CSV_WRITER_MOCK_PATH) as mock_writer:
exporter.export()
with mock_csv_writer() as mock_writer:
exporter.write()

writes = get_writes(mock_writer)
writes = mock_writer.get_writes()

assert exporter.max_options == 1
header_rows = exporter._get_header_rows()
header_rows = [list(row.as_row()) for row in exporter.get_header_rows()]
assert writes[0] == header_rows[0]
assert writes[1] == header_rows[1]
assert writes[2] == header_rows[2]

assert writes[3] == [
*list(
ApplicationExportRow(
application_id=str(application_1.id),
application_status=application_1.status.value,
applicant=application_1.organisation.name,
organisation_id=application_1.organisation.identifier,
contact_person_first_name=application_1.contact_person.first_name,
contact_person_last_name=application_1.contact_person.last_name,
contact_person_email=application_1.contact_person.email,
contact_person_phone=application_1.contact_person.phone_number,
section_id=str(section_1.id),
section_status=section_1.status.value,
section_name=section_1.name,
reservations_begin_date=local_date_string(section_1.reservations_begin_date),
reservations_end_date=local_date_string(section_1.reservations_end_date),
home_city_name=application_1.home_city.name,
purpose_name=section_1.purpose.name,
age_group_str=str(section_1.age_group),
num_persons=str(section_1.num_persons),
applicant_type=application_1.applicant_type,
applied_reservations_per_week=str(section_1.applied_reservations_per_week),
reservation_min_duration=local_timedelta_string(section_1.reservation_min_duration),
reservation_max_duration=local_timedelta_string(section_1.reservation_max_duration),
primary_monday="",
primary_tuesday="12:00-14:00",
primary_wednesday="",
primary_thursday="",
primary_friday="",
primary_saturday="",
primary_sunday="",
secondary_monday="",
secondary_tuesday="",
secondary_wednesday="",
secondary_thursday="",
secondary_friday="",
secondary_saturday="",
secondary_sunday="",
)
),
*ApplicationExportRow(
application_id=str(application_1.id),
application_status=application_1.status.value,
applicant=application_1.organisation.name,
organisation_id=application_1.organisation.identifier,
contact_person_first_name=application_1.contact_person.first_name,
contact_person_last_name=application_1.contact_person.last_name,
contact_person_email=application_1.contact_person.email,
contact_person_phone=application_1.contact_person.phone_number,
section_id=str(section_1.id),
section_status=section_1.status.value,
section_name=section_1.name,
reservations_begin_date=local_date_string(section_1.reservations_begin_date),
reservations_end_date=local_date_string(section_1.reservations_end_date),
home_city_name=application_1.home_city.name,
purpose_name=section_1.purpose.name,
age_group_str=str(section_1.age_group),
num_persons=str(section_1.num_persons),
applicant_type=application_1.applicant_type,
applied_reservations_per_week=str(section_1.applied_reservations_per_week),
reservation_min_duration=local_timedelta_string(section_1.reservation_min_duration),
reservation_max_duration=local_timedelta_string(section_1.reservation_max_duration),
primary_monday="",
primary_tuesday="12:00-14:00",
primary_wednesday="",
primary_thursday="",
primary_friday="",
primary_saturday="",
primary_sunday="",
secondary_monday="",
secondary_tuesday="",
secondary_wednesday="",
secondary_thursday="",
secondary_friday="",
secondary_saturday="",
secondary_sunday="",
).as_row(),
"foo, fizz", # reservation unit option
]

assert writes[4] == [
*list(
ApplicationExportRow(
application_id=str(application_2.id),
application_status=application_2.status.value,
applicant=application_2.organisation.name,
organisation_id=application_2.organisation.identifier,
contact_person_first_name=application_2.contact_person.first_name,
contact_person_last_name=application_2.contact_person.last_name,
contact_person_email=application_2.contact_person.email,
contact_person_phone=application_2.contact_person.phone_number,
section_id=str(section_2.id),
section_status=section_2.status.value,
section_name=section_2.name,
reservations_begin_date=local_date_string(section_2.reservations_begin_date),
reservations_end_date=local_date_string(section_2.reservations_end_date),
home_city_name=application_2.home_city.name,
purpose_name=section_2.purpose.name,
age_group_str=str(section_2.age_group),
num_persons=str(section_2.num_persons),
applicant_type=application_2.applicant_type,
applied_reservations_per_week=str(section_2.applied_reservations_per_week),
reservation_min_duration=local_timedelta_string(section_2.reservation_min_duration),
reservation_max_duration=local_timedelta_string(section_2.reservation_max_duration),
primary_monday="",
primary_tuesday="",
primary_wednesday="",
primary_thursday="",
primary_friday="",
primary_saturday="",
primary_sunday="",
secondary_monday="",
secondary_tuesday="",
secondary_wednesday="",
secondary_thursday="",
secondary_friday="12:00-14:00",
secondary_saturday="",
secondary_sunday="",
)
),
*ApplicationExportRow(
application_id=str(application_2.id),
application_status=application_2.status.value,
applicant=application_2.organisation.name,
organisation_id=application_2.organisation.identifier,
contact_person_first_name=application_2.contact_person.first_name,
contact_person_last_name=application_2.contact_person.last_name,
contact_person_email=application_2.contact_person.email,
contact_person_phone=application_2.contact_person.phone_number,
section_id=str(section_2.id),
section_status=section_2.status.value,
section_name=section_2.name,
reservations_begin_date=local_date_string(section_2.reservations_begin_date),
reservations_end_date=local_date_string(section_2.reservations_end_date),
home_city_name=application_2.home_city.name,
purpose_name=section_2.purpose.name,
age_group_str=str(section_2.age_group),
num_persons=str(section_2.num_persons),
applicant_type=application_2.applicant_type,
applied_reservations_per_week=str(section_2.applied_reservations_per_week),
reservation_min_duration=local_timedelta_string(section_2.reservation_min_duration),
reservation_max_duration=local_timedelta_string(section_2.reservation_max_duration),
primary_monday="",
primary_tuesday="",
primary_wednesday="",
primary_thursday="",
primary_friday="",
primary_saturday="",
primary_sunday="",
secondary_monday="",
secondary_tuesday="",
secondary_wednesday="",
secondary_thursday="",
secondary_friday="12:00-14:00",
secondary_saturday="",
secondary_sunday="",
).as_row(),
"bar, buzz", # reservation unit option
]

Expand Down Expand Up @@ -231,14 +222,16 @@ def test_application_round_applications_export__missing_data(graphql, column_val
missing.remove_from_data(data)
ApplicationFactory.create(**data)

exporter = ApplicationRoundApplicationsCSVExporter(application_round_id=application_round.id)

# when:
# - The exporter is run
with mock.patch(CSV_WRITER_MOCK_PATH) as mock_writer:
ApplicationRoundApplicationsCSVExporter(application_round_id=application_round.id).export()
with mock_csv_writer() as mock_writer:
exporter.write()

# then:
# - The writes to the csv file are correct
writes = get_writes(mock_writer)
writes = mock_writer.get_writes()

header_row = writes[2]
data_row = writes[3]
Expand All @@ -263,14 +256,16 @@ def test_application_round_applications_export__no_reservation_unit_options(grap
)
application_1.application_sections.first()

exporter = ApplicationRoundApplicationsCSVExporter(application_round_id=application_round.id)

# when:
# - The exporter is run
with mock.patch(CSV_WRITER_MOCK_PATH) as mock_writer:
ApplicationRoundApplicationsCSVExporter(application_round_id=application_round.id).export()
with mock_csv_writer() as mock_writer:
exporter.write()

# then:
# - The csv doesn't contain the reservation unit options column
writes = get_writes(mock_writer)
writes = mock_writer.get_writes()

header_row = writes[2]
assert "tilatoive 1" not in header_row
Loading