33import unittest
44from unittest .mock import patch
55
6- from common .data_quality .checker import DataQualityChecker
7- from common .data_quality .completeness import DataQualityCompletenessChecker
8- from test_common .data_quality .sample_values import VALID_BATCH_IMMUNISATION
6+ from common .data_quality .checker import DataQualityChecker , DataQualityOutput
7+ from test_common .data_quality .sample_values import VALID_BATCH_IMMUNISATION , VALID_FHIR_IMMUNISATION
98
109
1110class TestDataQualityChecker (unittest .TestCase ):
@@ -15,9 +14,23 @@ def setUp(self):
1514 self .mock_date_today = date_today_patcher .start ()
1615 self .mock_date_today .date .today .return_value = datetime .date (2024 , 5 , 20 )
1716
18- completeness_checker = DataQualityCompletenessChecker ()
19- self .batch_dq_checker = DataQualityChecker (completeness_checker , is_batch_csv = True )
20- self .fhir_json_dq_checker = DataQualityChecker (completeness_checker , is_batch_csv = False )
17+ # Fix datetime.now
18+ self .mock_fixed_datetime = datetime .datetime (2024 , 5 , 20 , 14 , 12 , 30 , 123 , tzinfo = datetime .timezone .utc )
19+ datetime_now_patcher = patch ("common.data_quality.checker.datetime" , wraps = datetime .datetime )
20+ self .mock_datetime_now = datetime_now_patcher .start ()
21+ self .mock_datetime_now .now .return_value = self .mock_fixed_datetime
22+
23+ self .batch_dq_checker = DataQualityChecker (is_batch_csv = True )
24+ self .fhir_json_dq_checker = DataQualityChecker (is_batch_csv = False )
25+
26+ def assert_successful_result (self , result : DataQualityOutput ) -> None :
27+ self .assertEqual ([], result .missing_fields .optional_fields )
28+ self .assertEqual ([], result .missing_fields .mandatory_fields )
29+ self .assertEqual ([], result .missing_fields .required_fields )
30+ self .assertEqual ([], result .invalid_fields )
31+ self .assertEqual (4 , result .timeliness .recorded_timeliness_days )
32+ self .assertEqual (785550 , result .timeliness .ingested_timeliness_seconds )
33+ self .assertEqual ("2024-05-20T14:12:30.000Z" , result .validation_datetime )
2134
2235 def test_check_validity_returns_empty_list_when_data_is_valid (self ):
2336 validation_result = self .batch_dq_checker ._check_validity (VALID_BATCH_IMMUNISATION )
@@ -76,3 +89,77 @@ def test_check_validity_returns_list_of_multiple_invalid_fields_for_multiple_fai
7689 self .assertEqual (
7790 ["NHS_NUMBER" , "PERSON_POSTCODE" , "EXPIRY_DATE" , "DOSE_AMOUNT" , "INDICATION_CODE" ], validation_result
7891 )
92+
93+ def test_check_timeliness_calculates_the_timeliness_diffs (self ):
94+ timeliness_output = self .batch_dq_checker ._check_timeliness (VALID_BATCH_IMMUNISATION , self .mock_fixed_datetime )
95+
96+ self .assertEqual (4 , timeliness_output .recorded_timeliness_days )
97+ self .assertEqual (785550 , timeliness_output .ingested_timeliness_seconds )
98+
99+ def test_check_timeliness_returns_none_for_recorded_timeliness_when_relevant_field_invalid (self ):
100+ invalid_batch_imms_payload = copy .deepcopy (VALID_BATCH_IMMUNISATION )
101+ invalid_batch_imms_payload ["RECORDED_DATE" ] = ""
102+
103+ timeliness_output = self .batch_dq_checker ._check_timeliness (invalid_batch_imms_payload , self .mock_fixed_datetime )
104+
105+ self .assertIsNone (timeliness_output .recorded_timeliness_days )
106+ self .assertEqual (785550 , timeliness_output .ingested_timeliness_seconds )
107+
108+ def test_check_timeliness_returns_none_for_both_when_date_and_time_field_invalid (self ):
109+ invalid_batch_imms_payload = copy .deepcopy (VALID_BATCH_IMMUNISATION )
110+ invalid_batch_imms_payload ["DATE_AND_TIME" ] = "20245"
111+
112+ timeliness_output = self .batch_dq_checker ._check_timeliness (invalid_batch_imms_payload , self .mock_fixed_datetime )
113+
114+ self .assertIsNone (timeliness_output .recorded_timeliness_days )
115+ self .assertIsNone (timeliness_output .ingested_timeliness_seconds )
116+
117+ def test_run_checks_returns_correct_output_for_valid_data_for_csv_payload (self ):
118+ result = self .batch_dq_checker .run_checks (VALID_BATCH_IMMUNISATION )
119+ self .assert_successful_result (result )
120+
121+ def test_run_checks_returns_correct_output_for_valid_data_for_fhir_payload (self ):
122+ result = self .fhir_json_dq_checker .run_checks (VALID_FHIR_IMMUNISATION )
123+ self .assert_successful_result (result )
124+
125+ def test_run_checks_returns_correct_output_for_invalid_data_for_csv_payload (self ):
126+ invalid_batch_imms_payload = copy .deepcopy (VALID_BATCH_IMMUNISATION )
127+ invalid_batch_imms_payload ["NHS_NUMBER" ] = "12345678901"
128+ invalid_batch_imms_payload ["RECORDED_DATE" ] = "20240137"
129+ invalid_batch_imms_payload ["PERSON_DOB" ] = ""
130+ invalid_batch_imms_payload ["DOSE_AMOUNT" ] = "6.789"
131+ invalid_batch_imms_payload ["BATCH_NUMBER" ] = ""
132+
133+ result = self .batch_dq_checker .run_checks (invalid_batch_imms_payload )
134+
135+ self .assertEqual ([], result .missing_fields .optional_fields )
136+ self .assertEqual (["PERSON_DOB" ], result .missing_fields .mandatory_fields )
137+ self .assertEqual (["BATCH_NUMBER" ], result .missing_fields .required_fields )
138+
139+ # Fields which are subject to validation and are also empty will appear in both the completeness and validity
140+ # checks e.g. PERSON_DOB
141+ self .assertEqual (["NHS_NUMBER" , "PERSON_DOB" , "DOSE_AMOUNT" ], result .invalid_fields )
142+ self .assertIsNone (result .timeliness .recorded_timeliness_days )
143+ self .assertEqual (785550 , result .timeliness .ingested_timeliness_seconds )
144+
145+ def test_run_checks_returns_correct_output_for_invalid_data_for_fhir_payload (self ):
146+ invalid_fhir_imms_payload = copy .deepcopy (VALID_FHIR_IMMUNISATION )
147+ invalid_fhir_imms_payload ["contained" ][1 ]["identifier" ][0 ]["value" ] = "12345678901"
148+ invalid_fhir_imms_payload ["recorded" ] = "2024-01-37"
149+ del invalid_fhir_imms_payload ["contained" ][1 ]["birthDate" ]
150+ invalid_fhir_imms_payload ["doseQuantity" ]["value" ] = "6.789"
151+ invalid_fhir_imms_payload ["lotNumber" ] = ""
152+
153+ result = self .fhir_json_dq_checker .run_checks (invalid_fhir_imms_payload )
154+
155+ self .assertEqual ([], result .missing_fields .optional_fields )
156+ # Worth noting that due to the use of the fhir converter, invalid dates will be mapped to an empty string hence
157+ # will also show up here where they would not in batch validation
158+ self .assertEqual (["PERSON_DOB" , "RECORDED_DATE" ], result .missing_fields .mandatory_fields )
159+ self .assertEqual (["BATCH_NUMBER" ], result .missing_fields .required_fields )
160+
161+ # Fields which are subject to validation and are also empty will appear in both the completeness and validity
162+ # checks e.g. PERSON_DOB
163+ self .assertEqual (["NHS_NUMBER" , "PERSON_DOB" , "DOSE_AMOUNT" ], result .invalid_fields )
164+ self .assertIsNone (result .timeliness .recorded_timeliness_days )
165+ self .assertEqual (785550 , result .timeliness .ingested_timeliness_seconds )
0 commit comments