1212from tests .utils_for_converter_tests import ValuesForTests , ErrorValuesForTests
1313from botocore .config import Config
1414from pathlib import Path
15+ from zoneinfo import ZoneInfo
1516from SchemaParser import SchemaParser
1617from Converter import Converter
17- from ConversionChecker import ConversionChecker
18-
18+ from ConversionChecker import ConversionChecker , RecordError
19+ import ExceptionMessages
1920
2021MOCK_ENV_VARS = {
2122 "AWS_SQS_QUEUE_URL" : "https://sqs.eu-west-2.amazonaws.com/123456789012/test-queue" ,
2728 from delta import handler , Converter
2829 from Converter import imms
2930
31+ class TestRecordError (unittest .TestCase ):
32+ def test_fields_and_str (self ):
33+ err = RecordError (
34+ code = 5 ,
35+ message = "Test failed" ,
36+ details = "Something went wrong"
37+ )
38+
39+ # The attributes should round‑trip
40+ self .assertEqual (err .code , 5 )
41+ self .assertEqual (err .message , "Test failed" )
42+ self .assertEqual (err .details , "Something went wrong" )
43+
44+ # __repr__ and __str__ both produce the tuple repr
45+ expected = "(5, 'Test failed', 'Something went wrong')"
46+ self .assertEqual (str (err ), expected )
47+ self .assertEqual (repr (err ), expected )
48+
49+ def test_default_args (self ):
50+ # If you omit arguments they default to None
51+ err = RecordError ()
52+ self .assertIsNone (err .code )
53+ self .assertIsNone (err .message )
54+ self .assertIsNone (err .details )
55+
56+ # repr shows three Nones
57+ self .assertEqual (str (err ), "(None, None, None)" )
3058
3159@patch .dict ("os.environ" , MOCK_ENV_VARS , clear = True )
3260@mock_dynamodb
@@ -66,12 +94,12 @@ def setUp(self):
6694 },
6795 ],
6896 )
69-
97+
7098 @staticmethod
7199 def get_event (event_name = "INSERT" , operation = "operation" , supplier = "EMIS" ):
72100 """Returns test event data."""
73101 return ValuesForTests .get_event (event_name , operation , supplier )
74-
102+
75103 def assert_dynamodb_record (self , operation_flag , items , expected_values , expected_imms , response ):
76104 """
77105 Asserts that a record with the expected structure exists in DynamoDB.
@@ -99,7 +127,7 @@ def assert_dynamodb_record(self, operation_flag, items, expected_values, expecte
99127 for key , expected_value in expected_values .items ():
100128 self .assertIn (key , filtered_items [0 ], f"{ key } is missing" )
101129 self .assertEqual (filtered_items [0 ][key ], expected_value , f"{ key } mismatch" )
102-
130+
103131 def test_fhir_converter_json_direct_data (self ):
104132 """it should convert fhir json data to flat json"""
105133 imms .clear ()
@@ -125,7 +153,7 @@ def test_fhir_converter_json_direct_data(self):
125153
126154 end = time .time ()
127155 print (end - start )
128-
156+
129157 def test_fhir_converter_json_error_scenario (self ):
130158 """it should convert fhir json data to flat json - error scenarios"""
131159 error_test_cases = [ErrorValuesForTests .missing_json , ErrorValuesForTests .json_dob_error ]
@@ -157,7 +185,7 @@ def test_fhir_converter_json_error_scenario(self):
157185
158186 end = time .time ()
159187 print (end - start )
160-
188+
161189 def test_handler_imms_convert_to_flat_json (self ):
162190 """Test that the Imms field contains the correct flat JSON data for CREATE, UPDATE, and DELETE operations."""
163191 expected_action_flags = [
@@ -188,13 +216,13 @@ def test_handler_imms_convert_to_flat_json(self):
188216 result = self .table .scan ()
189217 items = result .get ("Items" , [])
190218 self .clear_table ()
191-
219+
192220 def test_conversionCount (self ):
193221 parser = SchemaParser ()
194222 schema_data = {"conversions" : [{"conversion" : "type1" }, {"conversion" : "type2" }, {"conversion" : "type3" }]}
195223 parser .parseSchema (schema_data )
196224 self .assertEqual (parser .conversionCount (), 3 )
197-
225+
198226 def test_getConversion (self ):
199227 parser = SchemaParser ()
200228 schema_data = {"conversions" : [{"conversion" : "type1" }, {"conversion" : "type2" }, {"conversion" : "type3" }]}
@@ -297,7 +325,31 @@ def test_conversion_exceptions(self, mock_get_key_value, mock_get_conversions):
297325 error_records [0 ]["message" ],
298326 )
299327 self .assertEqual (error_records [0 ]["code" ], 0 )
328+
329+ @patch ("ConversionChecker.LookUpData" )
330+ def test_log_error (self , MockLookUpData ):
331+ # Instantiate ConversionChecker
332+ checker = ConversionChecker (dataParser = None , summarise = False , report_unexpected_exception = True )
333+
334+ # Simulate an exception
335+ exception = ValueError ("Invalid value" )
336+
337+ # Call the _log_error method twice to also check deduplication
338+ checker ._log_error ("test_field" , "test_value" , exception )
339+ checker ._log_error ("test_field" , "test_value" , exception )
340+
341+ # Assert that only one error record is added due to deduplication
342+ self .assertEqual (len (checker .errorRecords ), 1 )
300343
344+ # Assert that one error record is added
345+ self .assertEqual (len (checker .errorRecords ), 1 )
346+ error = checker .errorRecords [0 ]
347+
348+ # Assert that the error record contains correct details
349+ self .assertEqual (error ["field" ], "test_field" )
350+ self .assertEqual (error ["value" ], "test_value" )
351+ self .assertIn ("Invalid value" , error ["message" ])
352+ self .assertEqual (error ["code" ], ExceptionMessages .RECORD_CHECK_FAILED )
301353
302354 @patch ("ConversionChecker.LookUpData" )
303355 def test_convert_to_not_empty (self , MockLookUpData ):
@@ -334,7 +386,7 @@ def test_convert_to_nhs_number(self, MockLookUpData):
334386 invalid_nhs_number = "1234567890243"
335387 result = checker ._convertToNHSNumber ("NHSNUMBER" ,"fieldName" , invalid_nhs_number , False , True )
336388 self .assertEqual (result , "" , "Invalid NHS number should return empty string" )
337-
389+
338390 @patch ("ConversionChecker.LookUpData" )
339391 def test_convert_to_date (self , MockLookUpData ):
340392 dataParser = Mock ()
@@ -377,18 +429,33 @@ def test_convert_to_date(self, MockLookUpData):
377429 result = checker ._convertToDate ("%Y%m%d" , "fieldName" , "" , False , True )
378430 self .assertEqual (result , "" )
379431
380- #9 Validate all error logs of various responses
432+ # 9. Valid recorded date with timezone
433+ valid_recorded = datetime .now (ZoneInfo ("UTC" )).replace (microsecond = 0 ).isoformat ()
434+ result = checker ._convertToDate ("format:%Y-%m-%d" , "recorded" , valid_recorded , False , True )
435+ self .assertTrue (result .startswith (datetime .now (ZoneInfo ("UTC" )).strftime ("%Y%m%dT%H" )))
436+
437+ # 10. Recorded field: unsupported timezone offset (+02:00)
438+ result = checker ._convertToDate ("%Y%m%d" , "recorded" , "2022-01-01T12:00:00+02:00" , False , True )
439+ self .assertEqual (result , "" )
440+
441+ # 11. Recorded date with invalid format
442+ result = checker ._convertToDate ("format:%Y-%m-%d" , "recorded" , "invalid_date" , False , True )
443+ self .assertEqual (result , "" )
444+
445+ # 12 Validate all error logs of various responses
381446 messages = [err ["message" ] for err in checker .errorRecords ]
382447 print (f"Error Test Case, { messages } " )
383448
384449 self .assertIn ("Date must be in YYYYMMDD format" , messages )
385450 self .assertIn ("Value is not a string" , messages )
386451 self .assertIn ("Partial date not accepted" , messages )
387452 self .assertIn ("Date cannot be in the future" , messages )
453+ self .assertTrue (any (m .startswith ("Unsupported offset" ) for m in messages ))
454+ self .assertIn ("Invalid date format" , messages )
388455
389456 # Confirm Total Errors Per conversion
390- self .assertEqual (len (checker .errorRecords ), 4 )
391-
457+ self .assertEqual (len (checker .errorRecords ), 6 )
458+
392459 @patch ("ConversionChecker.LookUpData" )
393460 def test_convert_to_date_time (self , MockLookUpData ):
394461 dataParser = Mock ()
0 commit comments