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,22 @@ 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 )
2133
2234 def test_check_validity_returns_empty_list_when_data_is_valid (self ):
2335 validation_result = self .batch_dq_checker ._check_validity (VALID_BATCH_IMMUNISATION )
@@ -76,3 +88,77 @@ def test_check_validity_returns_list_of_multiple_invalid_fields_for_multiple_fai
7688 self .assertEqual (
7789 ["NHS_NUMBER" , "PERSON_POSTCODE" , "EXPIRY_DATE" , "DOSE_AMOUNT" , "INDICATION_CODE" ], validation_result
7890 )
91+
92+ def test_check_timeliness_calculates_the_timeliness_diffs (self ):
93+ timeliness_output = self .batch_dq_checker ._check_timeliness (VALID_BATCH_IMMUNISATION , self .mock_fixed_datetime )
94+
95+ self .assertEqual (4 , timeliness_output .recorded_timeliness_days )
96+ self .assertEqual (785550 , timeliness_output .ingested_timeliness_seconds )
97+
98+ def test_check_timeliness_returns_none_for_recorded_timeliness_when_relevant_field_invalid (self ):
99+ invalid_batch_imms_payload = copy .deepcopy (VALID_BATCH_IMMUNISATION )
100+ invalid_batch_imms_payload ["RECORDED_DATE" ] = ""
101+
102+ timeliness_output = self .batch_dq_checker ._check_timeliness (invalid_batch_imms_payload , self .mock_fixed_datetime )
103+
104+ self .assertIsNone (timeliness_output .recorded_timeliness_days )
105+ self .assertEqual (785550 , timeliness_output .ingested_timeliness_seconds )
106+
107+ def test_check_timeliness_returns_none_for_both_when_date_and_time_field_invalid (self ):
108+ invalid_batch_imms_payload = copy .deepcopy (VALID_BATCH_IMMUNISATION )
109+ invalid_batch_imms_payload ["DATE_AND_TIME" ] = "20245"
110+
111+ timeliness_output = self .batch_dq_checker ._check_timeliness (invalid_batch_imms_payload , self .mock_fixed_datetime )
112+
113+ self .assertIsNone (timeliness_output .recorded_timeliness_days )
114+ self .assertIsNone (timeliness_output .ingested_timeliness_seconds )
115+
116+ def test_run_checks_returns_correct_output_for_valid_data_for_csv_payload (self ):
117+ result = self .batch_dq_checker .run_checks (VALID_BATCH_IMMUNISATION )
118+ self .assert_successful_result (result )
119+
120+ def test_run_checks_returns_correct_output_for_valid_data_for_fhir_payload (self ):
121+ result = self .fhir_json_dq_checker .run_checks (VALID_FHIR_IMMUNISATION )
122+ self .assert_successful_result (result )
123+
124+ def test_run_checks_returns_correct_output_for_invalid_data_for_csv_payload (self ):
125+ invalid_batch_imms_payload = copy .deepcopy (VALID_BATCH_IMMUNISATION )
126+ invalid_batch_imms_payload ["NHS_NUMBER" ] = "12345678901"
127+ invalid_batch_imms_payload ["RECORDED_DATE" ] = "20240137"
128+ invalid_batch_imms_payload ["PERSON_DOB" ] = ""
129+ invalid_batch_imms_payload ["DOSE_AMOUNT" ] = "6.789"
130+ invalid_batch_imms_payload ["BATCH_NUMBER" ] = ""
131+
132+ result = self .batch_dq_checker .run_checks (invalid_batch_imms_payload )
133+
134+ self .assertEqual ([], result .missing_fields .optional_fields )
135+ self .assertEqual (["PERSON_DOB" ], result .missing_fields .mandatory_fields )
136+ self .assertEqual (["BATCH_NUMBER" ], result .missing_fields .required_fields )
137+
138+ # Fields which are subject to validation and are also empty will appear in both the completeness and validity
139+ # checks e.g. PERSON_DOB
140+ self .assertEqual (["NHS_NUMBER" , "PERSON_DOB" , "DOSE_AMOUNT" ], result .invalid_fields )
141+ self .assertIsNone (result .timeliness .recorded_timeliness_days )
142+ self .assertEqual (785550 , result .timeliness .ingested_timeliness_seconds )
143+
144+ def test_run_checks_returns_correct_output_for_invalid_data_for_fhir_payload (self ):
145+ invalid_fhir_imms_payload = copy .deepcopy (VALID_FHIR_IMMUNISATION )
146+ invalid_fhir_imms_payload ["contained" ][1 ]["identifier" ][0 ]["value" ] = "12345678901"
147+ invalid_fhir_imms_payload ["recorded" ] = "2024-01-37"
148+ del invalid_fhir_imms_payload ["contained" ][1 ]["birthDate" ]
149+ invalid_fhir_imms_payload ["doseQuantity" ]["value" ] = "6.789"
150+ invalid_fhir_imms_payload ["lotNumber" ] = ""
151+
152+ result = self .fhir_json_dq_checker .run_checks (invalid_fhir_imms_payload )
153+
154+ self .assertEqual ([], result .missing_fields .optional_fields )
155+ # Worth noting that due to the use of the fhir converter, invalid dates will be mapped to an empty string hence
156+ # will also show up here where they would not in batch validation
157+ self .assertEqual (["PERSON_DOB" , "RECORDED_DATE" ], result .missing_fields .mandatory_fields )
158+ self .assertEqual (["BATCH_NUMBER" ], result .missing_fields .required_fields )
159+
160+ # Fields which are subject to validation and are also empty will appear in both the completeness and validity
161+ # checks e.g. PERSON_DOB
162+ self .assertEqual (["NHS_NUMBER" , "PERSON_DOB" , "DOSE_AMOUNT" ], result .invalid_fields )
163+ self .assertIsNone (result .timeliness .recorded_timeliness_days )
164+ self .assertEqual (785550 , result .timeliness .ingested_timeliness_seconds )
0 commit comments