Skip to content

Commit a8ba5cb

Browse files
chg ! tests
1 parent a4df124 commit a8ba5cb

File tree

10 files changed

+93
-58
lines changed

10 files changed

+93
-58
lines changed

src/country_workspace/contrib/aurora/pipeline.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
from country_workspace.contrib.aurora.client import AuroraClient
66
from country_workspace.contrib.aurora.exceptions import TooManyBeneficiaryError
7-
from country_workspace.models import AsyncJob, Batch, Household, Individual, Office
7+
from country_workspace.models import AsyncJob, Batch, Household, Individual
88
from country_workspace.models.household import RELATIONSHIP_HEAD, RELATIONSHIP_FIELDNAME
99
from country_workspace.utils.config import BatchNameConfig, ValidateModeConfig
1010
from country_workspace.utils.fields import clean_field_names
11+
from country_workspace.utils.types import BeneficiaryMapping
1112
from country_workspace.validators.beneficiaries import validate_beneficiaries
1213

1314

@@ -61,18 +62,18 @@ def import_from_aurora(job: AsyncJob) -> dict[str, int]:
6162
total["households"] += 1
6263
records_data.append((record_id, individuals))
6364

64-
validate_records(records_data, cfg, job.program.country_office)
65+
if mapping := validate_records(records_data, cfg):
66+
validate_beneficiaries(mapping, cfg, job.program.country_office)
6567

6668
return total
6769

6870

69-
def validate_records(records_data: list[tuple[int, list[Individual]]], cfg: Config, office: Office) -> None:
71+
def validate_records(records_data: list[tuple[int, list[Individual]]], cfg: Config) -> BeneficiaryMapping:
7072
"""Validate beneficiaries based on configuration and record data.
7173
7274
Args:
7375
records_data: List of tuples containing record ID and created individuals.
7476
cfg: Configuration for validation and mapping.
75-
office: The office context for validation.
7677
7778
Raises:
7879
TooManyBeneficiaryError: If more than one Individual is created when master_detail is False.
@@ -88,9 +89,7 @@ def validate_records(records_data: list[tuple[int, list[Individual]]], cfg: Conf
8889
raise TooManyBeneficiaryError("Individual", record_id=record_id, count=len(individuals))
8990
if individuals:
9091
mapping[record_id] = individuals[0]
91-
92-
if mapping:
93-
validate_beneficiaries(cfg, mapping, office)
92+
return mapping
9493

9594

9695
def create_household(batch: Batch, data: dict[str, Any], prefix: str) -> Household:

src/country_workspace/datasources/rdi.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,17 +230,15 @@ def import_from_rdi(job: AsyncJob) -> dict[str, int]:
230230
def _import_master_detail(
231231
job: AsyncJob, batch: Batch, config: Config, validate: ValidateBeneficiaries
232232
) -> dict[str, int]:
233-
household_sheet, individual_sheet = read_sheets(
234-
config, job.file, SheetName.HOUSEHOLDS.value, SheetName.INDIVIDUALS.value
235-
)
233+
household_sheet, individual_sheet = read_sheets(config, job.file, SheetName.HOUSEHOLDS, SheetName.INDIVIDUALS)
236234
household_mapping = process_households(household_sheet, job, batch, config)
237235
individuals_mapping = process_beneficiaries(individual_sheet, job, batch, config, household_mapping)
238236
validate(household_mapping)
239237
return {"household": len(household_mapping), "individual": len(individuals_mapping)}
240238

241239

242240
def _import_people_only(job: AsyncJob, batch: Batch, config: Config, validate: ValidateBeneficiaries) -> dict[str, int]:
243-
(people_sheet,) = read_sheets(config, job.file, SheetName.PEOPLE.value)
241+
(people_sheet,) = read_sheets(config, job.file, SheetName.PEOPLE)
244242
people_mapping = process_beneficiaries(people_sheet, job, batch, config)
245243
validate(people_mapping)
246244
return {"people": len(people_mapping)}

src/country_workspace/validators/beneficiaries.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
from country_workspace.workspaces.admin.forms import ValidateMode
88

99

10-
def validate_beneficiaries(config: ValidateModeConfig, beneficiary_mapping: BeneficiaryMapping, office: Office) -> None:
10+
def validate_beneficiaries(beneficiary_mapping: BeneficiaryMapping, config: ValidateModeConfig, office: Office) -> None:
1111
mode = ValidateMode(config["validate_mode"])
1212
if mode is ValidateMode.NONE:
1313
return
1414

1515
fail_if_alien = mode is ValidateMode.CHECK_AND_FAIL_IF_ALIEN
16-
with state.set(tenant=office, program=office.program):
16+
with state.set(tenant=office):
1717
for key, beneficiary in beneficiary_mapping.items():
1818
if not beneficiary.validate_with_checker(fail_if_alien=fail_if_alien):
1919
raise BeneficiaryValidationError(beneficiary._meta.object_name, key)

src/country_workspace/workspaces/admin/forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class BulkUpdateImportForm(forms.Form):
3737

3838

3939
class ValidateMode(TextChoices):
40-
NONE = "none", _("Skip validation — import data as is")
40+
NONE = "none", _("Skip validation — import data as is.")
4141
CHECK_BEFORE = "check_before", _("Prevent import if data is not valid against data checker.")
4242
CHECK_AND_FAIL_IF_ALIEN = (
4343
"check_and_fail_if_alien",

tests/datasources/test_rdi.py

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
from country_workspace.datasources.utils import datetime_to_date, date_to_iso_string
3232
from country_workspace.models import Household, Individual
3333
from country_workspace.workspaces.exceptions import BeneficiaryValidationError
34-
from country_workspace.validators.beneficiaries import validate_beneficiaries
3534

3635

3736
HOUSEHOLD_1_PK = 1
@@ -45,13 +44,12 @@
4544
def config(request) -> Config:
4645
return {
4746
"batch_name": "batch_name",
47+
"validate_mode": "none",
4848
"master_detail": request.param,
4949
"household_pk_col": "household_pk",
5050
"master_column_label": "master_column",
5151
"detail_column_label": "detail_column",
5252
"people_column_prefix": "pp_",
53-
"check_before": False,
54-
"fail_if_alien": False,
5553
}
5654

5755

@@ -110,6 +108,11 @@ def household_mapping() -> Mapping[int, Mock]:
110108
}
111109

112110

111+
@pytest.fixture
112+
def people_mapping(people_sheet: Sheet) -> Mapping[int, Mock]:
113+
return {i: Mock() for i in range(len(list(people_sheet)))}
114+
115+
113116
def test_column_configuration_error_format() -> None:
114117
error = ColumnConfigurationError(column_name := "test_column")
115118
assert column_name in str(error)
@@ -317,60 +320,34 @@ def test_process_beneficiaries_failed_to_create(
317320
assert exc_info.value.sheet_name == expected_sheet_name
318321

319322

320-
def test_validate_beneficiaries(config: Config, household_mapping: Mapping[int, Mock]) -> None:
321-
config["check_before"] = True
322-
323-
validate_beneficiaries(config, household_mapping)
324-
325-
for household in household_mapping.values():
326-
household.validate_with_checker.assert_called_once()
327-
328-
329-
def test_validate_beneficiaries_raises_exception_on_failed_validation(
330-
config: Config, household_mapping: Mapping[int, Mock]
331-
) -> None:
332-
config["check_before"] = True
333-
household_mapping[HOUSEHOLD_1_PK].validate_with_checker.return_value = False
334-
335-
with pytest.raises(BeneficiaryValidationError):
336-
validate_beneficiaries(config, household_mapping)
337-
338-
339-
def test_validate_beneficiaries_check_before_is_false(config: Config, household_mapping: Mapping[int, Mock]) -> None:
340-
config["check_before"] = False
341-
342-
validate_beneficiaries(config, household_mapping)
343-
344-
for household in household_mapping.values():
345-
household.validate_with_checker.assert_not_called()
346-
347-
348323
def test_import_from_rdi(
349324
mocker: MockerFixture,
350325
config: Config,
351326
household_sheet: Sheet,
352327
individual_sheet: Sheet,
353328
people_sheet: Sheet,
354329
household_mapping: Mapping[int, Mock],
330+
people_mapping: Mapping[int, Mock],
355331
) -> None:
356332
job = Mock()
357333
job.config = config
358334
batch_class_mock = mocker.patch("country_workspace.datasources.rdi.Batch")
359335
read_sheets_mock = mocker.patch("country_workspace.datasources.rdi.read_sheets")
360336
process_beneficiaries_mock = mocker.patch("country_workspace.datasources.rdi.process_beneficiaries")
361337
validate_beneficiaries_mock = mocker.patch("country_workspace.datasources.rdi.validate_beneficiaries")
338+
partial_mock = mocker.patch("country_workspace.datasources.rdi.partial")
362339
if config["master_detail"]:
363340
read_sheets_mock.return_value = household_sheet, individual_sheet
364341
process_households_mock = mocker.patch("country_workspace.datasources.rdi.process_households")
365342
process_households_mock.return_value = household_mapping
366343
process_beneficiaries_mock.return_value = (processed_individuals := list(individual_sheet))
367344
else:
368345
read_sheets_mock.return_value = (people_sheet,)
369-
people_mapping = {i: Mock() for i in range(len(list(people_sheet)))}
370346
process_beneficiaries_mock.return_value = people_mapping
371347

372348
result = import_from_rdi(job)
373349

350+
partial_mock.assert_called_once_with(validate_beneficiaries_mock, config=config, office=job.program.country_office)
374351
if config["master_detail"]:
375352
assert result == {"household": len(household_mapping), "individual": len(processed_individuals)}
376353
process_households_mock.assert_called_once_with(
@@ -383,7 +360,7 @@ def test_import_from_rdi(
383360
config,
384361
household_mapping,
385362
)
386-
validate_beneficiaries_mock.assert_called_once_with(config, household_mapping)
363+
partial_mock.return_value.assert_called_once_with(household_mapping)
387364
else:
388365
assert result == {"people": len(people_mapping)}
389366
process_beneficiaries_mock.assert_called_once_with(
@@ -392,7 +369,7 @@ def test_import_from_rdi(
392369
batch_class_mock.objects.create.return_value,
393370
config,
394371
)
395-
validate_beneficiaries_mock.assert_called_once_with(config, people_mapping)
372+
partial_mock.return_value.assert_called_once_with(people_mapping)
396373

397374
batch_class_mock.objects.create.assert_called_once_with(
398375
name=config["batch_name"],

tests/functional/test_f_import_data.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,9 @@ def test_rdi_import_tab(browser_program):
6666
browser_program.assert_element_present('button[data-input-value="rdi"].selected')
6767

6868
browser_program.assert_element_visible("#id_rdi-batch_name")
69+
browser_program.assert_element_visible("#id_rdi-validate_mode")
6970
browser_program.assert_element_visible("#id_rdi-first_line")
7071
browser_program.assert_element_visible("#id_rdi-file")
71-
browser_program.assert_element_visible("#id_rdi-check_before")
72-
browser_program.assert_element_visible("#id_rdi-fail_if_alien")
7372

7473

7574
@pytest.mark.selenium
@@ -80,10 +79,9 @@ def test_rdi_import_tab_with_beneficiary(browser_program_beneficiary, program_be
8079
browser_program_beneficiary.assert_element_present('button[data-input-value="rdi"].selected')
8180

8281
browser_program_beneficiary.assert_element_visible("#id_rdi-batch_name")
82+
browser_program_beneficiary.assert_element_visible("#id_rdi-validate_mode")
8383
browser_program_beneficiary.assert_element_visible("#id_rdi-first_line")
8484
browser_program_beneficiary.assert_element_visible("#id_rdi-file")
85-
browser_program_beneficiary.assert_element_visible("#id_rdi-check_before")
86-
browser_program_beneficiary.assert_element_visible("#id_rdi-fail_if_alien")
8785

8886
if program_beneficiary.beneficiary_group.master_detail:
8987
browser_program_beneficiary.assert_element_visible("#id_rdi-pk_column_name")
@@ -107,10 +105,9 @@ def test_aurora_import_tab(browser_program):
107105

108106
aurora_input_ids = [
109107
"#id_aurora-batch_name",
108+
"#id_aurora-validate_mode",
110109
"#id_aurora-registration",
111110
"#id_aurora-individuals_column_prefix",
112-
"#id_aurora-check_before",
113-
"#id_aurora-fail_if_alien",
114111
]
115112

116113
for input_id in aurora_input_ids:
@@ -129,10 +126,9 @@ def test_aurora_import_tab_with_beneficiary(browser_program_beneficiary, program
129126

130127
common_input_ids = [
131128
"#id_aurora-batch_name",
129+
"#id_aurora-validate_mode",
132130
"#id_aurora-registration",
133131
"#id_aurora-individuals_column_prefix",
134-
"#id_aurora-check_before",
135-
"#id_aurora-fail_if_alien",
136132
]
137133

138134
for input_id in common_input_ids:
@@ -159,7 +155,6 @@ def test_kobo_import_tab(browser_program):
159155
browser_program.assert_element_present('button[data-input-value="kobo"].selected')
160156

161157
browser_program.assert_element_visible("#id_kobo-batch_name")
158+
browser_program.assert_element_visible("#id_kobo-validate_mode")
162159
browser_program.assert_element_visible("#id_kobo-project_id")
163160
browser_program.assert_element_visible("#id_kobo-individual_records_field")
164-
browser_program.assert_element_visible("#id_kobo-check_before")
165-
browser_program.assert_element_visible("#id_kobo-fail_if_alien")

tests/functional/test_f_rdi_import.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.core.management import call_command
55

66
from country_workspace.models import Household, Individual
7+
from country_workspace.workspaces.admin.forms import ValidateMode
78
from testutils.factories import OfficeFactory
89
from testutils.factories.program import BeneficiaryGroupFactory
910
from testutils.factories import CountryProgramFactory
@@ -49,6 +50,7 @@ def test_rdi_import_household(browser, program):
4950
browser.click_link("Programme")
5051
browser.click("#btn-import_data")
5152
browser.fill('input[name="rdi-batch_name"]', "Test Batch")
53+
browser.select_option_by_value('select[name="rdi-validate_mode"]', ValidateMode.NONE.value)
5254
test_file_path = Path(__file__).parent.parent / "data/rdi_one.xlsx"
5355
browser.choose_file('input[type="file"]', str(test_file_path))
5456
browser.click('input[type="submit"][value="Import"]')
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import pytest
2+
from unittest.mock import Mock
3+
4+
from country_workspace.validators.beneficiaries import validate_beneficiaries
5+
from country_workspace.workspaces.admin.forms import ValidateMode
6+
from country_workspace.utils.types import BeneficiaryMapping
7+
from country_workspace.utils.config import ValidateModeConfig
8+
from country_workspace.models.office import Office
9+
from country_workspace.workspaces.exceptions import BeneficiaryValidationError
10+
11+
12+
@pytest.fixture
13+
def mapping_and_office() -> tuple[BeneficiaryMapping, Office]:
14+
from testutils.factories import CountryHouseholdFactory, ProgramFactory
15+
16+
program = ProgramFactory()
17+
mapping: BeneficiaryMapping = {}
18+
for i in range(1, 3):
19+
beneficiary = CountryHouseholdFactory(batch__program=program, batch__country_office=program.country_office)
20+
beneficiary.validate_with_checker = Mock(return_value=True)
21+
mapping[i] = beneficiary
22+
return mapping, program.country_office
23+
24+
25+
@pytest.fixture(params=ValidateMode.__members__.values())
26+
def config(request) -> ValidateModeConfig:
27+
return {
28+
"validate_mode": request.param,
29+
}
30+
31+
32+
@pytest.fixture(params=[mode for mode in ValidateMode if mode != ValidateMode.NONE])
33+
def failing_config(request) -> ValidateModeConfig:
34+
return {
35+
"validate_mode": request.param,
36+
}
37+
38+
39+
def test_validate_beneficiaries(
40+
config: ValidateModeConfig, mapping_and_office: tuple[BeneficiaryMapping, Office]
41+
) -> None:
42+
beneficiary_mapping, office = mapping_and_office
43+
validate_beneficiaries(beneficiary_mapping, config, office)
44+
for beneficiary in beneficiary_mapping.values():
45+
if config["validate_mode"] == ValidateMode.NONE:
46+
beneficiary.validate_with_checker.assert_not_called()
47+
else:
48+
fail_if_alien = config["validate_mode"] == ValidateMode.CHECK_AND_FAIL_IF_ALIEN
49+
beneficiary.validate_with_checker.assert_called_once_with(fail_if_alien=fail_if_alien)
50+
51+
52+
def test_validate_beneficiaries_raises_exception_on_failed_validation(
53+
failing_config: ValidateModeConfig, mapping_and_office: tuple[BeneficiaryMapping, Office]
54+
) -> None:
55+
beneficiary_mapping, office = mapping_and_office
56+
beneficiary_mapping[1].validate_with_checker.return_value = False
57+
58+
with pytest.raises(BeneficiaryValidationError):
59+
validate_beneficiaries(beneficiary_mapping, failing_config, office)

tests/workspace/admin/test_program.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from country_workspace.workspaces.admin import CountryProgramAdmin
1212
from country_workspace.workspaces.admin.program import KOBO_IMPORT_JOB_DESCRIPTION
1313
from country_workspace.workspaces.models import CountryProgram
14+
from country_workspace.workspaces.admin.forms import ValidateMode
1415

1516

1617
def test__country_program_admin__import_kobo__job_description(mocker: MockerFixture) -> None:
@@ -106,6 +107,7 @@ def test_import_kobo_valid_form(program_admin, mock_request, mock_program):
106107

107108
mock_request.POST = {
108109
"kobo-batch_name": "Test Import Batch",
110+
"kobo-validate_mode": ValidateMode.NONE.value,
109111
"kobo-project_id": "test_project_123",
110112
"kobo-individual_records_field": "individual_questions",
111113
"_selected_tab": "kobo",

tests/workspace/test_ws_import.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from country_workspace.models import Office, Individual, Household
1313
from country_workspace.state import state
14+
from country_workspace.workspaces.admin.forms import ValidateMode
1415
from country_workspace.contrib.aurora.exceptions import TooManyBeneficiaryError
1516
from tests.contrib.aurora import stub
1617

@@ -89,6 +90,7 @@ def form_import_rdi(app: "DjangoTestApp", program: "CountryProgram") -> forms.Fo
8990
data = (Path(__file__).parent.parent / "data/rdi_one.xlsx").read_bytes()
9091
res = app.get(url)
9192

93+
res.forms["import-file"]["rdi-validate_mode"] = ValidateMode.NONE.value
9294
res.forms["import-file"]["_selected_tab"] = "rdi"
9395
res.forms["import-file"]["rdi-file"] = Upload("rdi_one.xlsx", data)
9496

@@ -161,6 +163,7 @@ def form_aurora(
161163

162164
res = app.get(url)
163165
res.forms["import-aurora"]["_selected_tab"] = "aurora"
166+
res.forms["import-aurora"]["aurora-validate_mode"] = ValidateMode.NONE.value
164167
res.forms["import-aurora"]["aurora-registration"] = program.projects.registrations.first().pk
165168

166169
return res.forms["import-aurora"]

0 commit comments

Comments
 (0)