Skip to content

Commit 31858cc

Browse files
committed
VED-755: added pagination, remove ieds_check_exist, modify test
1 parent f637a93 commit 31858cc

File tree

5 files changed

+90
-201
lines changed

5 files changed

+90
-201
lines changed

lambdas/id_sync/src/id_sync.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
from typing import Any, Dict
2-
3-
from common.clients import logger, STREAM_NAME
4-
from common.log_decorator import logging_decorator
5-
from common.aws_lambda_event import AwsLambdaEvent
6-
from exceptions.id_sync_exception import IdSyncException
7-
from record_processor import process_record
8-
91
"""
102
- Parses the incoming AWS event into `AwsLambdaEvent` and iterate its `records`.
113
- Delegate each record to `process_record` and collect `nhs_number` from each result.
124
- If any record has status == "error" raise `IdSyncException` with aggregated nhs_numbers.
135
- Any unexpected error is wrapped into `IdSyncException(message="Error processing id_sync event")`.
146
"""
157

8+
from typing import Any, Dict
9+
from common.clients import logger, STREAM_NAME
10+
from common.log_decorator import logging_decorator
11+
from common.aws_lambda_event import AwsLambdaEvent
12+
from exceptions.id_sync_exception import IdSyncException
13+
from record_processor import process_record
14+
1615

1716
@logging_decorator(prefix="id_sync", stream_name=STREAM_NAME)
1817
def handler(event_data: Dict[str, Any], _context) -> Dict[str, Any]:

lambdas/id_sync/src/ieds_db_operations.py

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,6 @@ def get_ieds_table():
1616
return ieds_table
1717

1818

19-
def ieds_check_exist(id: str) -> bool:
20-
"""Check if a record exists in the IEDS table for the given ID."""
21-
logger.info(f"Check Id exists ID: {id}")
22-
items = get_items_from_patient_id(id, 1)
23-
24-
if items or len(items) > 0:
25-
logger.info(f"Found patient ID: {id}")
26-
return True
27-
return False
28-
29-
3019
BATCH_SIZE = 25
3120

3221

@@ -118,28 +107,62 @@ def ieds_update_patient_id(old_id: str, new_id: str) -> dict:
118107
)
119108

120109

121-
def get_items_from_patient_id(id: str, limit=BATCH_SIZE) -> list:
122-
"""Get all items for patient ID."""
110+
def get_items_from_patient_id(id: str, filter_expression=None) -> list:
111+
"""Query the PatientGSI and paginate through all results.
112+
113+
- Uses LastEvaluatedKey to page until all items are collected.
114+
- If `filter_expression` is provided it will be included as `FilterExpression`.
115+
- Raises `IdSyncException` if the DynamoDB response doesn't include 'Items' or
116+
an underlying error occurs.
117+
"""
123118
logger.info(f"Getting items for patient id: {id}")
124119
patient_pk = f"Patient#{id}"
125-
try:
126-
response = get_ieds_table().query(
127-
IndexName='PatientGSI', # query the GSI
128-
KeyConditionExpression=Key('PatientPK').eq(patient_pk),
129-
Limit=limit
130-
)
131120

132-
if 'Items' not in response or not response['Items']:
121+
all_items: list = []
122+
last_evaluated_key = None
123+
try:
124+
while True:
125+
query_args = {
126+
"IndexName": "PatientGSI",
127+
"KeyConditionExpression": Key('PatientPK').eq(patient_pk),
128+
}
129+
if filter_expression is not None:
130+
query_args["FilterExpression"] = filter_expression
131+
if last_evaluated_key:
132+
query_args["ExclusiveStartKey"] = last_evaluated_key
133+
134+
response = get_ieds_table().query(**query_args)
135+
136+
if "Items" not in response:
137+
# Unexpected DynamoDB response shape - surface as IdSyncException
138+
logger.exception("Unexpected DynamoDB response: missing 'Items'")
139+
raise IdSyncException(
140+
message="No Items in DynamoDB response",
141+
nhs_numbers=[patient_pk],
142+
exception=response,
143+
)
144+
145+
items = response.get("Items", [])
146+
all_items.extend(items)
147+
148+
last_evaluated_key = response.get("LastEvaluatedKey")
149+
if not last_evaluated_key:
150+
break
151+
152+
if not all_items:
133153
logger.warning(f"No items found for patient PK: {patient_pk}")
134154
return []
135155

136-
return response['Items']
156+
return all_items
157+
158+
except IdSyncException:
159+
raise
137160
except Exception as e:
138161
logger.exception(f"Error querying items for patient PK: {patient_pk}")
139162
raise IdSyncException(
140163
message=f"Error querying items for patient PK: {patient_pk}",
141164
nhs_numbers=[patient_pk],
142-
exception=e
165+
exception=e,
143166
)
144167

145168

lambdas/id_sync/src/record_processor.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from typing import Dict, Any
33
from pds_details import pds_get_patient_id, pds_get_patient_details
44
from ieds_db_operations import (
5-
ieds_check_exist,
65
ieds_update_patient_id,
76
extract_patient_resource_from_item,
87
get_items_from_patient_id,
@@ -57,20 +56,26 @@ def process_nhs_number(nhs_number: str) -> Dict[str, Any]:
5756
}
5857
logger.info("Update patient ID from %s to %s", nhs_number, new_nhs_number)
5958

60-
if not ieds_check_exist(nhs_number):
61-
logger.info("No IEDS record found for: %s", nhs_number)
62-
response = {"status": "success", "message": f"No records returned for ID: {nhs_number}"}
63-
return response
6459
try:
6560
pds_details, ieds_details = fetch_demographic_details(nhs_number)
6661
except Exception as e:
67-
logger.exception("process_nhs_number: %s, aborting update", e)
62+
logger.exception("process_nhs_number: failed to fetch demographic details: %s", e)
6863
return {
6964
"status": "error",
7065
"message": str(e),
7166
"nhs_number": nhs_number,
7267
}
7368

69+
# If no IEDS items were returned, nothing to update — return a clear success
70+
# message to match existing test expectations.
71+
if not ieds_details:
72+
logger.info("No IEDS records returned for NHS number: %s", nhs_number)
73+
return {
74+
"status": "success",
75+
"message": f"No records returned for ID: {nhs_number}",
76+
"nhs_number": nhs_number,
77+
}
78+
7479
# If at least one IEDS item matches demographics, proceed with update
7580
if not all(demographics_match(pds_details, detail) for detail in ieds_details):
7681
logger.info("Not all IEDS items matched PDS demographics; skipping update for %s", nhs_number)

lambdas/id_sync/tests/test_ieds_db_operations.py

Lines changed: 0 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -533,142 +533,3 @@ def test_get_items_from_patient_id_no_records(self):
533533

534534
# Assert
535535
self.assertEqual(result, [])
536-
537-
538-
class TestIedsCheckExists(TestIedsDbOperations):
539-
540-
def setUp(self):
541-
"""Set up test fixtures"""
542-
super().setUp()
543-
544-
# Mock get_items_from_patient_id instead of table.query
545-
self.get_items_from_patient_id_patcher = patch('ieds_db_operations.get_items_from_patient_id')
546-
self.mock_get_items_from_patient_id = self.get_items_from_patient_id_patcher.start()
547-
548-
def tearDown(self):
549-
"""Clean up patches"""
550-
super().tearDown()
551-
552-
def test_ieds_check_exist_record_exists(self):
553-
"""Test when record exists in IEDS table"""
554-
# Arrange
555-
patient_id = "test-patient-123"
556-
mock_items = [{'PK': 'Patient#test-patient-123', 'PatientPK': 'Patient#test-patient-123'}]
557-
self.mock_get_items_from_patient_id.return_value = mock_items
558-
559-
# Act
560-
result = ieds_db_operations.ieds_check_exist(patient_id)
561-
562-
# Assert
563-
self.assertTrue(result)
564-
565-
# Verify get_items_from_patient_id was called with correct parameters
566-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)
567-
568-
def test_ieds_check_exist_record_not_exists(self):
569-
"""Test when no record exists in IEDS table"""
570-
# Arrange
571-
patient_id = "test-patient-456"
572-
self.mock_get_items_from_patient_id.return_value = []
573-
574-
# Act
575-
result = ieds_db_operations.ieds_check_exist(patient_id)
576-
577-
# Assert
578-
self.assertFalse(result)
579-
580-
# Verify get_items_from_patient_id was called
581-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)
582-
583-
def test_ieds_check_exist_empty_id(self):
584-
"""Test with empty patient ID"""
585-
# Arrange
586-
patient_id = ""
587-
self.mock_get_items_from_patient_id.return_value = []
588-
589-
# Act
590-
result = ieds_db_operations.ieds_check_exist(patient_id)
591-
592-
# Assert
593-
self.assertFalse(result)
594-
595-
# Verify get_items_from_patient_id was called with empty ID
596-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)
597-
598-
def test_ieds_check_exist_none_id(self):
599-
"""Test with None patient ID"""
600-
# Arrange
601-
patient_id = None
602-
self.mock_get_items_from_patient_id.return_value = []
603-
604-
# Act
605-
result = ieds_db_operations.ieds_check_exist(patient_id)
606-
607-
# Assert
608-
self.assertFalse(result)
609-
610-
# Verify get_items_from_patient_id was called with None ID
611-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)
612-
613-
def test_ieds_check_exist_query_exception(self):
614-
"""Test exception handling during get_items_from_patient_id"""
615-
# Arrange
616-
patient_id = "test-patient-error"
617-
test_exception = Exception("DynamoDB query failed")
618-
self.mock_get_items_from_patient_id.side_effect = test_exception
619-
620-
# Act & Assert
621-
with self.assertRaises(Exception) as context:
622-
ieds_db_operations.ieds_check_exist(patient_id)
623-
624-
self.assertEqual(str(context.exception), "DynamoDB query failed")
625-
626-
# Verify get_items_from_patient_id was attempted
627-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)
628-
629-
def test_ieds_check_exist_multiple_items_found(self):
630-
"""Test when multiple items are found (should still return True)"""
631-
# Arrange
632-
patient_id = "test-patient-multiple"
633-
mock_items = [
634-
{'PK': 'Patient#test-patient-multiple', 'PatientPK': 'Patient#test-patient-multiple'},
635-
{'PK': 'Patient#test-patient-multiple#record1', 'PatientPK': 'Patient#test-patient-multiple'}
636-
]
637-
self.mock_get_items_from_patient_id.return_value = mock_items
638-
639-
# Act
640-
result = ieds_db_operations.ieds_check_exist(patient_id)
641-
642-
# Assert
643-
self.assertTrue(result)
644-
645-
# Verify get_items_from_patient_id was called with limit=1
646-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)
647-
648-
def test_ieds_check_exist_single_item_found(self):
649-
"""Test when exactly one item is found"""
650-
# Arrange
651-
patient_id = "test-patient-single"
652-
mock_items = [{'PK': 'Patient#test-patient-single', 'PatientPK': 'Patient#test-patient-single'}]
653-
self.mock_get_items_from_patient_id.return_value = mock_items
654-
655-
# Act
656-
result = ieds_db_operations.ieds_check_exist(patient_id)
657-
658-
# Assert
659-
self.assertTrue(result)
660-
661-
# Verify get_items_from_patient_id was called
662-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)
663-
664-
def test_ieds_check_exist_limit_parameter(self):
665-
"""Test that the function passes limit=1 to get_items_from_patient_id"""
666-
# Arrange
667-
patient_id = "test-patient-limit"
668-
self.mock_get_items_from_patient_id.return_value = []
669-
670-
# Act
671-
ieds_db_operations.ieds_check_exist(patient_id)
672-
673-
# Assert - Verify the limit parameter is correctly passed
674-
self.mock_get_items_from_patient_id.assert_called_once_with(patient_id, 1)

0 commit comments

Comments
 (0)