From 3d3f46fa93fb634f22b512c2485ccb855ea8e2f8 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 5 Nov 2025 14:25:00 -0700 Subject: [PATCH 1/7] implement de-dupe of license encumbrance privilege update records --- .../cc_common/data_model/data_client.py | 42 ++++ .../tests/function/test_encumbrance_events.py | 238 ++++++++++++++++++ 2 files changed, 280 insertions(+) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index 1156b72ab..6a6315ef7 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -2700,6 +2700,27 @@ def encumber_home_jurisdiction_license_privileges( ) for privilege_data in unencumbered_privileges_associated_with_license: + # Check if an update record already exists for this adverse action + # to avoid creating duplicate update records if the event flow is re-run + existing_updates = provider_user_records.get_update_records_for_privilege( + jurisdiction=privilege_data.jurisdiction, + license_type=privilege_data.licenseType, + filter_condition=lambda update: ( + update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == adverse_action_id + ), + ) + + if existing_updates: + logger.info( + 'Update record already exists for this adverse action. Skipping duplicate creation.', + privilege_jurisdiction=privilege_data.jurisdiction, + privilege_license_type=privilege_data.licenseType, + adverse_action_id=adverse_action_id, + ) + continue + now = config.current_standard_datetime # Create privilege update record @@ -2733,6 +2754,27 @@ def encumber_home_jurisdiction_license_privileges( ) for encumbered_privilege in previously_encumbered_privileges_associated_with_license: + # Check if an update record already exists for this adverse action + # to avoid creating duplicate update records if the event flow is re-run + existing_updates = provider_user_records.get_update_records_for_privilege( + jurisdiction=encumbered_privilege.jurisdiction, + license_type=encumbered_privilege.licenseType, + filter_condition=lambda update: ( + update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == adverse_action_id + ), + ) + + if existing_updates: + logger.info( + 'Update record already exists for this adverse action. Skipping duplicate creation.', + privilege_jurisdiction=encumbered_privilege.jurisdiction, + privilege_license_type=encumbered_privilege.licenseType, + adverse_action_id=adverse_action_id, + ) + continue + now = config.current_standard_datetime # Create privilege update record diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py index e53e96ba6..bd8e13a52 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py @@ -3031,3 +3031,241 @@ def test_license_encumbrance_lifting_notification_listener_skips_notifications_w # Verify NO notifications were sent because license is still encumbered mock_provider_email.assert_not_called() mock_state_email.assert_not_called() + + def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_unencumbered_privileges_on_retry( + self, + ): + """Test that license encumbrance event does not create duplicate update records when re-run for unencumbered privileges.""" + from cc_common.data_model.schema.common import UpdateCategory + from handlers.encumbrance_events import license_encumbrance_listener + + # Set up test data + self.test_data_generator.put_default_provider_record_in_provider_table() + privilege = self.test_data_generator.put_default_privilege_record_in_provider_table( + value_overrides={ + 'licenseJurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'encumberedStatus': 'unencumbered', + 'jurisdiction': 'ne', + } + ) + + # Add adverse action for license + self.test_data_generator.put_default_adverse_action_record_in_provider_table( + value_overrides={'actionAgainst': 'license'} + ) + + message = self._generate_license_encumbrance_message() + event = self._create_sqs_event(message) + + # Execute the handler FIRST TIME + license_encumbrance_listener(event, self.mock_context) + + # Verify privilege update record was created + serialized_privilege = privilege.serialize_to_database_record() + privilege_update_records = self._provider_table.query( + Select='ALL_ATTRIBUTES', + KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) + & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), + ) + + self.assertEqual(1, len(privilege_update_records['Items'])) + first_update_record = privilege_update_records['Items'][0] + self.assertEqual('encumbrance', first_update_record['updateType']) + self.assertEqual({'encumberedStatus': 'licenseEncumbered'}, first_update_record['updatedValues']) + self.assertEqual(DEFAULT_ADVERSE_ACTION_ID, first_update_record['encumbranceDetails']['adverseActionId']) + + # Execute the handler SECOND TIME (simulating re-run of same event) + license_encumbrance_listener(event, self.mock_context) + + # Verify STILL only one update record exists (no duplicate created) + privilege_update_records_after_retry = self._provider_table.query( + Select='ALL_ATTRIBUTES', + KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) + & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), + ) + + self.assertEqual(1, len(privilege_update_records_after_retry['Items'])) + # Verify it's the same record (same sort key) + self.assertEqual(first_update_record['sk'], privilege_update_records_after_retry['Items'][0]['sk']) + + # Verify using the ProviderUserRecords helper to ensure deduplication logic works correctly + provider_records = self.config.data_client.get_provider_user_records( + compact=DEFAULT_COMPACT, + provider_id=DEFAULT_PROVIDER_ID, + ) + matching_updates = provider_records.get_update_records_for_privilege( + jurisdiction='ne', + license_type=DEFAULT_LICENSE_TYPE, + filter_condition=lambda update: ( + update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == DEFAULT_ADVERSE_ACTION_ID + ), + ) + self.assertEqual(1, len(matching_updates)) + + def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_already_encumbered_privileges_on_retry( + self, + ): + """Test that license encumbrance event does not create duplicate update records when re-run for already encumbered privileges.""" + from cc_common.data_model.schema.common import UpdateCategory + from handlers.encumbrance_events import license_encumbrance_listener + + # Set up test data + self.test_data_generator.put_default_provider_record_in_provider_table() + privilege = self.test_data_generator.put_default_privilege_record_in_provider_table( + value_overrides={ + 'licenseJurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'encumberedStatus': 'encumbered', # Already encumbered by a different adverse action + 'jurisdiction': 'ky', + } + ) + + # Add adverse action for license + self.test_data_generator.put_default_adverse_action_record_in_provider_table( + value_overrides={'actionAgainst': 'license'} + ) + + message = self._generate_license_encumbrance_message() + event = self._create_sqs_event(message) + + # Execute the handler FIRST TIME + license_encumbrance_listener(event, self.mock_context) + + # Verify privilege update record was created (even though privilege was already encumbered) + serialized_privilege = privilege.serialize_to_database_record() + privilege_update_records = self._provider_table.query( + Select='ALL_ATTRIBUTES', + KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) + & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), + ) + + self.assertEqual(1, len(privilege_update_records['Items'])) + first_update_record = privilege_update_records['Items'][0] + self.assertEqual('encumbrance', first_update_record['updateType']) + # For already encumbered privileges, updatedValues is empty but we still track the encumbrance event + self.assertEqual({}, first_update_record['updatedValues']) + self.assertEqual(DEFAULT_ADVERSE_ACTION_ID, first_update_record['encumbranceDetails']['adverseActionId']) + + # Execute the handler SECOND TIME (simulating re-run of same event) + license_encumbrance_listener(event, self.mock_context) + + # Verify STILL only one update record exists (no duplicate created) + privilege_update_records_after_retry = self._provider_table.query( + Select='ALL_ATTRIBUTES', + KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) + & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), + ) + + self.assertEqual(1, len(privilege_update_records_after_retry['Items'])) + # Verify it's the same record (same sort key) + self.assertEqual(first_update_record['sk'], privilege_update_records_after_retry['Items'][0]['sk']) + + # Verify using the ProviderUserRecords helper to ensure deduplication logic works correctly + provider_records = self.config.data_client.get_provider_user_records( + compact=DEFAULT_COMPACT, + provider_id=DEFAULT_PROVIDER_ID, + ) + matching_updates = provider_records.get_update_records_for_privilege( + jurisdiction='ky', + license_type=DEFAULT_LICENSE_TYPE, + filter_condition=lambda update: ( + update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == DEFAULT_ADVERSE_ACTION_ID + ), + ) + self.assertEqual(1, len(matching_updates)) + + def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_records_on_retry(self): + """Test that license encumbrance lifting event does not create duplicate update records when re-run. + + This test confirms that the early return logic when no LICENSE_ENCUMBERED privileges are found + prevents duplicate update record creation on retry. + """ + from cc_common.data_model.schema.common import PrivilegeEncumberedStatusEnum, UpdateCategory + from handlers.encumbrance_events import license_encumbrance_lifted_listener + + # Set up test data + self.test_data_generator.put_default_provider_record_in_provider_table() + + # Set up license record that is unencumbered (all adverse actions lifted) + self.test_data_generator.put_default_license_record_in_provider_table( + value_overrides={ + 'jurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'licenseType': DEFAULT_LICENSE_TYPE, + 'encumberedStatus': 'unencumbered', + } + ) + + privilege = self.test_data_generator.put_default_privilege_record_in_provider_table( + value_overrides={ + 'licenseJurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'encumberedStatus': 'licenseEncumbered', # Will be lifted + } + ) + + # Add adverse action with effectiveLiftDate set + self.test_data_generator.put_default_adverse_action_record_in_provider_table( + value_overrides={ + 'actionAgainst': 'license', + 'effectiveLiftDate': date.fromisoformat(DEFAULT_EFFECTIVE_DATE), + 'jurisdiction': DEFAULT_LICENSE_JURISDICTION, + 'licenseTypeAbbreviation': DEFAULT_LICENSE_TYPE_ABBREVIATION, + 'licenseType': DEFAULT_LICENSE_TYPE, + } + ) + + message = self._generate_license_encumbrance_lifting_message() + event = self._create_sqs_event(message) + + # Execute the handler FIRST TIME + license_encumbrance_lifted_listener(event, self.mock_context) + + # Verify privilege update record was created + serialized_privilege = privilege.serialize_to_database_record() + privilege_update_records = self._provider_table.query( + Select='ALL_ATTRIBUTES', + KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) + & Key('sk').begins_with(f'{privilege.compact}#PROVIDER#privilege/{privilege.jurisdiction}/slp#UPDATE'), + ) + + self.assertEqual(1, len(privilege_update_records['Items'])) + first_update_record = privilege_update_records['Items'][0] + self.assertEqual('lifting_encumbrance', first_update_record['updateType']) + self.assertEqual({'encumberedStatus': 'unencumbered'}, first_update_record['updatedValues']) + + # Verify privilege is now unencumbered + provider_records = self.config.data_client.get_provider_user_records( + compact=DEFAULT_COMPACT, + provider_id=DEFAULT_PROVIDER_ID, + ) + privileges = provider_records.get_privilege_records() + self.assertEqual(1, len(privileges)) + self.assertEqual(PrivilegeEncumberedStatusEnum.UNENCUMBERED, privileges[0].encumberedStatus) + + # Execute the handler SECOND TIME (simulating re-run of same event) + license_encumbrance_lifted_listener(event, self.mock_context) + + # Verify STILL only one update record exists (no duplicate created) + # The function should return early because no LICENSE_ENCUMBERED privileges remain + privilege_update_records_after_retry = self._provider_table.query( + Select='ALL_ATTRIBUTES', + KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) + & Key('sk').begins_with(f'{privilege.compact}#PROVIDER#privilege/{privilege.jurisdiction}/slp#UPDATE'), + ) + + self.assertEqual(1, len(privilege_update_records_after_retry['Items'])) + # Verify it's the same record (same sort key) + self.assertEqual(first_update_record['sk'], privilege_update_records_after_retry['Items'][0]['sk']) + + # Verify using the ProviderUserRecords helper + matching_updates = provider_records.get_update_records_for_privilege( + jurisdiction=privileges[0].jurisdiction, + license_type=DEFAULT_LICENSE_TYPE, + filter_condition=lambda update: update.updateType == UpdateCategory.LIFTING_ENCUMBRANCE, + ) + self.assertEqual(1, len(matching_updates)) From 4a0adca61948c8a20b5db15ceface14b27bf5a5d Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 5 Nov 2025 14:29:06 -0700 Subject: [PATCH 2/7] lint fixes --- .../tests/function/test_encumbrance_events.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py index bd8e13a52..40b7a4d4d 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py @@ -3035,7 +3035,8 @@ def test_license_encumbrance_lifting_notification_listener_skips_notifications_w def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_unencumbered_privileges_on_retry( self, ): - """Test that license encumbrance event does not create duplicate update records when re-run for unencumbered privileges.""" + """Test that license encumbrance event does not create duplicate update records + when re-run for unencumbered privileges.""" from cc_common.data_model.schema.common import UpdateCategory from handlers.encumbrance_events import license_encumbrance_listener @@ -3105,10 +3106,11 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f ) self.assertEqual(1, len(matching_updates)) - def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_already_encumbered_privileges_on_retry( + def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_already_encumbered_privileges( self, ): - """Test that license encumbrance event does not create duplicate update records when re-run for already encumbered privileges.""" + """Test that license encumbrance event does not create duplicate update records when + re-run for already encumbered privileges.""" from cc_common.data_model.schema.common import UpdateCategory from handlers.encumbrance_events import license_encumbrance_listener @@ -3181,7 +3183,7 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_records_on_retry(self): """Test that license encumbrance lifting event does not create duplicate update records when re-run. - + This test confirms that the early return logic when no LICENSE_ENCUMBERED privileges are found prevents duplicate update record creation on retry. """ From 4dd29b5272bc7e1e2a5f23b284177df22b43582a Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 5 Nov 2025 15:03:45 -0700 Subject: [PATCH 3/7] fix tests --- .../common/cc_common/data_model/data_client.py | 4 ++-- .../tests/function/test_encumbrance_events.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index 6a6315ef7..a10424cf5 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -2708,7 +2708,7 @@ def encumber_home_jurisdiction_license_privileges( filter_condition=lambda update: ( update.updateType == UpdateCategory.ENCUMBRANCE and update.encumbranceDetails is not None - and update.encumbranceDetails.get('adverseActionId') == adverse_action_id + and str(update.encumbranceDetails.get('adverseActionId')) == adverse_action_id ), ) @@ -2762,7 +2762,7 @@ def encumber_home_jurisdiction_license_privileges( filter_condition=lambda update: ( update.updateType == UpdateCategory.ENCUMBRANCE and update.encumbranceDetails is not None - and update.encumbranceDetails.get('adverseActionId') == adverse_action_id + and str(update.encumbranceDetails.get('adverseActionId')) == adverse_action_id ), ) diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py index 40b7a4d4d..5c2fbd9ea 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py @@ -3032,8 +3032,10 @@ def test_license_encumbrance_lifting_notification_listener_skips_notifications_w mock_provider_email.assert_not_called() mock_state_email.assert_not_called() + @patch('cc_common.event_bus_client.EventBusClient._publish_event') def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_unencumbered_privileges_on_retry( self, + mock_publish_event, ): """Test that license encumbrance event does not create duplicate update records when re-run for unencumbered privileges.""" @@ -3101,13 +3103,15 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f filter_condition=lambda update: ( update.updateType == UpdateCategory.ENCUMBRANCE and update.encumbranceDetails is not None - and update.encumbranceDetails.get('adverseActionId') == DEFAULT_ADVERSE_ACTION_ID + and str(update.encumbranceDetails.get('adverseActionId')) == DEFAULT_ADVERSE_ACTION_ID ), ) self.assertEqual(1, len(matching_updates)) + @patch('cc_common.event_bus_client.EventBusClient._publish_event') def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_already_encumbered_privileges( self, + mock_publish_event, ): """Test that license encumbrance event does not create duplicate update records when re-run for already encumbered privileges.""" @@ -3176,14 +3180,17 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f filter_condition=lambda update: ( update.updateType == UpdateCategory.ENCUMBRANCE and update.encumbranceDetails is not None - and update.encumbranceDetails.get('adverseActionId') == DEFAULT_ADVERSE_ACTION_ID + and str(update.encumbranceDetails.get('adverseActionId')) == DEFAULT_ADVERSE_ACTION_ID ), ) self.assertEqual(1, len(matching_updates)) - def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_records_on_retry(self): + @patch('cc_common.event_bus_client.EventBusClient._publish_event') + def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_records_on_retry( + self, mock_publish_event + ): """Test that license encumbrance lifting event does not create duplicate update records when re-run. - + This test confirms that the early return logic when no LICENSE_ENCUMBERED privileges are found prevents duplicate update record creation on retry. """ From 7d4c21771b050b702a8bd4bb61def4a7ff656d50 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 5 Nov 2025 15:28:30 -0700 Subject: [PATCH 4/7] lint fixes --- .../data-events/tests/function/test_encumbrance_events.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py index 5c2fbd9ea..920ed3669 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py @@ -3035,7 +3035,7 @@ def test_license_encumbrance_lifting_notification_listener_skips_notifications_w @patch('cc_common.event_bus_client.EventBusClient._publish_event') def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_unencumbered_privileges_on_retry( self, - mock_publish_event, + _mock_publish_event, ): """Test that license encumbrance event does not create duplicate update records when re-run for unencumbered privileges.""" @@ -3111,7 +3111,7 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f @patch('cc_common.event_bus_client.EventBusClient._publish_event') def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_already_encumbered_privileges( self, - mock_publish_event, + _mock_publish_event, ): """Test that license encumbrance event does not create duplicate update records when re-run for already encumbered privileges.""" @@ -3187,10 +3187,10 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f @patch('cc_common.event_bus_client.EventBusClient._publish_event') def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_records_on_retry( - self, mock_publish_event + self, _mock_publish_event ): """Test that license encumbrance lifting event does not create duplicate update records when re-run. - + This test confirms that the early return logic when no LICENSE_ENCUMBERED privileges are found prevents duplicate update record creation on retry. """ From 47e60ea3967b84155978597d271529cda96ff0c2 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 19 Nov 2025 17:26:15 -0700 Subject: [PATCH 5/7] PR fixes --- .../cc_common/data_model/data_client.py | 10 +- .../data_model/provider_record_util.py | 5 +- .../tests/function/test_encumbrance_events.py | 178 ++++++++---------- 3 files changed, 87 insertions(+), 106 deletions(-) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py index a10424cf5..3f7e30695 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py @@ -2,7 +2,7 @@ from datetime import date, datetime from datetime import time as dtime from urllib.parse import quote -from uuid import uuid4 +from uuid import UUID, uuid4 from aws_lambda_powertools.metrics import MetricUnit from boto3.dynamodb.conditions import Attr, Key @@ -2613,7 +2613,7 @@ def encumber_home_jurisdiction_license_privileges( compact: str, provider_id: str, jurisdiction: str, - adverse_action_id: str, + adverse_action_id: UUID, license_type_abbreviation: str, effective_date: date, ) -> list[PrivilegeData]: @@ -2626,7 +2626,7 @@ def encumber_home_jurisdiction_license_privileges( :param str compact: The compact name. :param str provider_id: The provider ID. :param str jurisdiction: The jurisdiction of the license. - :param adverse_action_id: The ID of the adverse action. + :param UUID adverse_action_id: The ID of the adverse action. :param str license_type_abbreviation: The license type abbreviation. :param date effective_date: effective date of the encumbrance on the license and therefore privilege. :return: List of privileges that were encumbered @@ -2708,7 +2708,7 @@ def encumber_home_jurisdiction_license_privileges( filter_condition=lambda update: ( update.updateType == UpdateCategory.ENCUMBRANCE and update.encumbranceDetails is not None - and str(update.encumbranceDetails.get('adverseActionId')) == adverse_action_id + and update.encumbranceDetails.get('adverseActionId') == adverse_action_id ), ) @@ -2762,7 +2762,7 @@ def encumber_home_jurisdiction_license_privileges( filter_condition=lambda update: ( update.updateType == UpdateCategory.ENCUMBRANCE and update.encumbranceDetails is not None - and str(update.encumbranceDetails.get('adverseActionId')) == adverse_action_id + and update.encumbranceDetails.get('adverseActionId') == adverse_action_id ), ) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py index 5ce44325a..1ac0e9be1 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -6,6 +6,7 @@ timedelta, ) from enum import StrEnum +from uuid import UUID from cc_common.config import config, logger from cc_common.data_model.schema.adverse_action import AdverseActionData @@ -522,11 +523,11 @@ def get_adverse_action_records_for_license( and (filter_condition is None or filter_condition(record)) ] - def get_adverse_action_by_id(self, adverse_action_id: str) -> AdverseActionData | None: + def get_adverse_action_by_id(self, adverse_action_id: UUID) -> AdverseActionData | None: """ Get an adverse action record by its ID. - :param str adverse_action_id: The ID of the adverse action to find + :param UUID adverse_action_id: The ID of the adverse action to find :return: The found adverse action record if found, else None """ return next( diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py index 920ed3669..3b316c4c6 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py @@ -3064,49 +3064,42 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f # Execute the handler FIRST TIME license_encumbrance_listener(event, self.mock_context) - # Verify privilege update record was created - serialized_privilege = privilege.serialize_to_database_record() - privilege_update_records = self._provider_table.query( - Select='ALL_ATTRIBUTES', - KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) - & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), - ) + # Verify privilege update record was created using the test helper + update_records = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) + matching_updates = [ + update + for update in update_records + if update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == UUID(DEFAULT_ADVERSE_ACTION_ID) + ] - self.assertEqual(1, len(privilege_update_records['Items'])) - first_update_record = privilege_update_records['Items'][0] - self.assertEqual('encumbrance', first_update_record['updateType']) - self.assertEqual({'encumberedStatus': 'licenseEncumbered'}, first_update_record['updatedValues']) - self.assertEqual(DEFAULT_ADVERSE_ACTION_ID, first_update_record['encumbranceDetails']['adverseActionId']) + self.assertEqual(1, len(matching_updates)) + first_update_record = matching_updates[0] + self.assertEqual(UpdateCategory.ENCUMBRANCE, first_update_record.updateType) + self.assertEqual({'encumberedStatus': 'licenseEncumbered'}, first_update_record.updatedValues) + self.assertEqual(UUID(DEFAULT_ADVERSE_ACTION_ID), first_update_record.encumbranceDetails.get('adverseActionId')) # Execute the handler SECOND TIME (simulating re-run of same event) license_encumbrance_listener(event, self.mock_context) # Verify STILL only one update record exists (no duplicate created) - privilege_update_records_after_retry = self._provider_table.query( - Select='ALL_ATTRIBUTES', - KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) - & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), - ) - - self.assertEqual(1, len(privilege_update_records_after_retry['Items'])) - # Verify it's the same record (same sort key) - self.assertEqual(first_update_record['sk'], privilege_update_records_after_retry['Items'][0]['sk']) + update_records_after_retry = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) + matching_updates_after_retry = [ + update + for update in update_records_after_retry + if update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == UUID(DEFAULT_ADVERSE_ACTION_ID) + ] - # Verify using the ProviderUserRecords helper to ensure deduplication logic works correctly - provider_records = self.config.data_client.get_provider_user_records( - compact=DEFAULT_COMPACT, - provider_id=DEFAULT_PROVIDER_ID, - ) - matching_updates = provider_records.get_update_records_for_privilege( - jurisdiction='ne', - license_type=DEFAULT_LICENSE_TYPE, - filter_condition=lambda update: ( - update.updateType == UpdateCategory.ENCUMBRANCE - and update.encumbranceDetails is not None - and str(update.encumbranceDetails.get('adverseActionId')) == DEFAULT_ADVERSE_ACTION_ID - ), - ) - self.assertEqual(1, len(matching_updates)) + self.assertEqual(1, len(matching_updates_after_retry)) + # Verify it's the same record (same createDate as a proxy for same record) + self.assertEqual(first_update_record.createDate, matching_updates_after_retry[0].createDate) @patch('cc_common.event_bus_client.EventBusClient._publish_event') def test_license_encumbrance_listener_does_not_create_duplicate_update_records_for_already_encumbered_privileges( @@ -3141,49 +3134,43 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f license_encumbrance_listener(event, self.mock_context) # Verify privilege update record was created (even though privilege was already encumbered) - serialized_privilege = privilege.serialize_to_database_record() - privilege_update_records = self._provider_table.query( - Select='ALL_ATTRIBUTES', - KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) - & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), - ) + # using the test helper + update_records = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) + matching_updates = [ + update + for update in update_records + if update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == UUID(DEFAULT_ADVERSE_ACTION_ID) + ] - self.assertEqual(1, len(privilege_update_records['Items'])) - first_update_record = privilege_update_records['Items'][0] - self.assertEqual('encumbrance', first_update_record['updateType']) + self.assertEqual(1, len(matching_updates)) + first_update_record = matching_updates[0] + self.assertEqual(UpdateCategory.ENCUMBRANCE, first_update_record.updateType) # For already encumbered privileges, updatedValues is empty but we still track the encumbrance event - self.assertEqual({}, first_update_record['updatedValues']) - self.assertEqual(DEFAULT_ADVERSE_ACTION_ID, first_update_record['encumbranceDetails']['adverseActionId']) + self.assertEqual({}, first_update_record.updatedValues) + self.assertEqual(UUID(DEFAULT_ADVERSE_ACTION_ID), first_update_record.encumbranceDetails.get('adverseActionId')) # Execute the handler SECOND TIME (simulating re-run of same event) license_encumbrance_listener(event, self.mock_context) # Verify STILL only one update record exists (no duplicate created) - privilege_update_records_after_retry = self._provider_table.query( - Select='ALL_ATTRIBUTES', - KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) - & Key('sk').begins_with(f'{serialized_privilege["sk"]}UPDATE'), - ) + update_records_after_retry = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) + matching_updates_after_retry = [ + update + for update in update_records_after_retry + if update.updateType == UpdateCategory.ENCUMBRANCE + and update.encumbranceDetails is not None + and update.encumbranceDetails.get('adverseActionId') == UUID(DEFAULT_ADVERSE_ACTION_ID) + ] - self.assertEqual(1, len(privilege_update_records_after_retry['Items'])) - # Verify it's the same record (same sort key) - self.assertEqual(first_update_record['sk'], privilege_update_records_after_retry['Items'][0]['sk']) - - # Verify using the ProviderUserRecords helper to ensure deduplication logic works correctly - provider_records = self.config.data_client.get_provider_user_records( - compact=DEFAULT_COMPACT, - provider_id=DEFAULT_PROVIDER_ID, - ) - matching_updates = provider_records.get_update_records_for_privilege( - jurisdiction='ky', - license_type=DEFAULT_LICENSE_TYPE, - filter_condition=lambda update: ( - update.updateType == UpdateCategory.ENCUMBRANCE - and update.encumbranceDetails is not None - and str(update.encumbranceDetails.get('adverseActionId')) == DEFAULT_ADVERSE_ACTION_ID - ), - ) - self.assertEqual(1, len(matching_updates)) + self.assertEqual(1, len(matching_updates_after_retry)) + # Verify it's the same record (same createDate as a proxy for same record) + self.assertEqual(first_update_record.createDate, matching_updates_after_retry[0].createDate) @patch('cc_common.event_bus_client.EventBusClient._publish_event') def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_records_on_retry( @@ -3234,19 +3221,6 @@ def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_re # Execute the handler FIRST TIME license_encumbrance_lifted_listener(event, self.mock_context) - # Verify privilege update record was created - serialized_privilege = privilege.serialize_to_database_record() - privilege_update_records = self._provider_table.query( - Select='ALL_ATTRIBUTES', - KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) - & Key('sk').begins_with(f'{privilege.compact}#PROVIDER#privilege/{privilege.jurisdiction}/slp#UPDATE'), - ) - - self.assertEqual(1, len(privilege_update_records['Items'])) - first_update_record = privilege_update_records['Items'][0] - self.assertEqual('lifting_encumbrance', first_update_record['updateType']) - self.assertEqual({'encumberedStatus': 'unencumbered'}, first_update_record['updatedValues']) - # Verify privilege is now unencumbered provider_records = self.config.data_client.get_provider_user_records( compact=DEFAULT_COMPACT, @@ -3256,25 +3230,31 @@ def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_re self.assertEqual(1, len(privileges)) self.assertEqual(PrivilegeEncumberedStatusEnum.UNENCUMBERED, privileges[0].encumberedStatus) + # Verify privilege update record was created using the test helper + update_records = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) + matching_updates = [ + update for update in update_records if update.updateType == UpdateCategory.LIFTING_ENCUMBRANCE + ] + + self.assertEqual(1, len(matching_updates)) + first_update_record = matching_updates[0] + self.assertEqual(UpdateCategory.LIFTING_ENCUMBRANCE, first_update_record.updateType) + self.assertEqual({'encumberedStatus': 'unencumbered'}, first_update_record.updatedValues) + # Execute the handler SECOND TIME (simulating re-run of same event) license_encumbrance_lifted_listener(event, self.mock_context) # Verify STILL only one update record exists (no duplicate created) # The function should return early because no LICENSE_ENCUMBERED privileges remain - privilege_update_records_after_retry = self._provider_table.query( - Select='ALL_ATTRIBUTES', - KeyConditionExpression=Key('pk').eq(serialized_privilege['pk']) - & Key('sk').begins_with(f'{privilege.compact}#PROVIDER#privilege/{privilege.jurisdiction}/slp#UPDATE'), + update_records_after_retry = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege ) + matching_updates_after_retry = [ + update for update in update_records_after_retry if update.updateType == UpdateCategory.LIFTING_ENCUMBRANCE + ] - self.assertEqual(1, len(privilege_update_records_after_retry['Items'])) - # Verify it's the same record (same sort key) - self.assertEqual(first_update_record['sk'], privilege_update_records_after_retry['Items'][0]['sk']) - - # Verify using the ProviderUserRecords helper - matching_updates = provider_records.get_update_records_for_privilege( - jurisdiction=privileges[0].jurisdiction, - license_type=DEFAULT_LICENSE_TYPE, - filter_condition=lambda update: update.updateType == UpdateCategory.LIFTING_ENCUMBRANCE, - ) - self.assertEqual(1, len(matching_updates)) + self.assertEqual(1, len(matching_updates_after_retry)) + # Verify it's the same record (same createDate as a proxy for same record) + self.assertEqual(first_update_record.createDate, matching_updates_after_retry[0].createDate) From 2f77f632c181fd181162b189a02529874b1d90ea Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Thu, 20 Nov 2025 13:34:46 -0700 Subject: [PATCH 6/7] lint fixes --- .../tests/function/test_encumbrance_events.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py index 7a84b1c01..820f18646 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py @@ -3087,8 +3087,10 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f license_encumbrance_listener(event, self.mock_context) # Verify STILL only one update record exists (no duplicate created) - update_records_after_retry = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( - privilege + update_records_after_retry = ( + self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) ) matching_updates_after_retry = [ update @@ -3158,8 +3160,10 @@ def test_license_encumbrance_listener_does_not_create_duplicate_update_records_f license_encumbrance_listener(event, self.mock_context) # Verify STILL only one update record exists (no duplicate created) - update_records_after_retry = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( - privilege + update_records_after_retry = ( + self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) ) matching_updates_after_retry = [ update @@ -3249,8 +3253,10 @@ def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_re # Verify STILL only one update record exists (no duplicate created) # The function should return early because no LICENSE_ENCUMBERED privileges remain - update_records_after_retry = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( - privilege + update_records_after_retry = ( + self.test_data_generator.query_privilege_update_records_for_given_record_from_database( + privilege + ) ) matching_updates_after_retry = [ update for update in update_records_after_retry if update.updateType == UpdateCategory.LIFTING_ENCUMBRANCE From 1fa7214e83a225547e2b51be4d44e72d295114d1 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Tue, 25 Nov 2025 13:20:27 -0700 Subject: [PATCH 7/7] clarify comment --- .../data-events/tests/function/test_encumbrance_events.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py index 820f18646..95b0d3cb8 100644 --- a/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py +++ b/backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py @@ -3252,7 +3252,8 @@ def test_license_encumbrance_lifted_listener_does_not_create_duplicate_update_re license_encumbrance_lifted_listener(event, self.mock_context) # Verify STILL only one update record exists (no duplicate created) - # The function should return early because no LICENSE_ENCUMBERED privileges remain + # license_encumbrance_lifted_listener will skip creating privilege updates because it only + # does so on LICENSE_ENCUMBERED privileges and none of those would remain update_records_after_retry = ( self.test_data_generator.query_privilege_update_records_for_given_record_from_database( privilege