Skip to content

Commit 4d76908

Browse files
committed
bump test
1 parent de2b372 commit 4d76908

1 file changed

Lines changed: 162 additions & 40 deletions

File tree

lambdas/mns_publisher/tests/test_create_notification.py

Lines changed: 162 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
import copy
12
import json
23
import unittest
4+
from pathlib import Path
35
from unittest.mock import MagicMock, patch
46

57
from constants import IMMUNISATION_TYPE, SPEC_VERSION
6-
from create_notification import calculate_age_at_vaccination, create_mns_notification
8+
from create_notification import (
9+
calculate_age_at_vaccination,
10+
create_mns_notification,
11+
get_practitioner_details_from_pds,
12+
)
713

814

915
class TestCalculateAgeAtVaccination(unittest.TestCase):
1016
"""Tests for age calculation at vaccination time."""
1117

1218
def test_age_calculation_yyyymmdd_format(self):
13-
"""Test age calculation with YYYYMMDD format (actual format from payload)."""
19+
"""Test age calculation with YYYYMMDD format."""
1420
birth_date = "20040609"
1521
vaccination_date = "20260212"
1622

@@ -54,48 +60,131 @@ def test_age_calculation_infant(self):
5460

5561
self.assertEqual(age, 0)
5662

63+
def test_age_calculation_leap_year_birthday(self):
64+
"""Test age calculation with leap year birthday."""
65+
birth_date = "20000229"
66+
vaccination_date = "20240228"
67+
68+
age = calculate_age_at_vaccination(birth_date, vaccination_date)
69+
70+
self.assertEqual(age, 23)
71+
72+
def test_age_calculation_same_day_different_year(self):
73+
"""Test age calculation for same day in different year."""
74+
birth_date = "20000101"
75+
vaccination_date = "20250101"
76+
77+
age = calculate_age_at_vaccination(birth_date, vaccination_date)
78+
79+
self.assertEqual(age, 25)
80+
81+
82+
class TestGetPractitionerDetailsFromPds(unittest.TestCase):
83+
"""Tests for get_practitioner_details_from_pds function."""
84+
85+
@patch("create_notification.pds_get_patient_details")
86+
@patch("create_notification.logger")
87+
def test_get_practitioner_success(self, mock_logger, mock_pds_get):
88+
"""Test successful retrieval of GP ODS code."""
89+
mock_pds_get.return_value = {"generalPractitioner": {"value": "Y12345"}}
90+
91+
result = get_practitioner_details_from_pds("9481152782")
92+
93+
self.assertEqual(result, "Y12345")
94+
mock_pds_get.assert_called_once_with("9481152782")
95+
mock_logger.warning.assert_not_called()
96+
97+
@patch("create_notification.pds_get_patient_details")
98+
@patch("create_notification.logger")
99+
def test_get_practitioner_no_gp_details(self, mock_logger, mock_pds_get):
100+
"""Test when generalPractitioner is missing."""
101+
mock_pds_get.return_value = {"name": "John Doe"}
102+
103+
result = get_practitioner_details_from_pds("9481152782")
104+
105+
self.assertIsNone(result)
106+
mock_logger.warning.assert_called_once_with("No patient details found for NHS number")
107+
108+
@patch("create_notification.pds_get_patient_details")
109+
@patch("create_notification.logger")
110+
def test_get_practitioner_gp_is_none(self, mock_logger, mock_pds_get):
111+
"""Test when generalPractitioner is None."""
112+
mock_pds_get.return_value = {"generalPractitioner": None}
113+
114+
result = get_practitioner_details_from_pds("9481152782")
115+
116+
self.assertIsNone(result)
117+
mock_logger.warning.assert_called_once()
118+
119+
@patch("create_notification.pds_get_patient_details")
120+
@patch("create_notification.logger")
121+
def test_get_practitioner_no_value_field(self, mock_logger, mock_pds_get):
122+
"""Test when value field is missing from generalPractitioner."""
123+
mock_pds_get.return_value = {"generalPractitioner": {"system": "https://fhir.nhs.uk"}}
124+
125+
result = get_practitioner_details_from_pds("9481152782")
126+
127+
self.assertIsNone(result)
128+
mock_logger.warning.assert_called_with("GP ODS code not found in practitioner details")
129+
130+
@patch("create_notification.pds_get_patient_details")
131+
@patch("create_notification.logger")
132+
def test_get_practitioner_empty_value(self, mock_logger, mock_pds_get):
133+
"""Test when value is empty string."""
134+
mock_pds_get.return_value = {"generalPractitioner": {"value": ""}}
135+
136+
result = get_practitioner_details_from_pds("9481152782")
137+
138+
self.assertIsNone(result)
139+
mock_logger.warning.assert_called_with("GP ODS code not found in practitioner details")
140+
141+
@patch("create_notification.pds_get_patient_details")
142+
@patch("create_notification.logger")
143+
def test_get_practitioner_pds_exception(self, mock_logger, mock_pds_get):
144+
"""Test when PDS API raises exception."""
145+
mock_pds_get.side_effect = Exception("PDS API error")
146+
147+
with self.assertRaises(Exception) as context:
148+
get_practitioner_details_from_pds("9481152782")
149+
150+
self.assertEqual(str(context.exception), "PDS API error")
151+
mock_logger.exception.assert_called_once()
152+
153+
@patch("create_notification.pds_get_patient_details")
154+
@patch("create_notification.logger")
155+
def test_get_practitioner_patient_details_none(self, mock_logger, mock_pds_get):
156+
"""Test when pds_get_patient_details returns None."""
157+
mock_pds_get.return_value = None
158+
159+
with self.assertRaises(AttributeError):
160+
get_practitioner_details_from_pds("9481152782")
161+
57162

58163
class TestCreateMnsNotification(unittest.TestCase):
59164
"""Tests for MNS notification creation."""
60165

166+
@classmethod
167+
def setUpClass(cls):
168+
"""Load the sample SQS event once for all tests."""
169+
sample_event_path = Path(__file__).parent.parent / "tests" / "sqs_event.json"
170+
with open(sample_event_path, "r") as f:
171+
raw_event = json.load(f)
172+
173+
# Convert body from dict to JSON string (as it would be in real SQS)
174+
if isinstance(raw_event.get("body"), dict):
175+
raw_event["body"] = json.dumps(raw_event["body"])
176+
cls.sample_sqs_event = raw_event
177+
61178
def setUp(self):
62179
"""Set up test fixtures."""
63-
self.sample_sqs_event = {
64-
"messageId": "98ed30eb-829f-41df-8a73-57fef70cf161",
65-
"body": json.dumps(
66-
{
67-
"eventID": "b1ba2a48eae68bf43a8cb49b400788c6",
68-
"eventName": "INSERT",
69-
"dynamodb": {
70-
"NewImage": {
71-
"ImmsID": {"S": "d058014c-b0fd-4471-8db9-3316175eb825"},
72-
"VaccineType": {"S": "hib"},
73-
"SupplierSystem": {"S": "TPP"},
74-
"DateTimeStamp": {"S": "2026-02-12T17:45:37+00:00"},
75-
"Imms": {
76-
"M": {
77-
"NHS_NUMBER": {"S": "9481152782"},
78-
"PERSON_DOB": {"S": "20040609"},
79-
"DATE_AND_TIME": {"S": "20260212T174437"},
80-
"VACCINE_TYPE": {"S": "hib"},
81-
"SITE_CODE": {"S": "B0C4P"},
82-
}
83-
},
84-
"Operation": {"S": "CREATE"},
85-
}
86-
},
87-
}
88-
),
89-
}
90-
91180
self.expected_gp_ods_code = "Y12345"
92181
self.expected_immunisation_url = "https://int.api.service.nhs.uk/immunisation-fhir-api"
93182

94183
@patch("create_notification.get_practitioner_details_from_pds")
95184
@patch("create_notification.get_service_url")
96185
@patch("create_notification.uuid.uuid4")
97-
def test_create_mns_notification_success(self, mock_uuid, mock_get_service_url, mock_get_gp):
98-
"""Test successful MNS notification creation."""
186+
def test_create_mns_notification_success_with_real_payload(self, mock_uuid, mock_get_service_url, mock_get_gp):
187+
"""Test successful MNS notification creation using real SQS event."""
99188
mock_uuid.return_value = MagicMock(hex="236a1d4a-5d69-4fa9-9c7f-e72bf505aa5b")
100189
mock_get_service_url.return_value = self.expected_immunisation_url
101190
mock_get_gp.return_value = self.expected_gp_ods_code
@@ -113,8 +202,8 @@ def test_create_mns_notification_success(self, mock_uuid, mock_get_service_url,
113202

114203
@patch("create_notification.get_practitioner_details_from_pds")
115204
@patch("create_notification.get_service_url")
116-
def test_create_mns_notification_dataref_format(self, mock_get_service_url, mock_get_gp):
117-
"""Test dataref URL format is correct."""
205+
def test_create_mns_notification_dataref_format_real_payload(self, mock_get_service_url, mock_get_gp):
206+
"""Test dataref URL format is correct with real payload."""
118207
mock_get_service_url.return_value = self.expected_immunisation_url
119208
mock_get_gp.return_value = self.expected_gp_ods_code
120209

@@ -125,8 +214,8 @@ def test_create_mns_notification_dataref_format(self, mock_get_service_url, mock
125214

126215
@patch("create_notification.get_practitioner_details_from_pds")
127216
@patch("create_notification.get_service_url")
128-
def test_create_mns_notification_filtering_fields(self, mock_get_service_url, mock_get_gp):
129-
"""Test all filtering fields are populated correctly."""
217+
def test_create_mns_notification_filtering_fields_real_payload(self, mock_get_service_url, mock_get_gp):
218+
"""Test all filtering fields are populated correctly with real payload."""
130219
mock_get_service_url.return_value = self.expected_immunisation_url
131220
mock_get_gp.return_value = self.expected_gp_ods_code
132221

@@ -142,8 +231,8 @@ def test_create_mns_notification_filtering_fields(self, mock_get_service_url, mo
142231

143232
@patch("create_notification.get_practitioner_details_from_pds")
144233
@patch("create_notification.get_service_url")
145-
def test_create_mns_notification_age_calculation(self, mock_get_service_url, mock_get_gp):
146-
"""Test patient age is calculated correctly."""
234+
def test_create_mns_notification_age_calculation_real_payload(self, mock_get_service_url, mock_get_gp):
235+
"""Test patient age is calculated correctly with real payload."""
147236
mock_get_service_url.return_value = self.expected_immunisation_url
148237
mock_get_gp.return_value = self.expected_gp_ods_code
149238

@@ -153,8 +242,8 @@ def test_create_mns_notification_age_calculation(self, mock_get_service_url, moc
153242

154243
@patch("create_notification.get_practitioner_details_from_pds")
155244
@patch("create_notification.get_service_url")
156-
def test_create_mns_notification_calls_get_practitioner(self, mock_get_service_url, mock_get_gp):
157-
"""Test get_practitioner_details_from_pds is called with correct NHS number."""
245+
def test_create_mns_notification_calls_get_practitioner_real_payload(self, mock_get_service_url, mock_get_gp):
246+
"""Test get_practitioner_details_from_pds is called with correct NHS number from real payload."""
158247
mock_get_service_url.return_value = self.expected_immunisation_url
159248
mock_get_gp.return_value = self.expected_gp_ods_code
160249

@@ -219,6 +308,39 @@ def test_create_mns_notification_required_fields_present(self, mock_get_service_
219308
for field in required_fields:
220309
self.assertIn(field, result, f"Required field '{field}' missing")
221310

311+
@patch("create_notification.get_practitioner_details_from_pds")
312+
@patch("create_notification.get_service_url")
313+
def test_create_mns_notification_missing_imms_data_field(self, mock_get_service_url, mock_get_gp):
314+
"""Test handling when a required field is missing from imms_data."""
315+
mock_get_service_url.return_value = self.expected_immunisation_url
316+
mock_get_gp.return_value = self.expected_gp_ods_code
317+
318+
incomplete_event = {
319+
"messageId": "test-id",
320+
"body": json.dumps({"dynamodb": {"NewImage": {"ImmsID": {"S": "test-id"}}}}),
321+
}
322+
323+
with self.assertRaises((KeyError, TypeError)):
324+
create_mns_notification(incomplete_event)
325+
326+
327+
@patch("create_notification.get_practitioner_details_from_pds")
328+
@patch("create_notification.get_service_url")
329+
def test_create_mns_notification_with_update_action(self, mock_get_service_url, mock_get_gp):
330+
"""Test notification creation with UPDATE action using real payload structure."""
331+
mock_get_service_url.return_value = self.expected_immunisation_url
332+
mock_get_gp.return_value = self.expected_gp_ods_code
333+
334+
update_event = copy.deepcopy(self.sample_sqs_event)
335+
336+
update_event["body"]["dynamodb"]["NewImage"]["Operation"]["S"] = "UPDATE"
337+
338+
result = create_mns_notification(update_event)
339+
340+
self.assertEqual(result["filtering"]["action"], "UPDATE")
341+
mock_get_service_url.assert_called()
342+
mock_get_gp.assert_called()
343+
222344

223345
if __name__ == "__main__":
224346
unittest.main()

0 commit comments

Comments
 (0)