From 0fb09e8b657bbe1af2d1982bacf5ba235b43955b Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Fri, 3 Oct 2025 15:07:15 -0600 Subject: [PATCH 01/19] api first pass --- .../compact_configuration_client.py | 28 ++++++++++++++ .../handlers/compact_configuration.py | 37 ++++++++++++++++++ .../stacks/api_stack/v1_api/api.py | 3 ++ .../v1_api/compact_configuration_api.py | 38 +++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/backend/compact-connect/lambdas/python/common/cc_common/data_model/compact_configuration_client.py b/backend/compact-connect/lambdas/python/common/cc_common/data_model/compact_configuration_client.py index aab3c3558..a31b15097 100644 --- a/backend/compact-connect/lambdas/python/common/cc_common/data_model/compact_configuration_client.py +++ b/backend/compact-connect/lambdas/python/common/cc_common/data_model/compact_configuration_client.py @@ -450,3 +450,31 @@ def update_compact_configured_states(self, compact: str, configured_states: list ':dou': self.config.current_standard_datetime.isoformat(), }, ) + + def get_live_compact_jurisdictions(self, compact: str) -> list[str]: + """ + Get all live (isLive: true) jurisdiction postal abbreviations for a specific compact. + + :param compact: The compact abbreviation + :return: List of jurisdiction postal abbreviations that are live in the compact + """ + logger.info('Getting live jurisdictions for compact', compact=compact) + + try: + compact_config = self.get_compact_configuration(compact) + except CCNotFoundException: + logger.info('Compact configuration not found, returning empty list', compact=compact) + return [] + + # Filter configuredStates for those with isLive: true and extract postal abbreviations + live_jurisdictions = [ + state['postalAbbreviation'] for state in compact_config.configuredStates if state.get('isLive', False) + ] + + logger.info( + 'Retrieved live jurisdictions for compact', + compact=compact, + live_jurisdictions_count=len(live_jurisdictions), + ) + + return live_jurisdictions diff --git a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py index 61bda0dc5..454df13c1 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py @@ -28,6 +28,8 @@ def compact_configuration_api_handler(event: dict, context: LambdaContext): # n return _get_staff_users_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/public/compacts/{compact}/jurisdictions': return _get_public_compact_jurisdictions(event, context) + if event['httpMethod'] == 'GET' and event['resource'] == '/v1/compacts/live': + return _get_live_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/compacts/{compact}': return _get_staff_users_compact_configuration(event, context) if event['httpMethod'] == 'PUT' and event['resource'] == '/v1/compacts/{compact}': @@ -118,6 +120,41 @@ def _get_public_compact_jurisdictions(event: dict, context: LambdaContext): # n return CompactJurisdictionsPublicResponseSchema().load(compact_jurisdictions, many=True) +def _get_live_compact_jurisdictions(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument + """ + Endpoint to get all live jurisdictions, optionally filtered by compact. + + :param event: API Gateway event with optional query parameter 'compact' + :param context: Lambda context + :return: Dictionary with compact abbreviations as keys and lists of live jurisdiction abbreviations as values + """ + query_params = event.get('queryStringParameters') or {} + compact_filter = query_params.get('compact') + + # Determine which compacts to query + compacts_to_query = [] + if compact_filter: + # Validate the compact, if invalid treat as if no filter was provided + if compact_filter.lower() in config.compacts: + compacts_to_query = [compact_filter.lower()] + logger.info('Getting live jurisdictions for specific compact', compact=compact_filter) + else: + logger.info('Invalid compact provided, returning data for all compacts', compact=compact_filter) + compacts_to_query = config.compacts + else: + logger.info('Getting live jurisdictions for all compacts') + compacts_to_query = config.compacts + + # Build result dictionary + result = {} + for compact in compacts_to_query: + live_jurisdictions = config.compact_configuration_client.get_live_compact_jurisdictions(compact=compact) + result[compact] = live_jurisdictions + + logger.info('Returning live jurisdictions', compacts_count=len(result)) + return result + + @authorize_compact_level_only_action(action=CCPermissionsAction.ADMIN) def _get_staff_users_compact_configuration(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument """ diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 0d63fdfb1..3aa27a653 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -160,6 +160,8 @@ def __init__( # /v1/compacts self.compacts_resource = self.resource.add_resource('compacts') + # /v1/compacts/live + self.live_compacts_resource = self.compacts_resource.add_resource('live') # /v1/compacts/{compact} self.compact_resource = self.compacts_resource.add_resource('{compact}') @@ -205,6 +207,7 @@ def __init__( self.compact_configuration_api = CompactConfigurationApi( api=self.api, compact_resource=self.compact_resource, + live_compacts_resource=self.live_compacts_resource, jurisdictions_resource=self.jurisdictions_resource, public_jurisdictions_resource=self.public_compacts_compact_jurisdictions_resource, jurisdiction_resource=self.jurisdiction_resource, diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index 7fc99a4a6..e1cd4f7bb 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -25,6 +25,7 @@ def __init__( *, api: CCApi, compact_resource: Resource, + live_compacts_resource: Resource, jurisdictions_resource: Resource, public_jurisdictions_resource: Resource, jurisdiction_resource: Resource, @@ -38,6 +39,8 @@ def __init__( self.api = api # /v1/compacts/{compact} self.staff_users_compact_resource = compact_resource + # /v1/compacts/live + self.live_compacts_resource = live_compacts_resource # /v1/compacts/{compact}/jurisdictions self.staff_users_jurisdictions_resource = jurisdictions_resource # /v1/compacts/{compact}/jurisdictions/{jurisdiction} @@ -88,6 +91,10 @@ def __init__( compact_configuration_api_handler=self.compact_configuration_api_function, ) + self._add_get_live_compact_jurisdictions_endpoint( + compact_configuration_api_handler=self.compact_configuration_api_function, + ) + self._add_staff_users_get_compact_configuration_endpoint( compact_configuration_api_handler=self.compact_configuration_api_function, general_read_method_options=general_read_method_options, @@ -154,6 +161,37 @@ def _add_public_get_compact_jurisdictions_endpoint(self, compact_configuration_a ], ) + def _add_get_live_compact_jurisdictions_endpoint(self, compact_configuration_api_handler: PythonFunction): + """Add GET endpoint for /v1/compacts/live""" + get_live_compact_jurisdictions_method = self.live_compacts_resource.add_method( + 'GET', + LambdaIntegration(compact_configuration_api_handler), + method_responses=[ + MethodResponse( + status_code='200', + ), + ], + request_parameters={ + 'method.request.querystring.compact': False, + }, + ) + + # Add suppressions for the public GET endpoint + NagSuppressions.add_resource_suppressions( + get_live_compact_jurisdictions_method, + suppressions=[ + { + 'id': 'AwsSolutions-APIG4', + 'reason': 'This is a public endpoint that intentionally does not require authorization', + }, + { + 'id': 'AwsSolutions-COG4', + 'reason': 'This is a public endpoint that intentionally ' + 'does not use a Cognito user pool authorizer', + }, + ], + ) + def _add_staff_users_get_compact_configuration_endpoint( self, compact_configuration_api_handler: PythonFunction, general_read_method_options: MethodOptions ): From 28cbce35c89fb17cd66becbeada119e9afbe4999 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Fri, 3 Oct 2025 15:54:18 -0600 Subject: [PATCH 02/19] add documentation --- .../api-specification/latest-oas30.json | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json index 959637328..cb4534517 100644 --- a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json +++ b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json @@ -10,6 +10,45 @@ } ], "paths": { + "/v1/compacts/live": { + "get": { + "summary": "Get live compact jurisdictions", + "description": "Returns all jurisdictions that are live (enabled for operations) across all compacts or for a specific compact if the optional compact query parameter is provided.", + "parameters": [ + { + "name": "compact", + "in": "query", + "required": false, + "description": "Optional compact abbreviation to filter results. If not provided or invalid, returns data for all compacts.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "200 response - Returns a dictionary with compact abbreviations as keys and arrays of live jurisdiction postal abbreviations as values", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": { + "aslp": ["co", "ne", "wy"], + "octp": ["ak", "ky"] + } + } + } + } + } + } + } + }, "/v1/compacts/{compact}": { "get": { "parameters": [ From b9d3490c3a2933c3cbce6aa24458a741237fe4c5 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Mon, 6 Oct 2025 07:56:44 -0600 Subject: [PATCH 03/19] tests --- .../handlers/compact_configuration.py | 4 +- .../function/test_compact_configuration.py | 138 ++++++++++++++++++ .../test_compact_configuration_api.py | 42 ++++++ 3 files changed, 182 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py index 454df13c1..e81245bd1 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py @@ -29,7 +29,7 @@ def compact_configuration_api_handler(event: dict, context: LambdaContext): # n if event['httpMethod'] == 'GET' and event['resource'] == '/v1/public/compacts/{compact}/jurisdictions': return _get_public_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/compacts/live': - return _get_live_compact_jurisdictions(event, context) + return _get_live_public_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/compacts/{compact}': return _get_staff_users_compact_configuration(event, context) if event['httpMethod'] == 'PUT' and event['resource'] == '/v1/compacts/{compact}': @@ -120,7 +120,7 @@ def _get_public_compact_jurisdictions(event: dict, context: LambdaContext): # n return CompactJurisdictionsPublicResponseSchema().load(compact_jurisdictions, many=True) -def _get_live_compact_jurisdictions(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument +def _get_live_public_compact_jurisdictions(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument """ Endpoint to get all live jurisdictions, optionally filtered by compact. diff --git a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py index a48c0f3af..2d7f9bcb9 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py @@ -12,6 +12,7 @@ STAFF_USERS_COMPACT_JURISDICTION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/jurisdictions' PUBLIC_COMPACT_JURISDICTION_ENDPOINT_RESOURCE = '/v1/public/compacts/{compact}/jurisdictions' +LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE = '/v1/compacts/live' COMPACT_CONFIGURATION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}' JURISDICTION_CONFIGURATION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/jurisdictions/{jurisdiction}' @@ -191,6 +192,143 @@ def test_get_compact_jurisdictions_returns_list_of_configured_jurisdictions(self sorted_response, ) + def test_get_public_live_compact_jurisdictions_returns_list_of_all_live_jurisdictions(self): + """Test getting list of live jurisdictions across all compacts when no query param provided""" + from handlers.compact_configuration import compact_configuration_api_handler + + # Create compact configurations with some jurisdictions marked as live + # ASLP compact with some live jurisdictions + self.test_data_generator.put_default_compact_configuration_in_configuration_table( + value_overrides={ + 'compactAbbr': 'aslp', + 'configuredStates': [ + {'postalAbbreviation': 'ky', 'isLive': True}, + {'postalAbbreviation': 'oh', 'isLive': True}, + {'postalAbbreviation': 'ne', 'isLive': False}, + ], + }, + ) + + # OCTP compact with different live jurisdictions + self.test_data_generator.put_default_compact_configuration_in_configuration_table( + value_overrides={ + 'compactAbbr': 'octp', + 'configuredStates': [ + {'postalAbbreviation': 'ne', 'isLive': True}, + {'postalAbbreviation': 'oh', 'isLive': False}, + ] + }, + ) + + # COUN compact with no live jurisdictions + self.test_data_generator.put_default_compact_configuration_in_configuration_table( + value_overrides={ + 'compactAbbr': 'coun', + 'configuredStates': [ + {'postalAbbreviation': 'ky', 'isLive': False}, + ], + }, + ) + + # Create event without query params + event = generate_test_event('GET', LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE) + event['queryStringParameters'] = None + + response = compact_configuration_api_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + response_body = json.loads(response['body']) + + # Should return all compacts with their live jurisdictions + self.assertIn('aslp', response_body) + self.assertIn('octp', response_body) + self.assertIn('coun', response_body) + + # Verify the live jurisdictions for each compact + self.assertCountEqual(['oh', 'ky'], response_body['aslp']) + self.assertCountEqual(['ne'], response_body['octp']) + self.assertCountEqual([], response_body['coun']) + + def test_get_public_live_compact_jurisdictions_returns_list_of_live_jurisdictions_in_compact(self): + """Test getting list of live jurisdictions for compact designated through query param""" + from handlers.compact_configuration import compact_configuration_api_handler + + # Create compact configurations + self.test_data_generator.put_default_compact_configuration_in_configuration_table( + value_overrides={ + 'compactAbbr': 'aslp', + 'configuredStates': [ + {'postalAbbreviation': 'ky', 'isLive': True}, + {'postalAbbreviation': 'oh', 'isLive': True}, + {'postalAbbreviation': 'ne', 'isLive': False}, + ], + }, + ) + + self.test_data_generator.put_default_compact_configuration_in_configuration_table( + value_overrides={ + 'compactAbbr': 'octp', + 'configuredStates': [ + {'postalAbbreviation': 'ne', 'isLive': True}, + ], + }, + ) + + # Create event with compact query param + event = generate_test_event('GET', LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE) + event['queryStringParameters'] = {'compact': 'aslp'} + + response = compact_configuration_api_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + response_body = json.loads(response['body']) + + # Should only return the specified compact + self.assertIn('aslp', response_body) + self.assertNotIn('octp', response_body) + self.assertNotIn('coun', response_body) + + # Verify the live jurisdictions + self.assertCountEqual(['ky', 'oh'], response_body['aslp']) + + def test_get_public_live_compact_jurisdictions_returns_list_of_all_live_jurisdictions_if_bad_compact_param(self): + """Test getting list of live jurisdictions across all compacts when invalid query param provided""" + from handlers.compact_configuration import compact_configuration_api_handler + + # Create compact configurations + self.test_data_generator.put_default_compact_configuration_in_configuration_table( + value_overrides={ + 'compactAbbr': 'aslp', + 'configuredStates': [ + {'postalAbbreviation': 'ky', 'isLive': True}, + ], + }, + ) + + self.test_data_generator.put_default_compact_configuration_in_configuration_table( + value_overrides={ + 'compactAbbr': 'octp', + 'configuredStates': [ + {'postalAbbreviation': 'oh', 'isLive': True}, + ], + }, + ) + + # Create event with invalid compact query param + event = generate_test_event('GET', LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE) + event['queryStringParameters'] = {'compact': 'invalid_compact'} + + response = compact_configuration_api_handler(event, self.mock_context) + self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + response_body = json.loads(response['body']) + + # Should return all compacts when invalid compact is provided + self.assertIn('aslp', response_body) + self.assertIn('octp', response_body) + self.assertIn('coun', response_body) + + # Verify the live jurisdictions + self.assertCountEqual(['ky'], response_body['aslp']) + self.assertCountEqual(['oh'], response_body['octp']) + @mock_aws @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index b8f139e14..44998a9f3 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -145,6 +145,48 @@ def test_synth_generates_get_public_compact_jurisdictions_resource(self): overwrite_snapshot=False, ) + def test_synth_generates_get_live_compact_jurisdictions_resource(self): + """Test that the GET /v1/compacts/live endpoint is properly configured as a public endpoint""" + api_stack = self.app.sandbox_backend_stage.api_stack + api_stack_template = Template.from_stack(api_stack) + + # Ensure the resource is created with expected path + api_stack_template.has_resource_properties( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'ParentId': { + # Verify the parent id matches the expected 'compacts' resource + 'Ref': api_stack.get_logical_id(api_stack.api.v1_api.compacts_resource.node.default_child), + }, + 'PathPart': 'live', + }, + ) + + # Get the live compacts resource + live_compacts_resource_id = api_stack.get_logical_id( + api_stack.api.v1_api.live_compacts_resource.node.default_child + ) + + # Ensure the GET method is configured with the lambda integration (no authorizer since it's public) + api_stack_template.has_resource_properties( + type=CfnMethod.CFN_RESOURCE_TYPE_NAME, + props={ + 'HttpMethod': 'GET', + 'ResourceId': {'Ref': live_compacts_resource_id}, + # ensure the lambda integration is configured with the expected handler + 'Integration': TestApi.generate_expected_integration_object( + api_stack.get_logical_id( + api_stack.api.v1_api.compact_configuration_api.compact_configuration_api_function.node.default_child, + ), + ), + 'MethodResponses': [ + { + 'StatusCode': '200', + }, + ], + }, + ) + def test_synth_generates_get_compact_configuration_endpoint(self): """Test that the GET /v1/compacts/{compact} endpoint is properly configured""" api_stack = self.app.sandbox_backend_stage.api_stack From 94eeae58b7d5ad52a7743cf64dcd5fb2c241099d Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Mon, 6 Oct 2025 08:44:55 -0600 Subject: [PATCH 04/19] better endpoint --- .../api-specification/latest-oas30.json | 2 +- .../handlers/compact_configuration.py | 2 +- .../function/test_compact_configuration.py | 2 +- .../stacks/api_stack/v1_api/api.py | 8 +++-- .../v1_api/compact_configuration_api.py | 10 +++---- .../test_compact_configuration_api.py | 30 ++++++++++++++----- 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json index cb4534517..e8e6b5037 100644 --- a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json +++ b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json @@ -10,7 +10,7 @@ } ], "paths": { - "/v1/compacts/live": { + "/v1/public/compacts/jurisdictions/live": { "get": { "summary": "Get live compact jurisdictions", "description": "Returns all jurisdictions that are live (enabled for operations) across all compacts or for a specific compact if the optional compact query parameter is provided.", diff --git a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py index e81245bd1..4c1211467 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py @@ -28,7 +28,7 @@ def compact_configuration_api_handler(event: dict, context: LambdaContext): # n return _get_staff_users_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/public/compacts/{compact}/jurisdictions': return _get_public_compact_jurisdictions(event, context) - if event['httpMethod'] == 'GET' and event['resource'] == '/v1/compacts/live': + if event['httpMethod'] == 'GET' and event['resource'] == '/v1/public/compacts/jurisdictions/live': return _get_live_public_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/compacts/{compact}': return _get_staff_users_compact_configuration(event, context) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py index 2d7f9bcb9..20fff5f66 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py @@ -12,7 +12,7 @@ STAFF_USERS_COMPACT_JURISDICTION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/jurisdictions' PUBLIC_COMPACT_JURISDICTION_ENDPOINT_RESOURCE = '/v1/public/compacts/{compact}/jurisdictions' -LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE = '/v1/compacts/live' +LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE = '/v1/public/compacts/jurisdictions/live' COMPACT_CONFIGURATION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}' JURISDICTION_CONFIGURATION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/jurisdictions/{jurisdiction}' diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 3aa27a653..77202f503 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -127,6 +127,10 @@ def __init__( # POST /v1/public/compacts/{compact}/providers/query # GET /v1/public/compacts/{compact}/providers/{providerId} self.public_compacts_resource = self.public_resource.add_resource('compacts') + # /v1/public/compacts/jurisdictions + self.public_compacts_jurisdictions_resource = self.public_compacts_resource.add_resource('jurisdictions') + # /v1/public/compacts/jurisdictions/live + self.live_compacts_jurisdictions_resource = self.public_compacts_jurisdictions_resource.add_resource('live') self.public_compacts_compact_resource = self.public_compacts_resource.add_resource('{compact}') self.public_compacts_compact_providers_resource = self.public_compacts_compact_resource.add_resource( 'providers' @@ -160,8 +164,6 @@ def __init__( # /v1/compacts self.compacts_resource = self.resource.add_resource('compacts') - # /v1/compacts/live - self.live_compacts_resource = self.compacts_resource.add_resource('live') # /v1/compacts/{compact} self.compact_resource = self.compacts_resource.add_resource('{compact}') @@ -207,7 +209,7 @@ def __init__( self.compact_configuration_api = CompactConfigurationApi( api=self.api, compact_resource=self.compact_resource, - live_compacts_resource=self.live_compacts_resource, + live_compacts_jurisdictions_resource=self.live_compacts_jurisdictions_resource, jurisdictions_resource=self.jurisdictions_resource, public_jurisdictions_resource=self.public_compacts_compact_jurisdictions_resource, jurisdiction_resource=self.jurisdiction_resource, diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index e1cd4f7bb..ce938dfab 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -25,7 +25,7 @@ def __init__( *, api: CCApi, compact_resource: Resource, - live_compacts_resource: Resource, + live_compacts_jurisdictions_resource: Resource, jurisdictions_resource: Resource, public_jurisdictions_resource: Resource, jurisdiction_resource: Resource, @@ -39,8 +39,8 @@ def __init__( self.api = api # /v1/compacts/{compact} self.staff_users_compact_resource = compact_resource - # /v1/compacts/live - self.live_compacts_resource = live_compacts_resource + # /v1/public/compacts/jurisdictions/live + self.live_compacts_jurisdictions_resource = live_compacts_jurisdictions_resource # /v1/compacts/{compact}/jurisdictions self.staff_users_jurisdictions_resource = jurisdictions_resource # /v1/compacts/{compact}/jurisdictions/{jurisdiction} @@ -162,8 +162,8 @@ def _add_public_get_compact_jurisdictions_endpoint(self, compact_configuration_a ) def _add_get_live_compact_jurisdictions_endpoint(self, compact_configuration_api_handler: PythonFunction): - """Add GET endpoint for /v1/compacts/live""" - get_live_compact_jurisdictions_method = self.live_compacts_resource.add_method( + """Add GET endpoint for /v1/public/compacts/jurisdictions/live""" + get_live_compact_jurisdictions_method = self.live_compacts_jurisdictions_resource.add_method( 'GET', LambdaIntegration(compact_configuration_api_handler), method_responses=[ diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index 44998a9f3..fa0c567aa 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -146,25 +146,39 @@ def test_synth_generates_get_public_compact_jurisdictions_resource(self): ) def test_synth_generates_get_live_compact_jurisdictions_resource(self): - """Test that the GET /v1/compacts/live endpoint is properly configured as a public endpoint""" + """Test that the GET /v1/public/compacts/jurisdictions/live endpoint is properly configured as a public endpoint""" api_stack = self.app.sandbox_backend_stage.api_stack api_stack_template = Template.from_stack(api_stack) - # Ensure the resource is created with expected path + # Ensure the /v1/public/compacts/jurisdictions resource is created + api_stack_template.has_resource_properties( + type=CfnResource.CFN_RESOURCE_TYPE_NAME, + props={ + 'ParentId': { + # Verify the parent id matches the expected 'public/compacts' resource + 'Ref': api_stack.get_logical_id(api_stack.api.v1_api.public_compacts_resource.node.default_child), + }, + 'PathPart': 'jurisdictions', + }, + ) + + # Ensure the /v1/public/compacts/jurisdictions/live resource is created api_stack_template.has_resource_properties( type=CfnResource.CFN_RESOURCE_TYPE_NAME, props={ 'ParentId': { - # Verify the parent id matches the expected 'compacts' resource - 'Ref': api_stack.get_logical_id(api_stack.api.v1_api.compacts_resource.node.default_child), + # Verify the parent id matches the expected 'public/compacts/jurisdictions' resource + 'Ref': api_stack.get_logical_id( + api_stack.api.v1_api.public_compacts_jurisdictions_resource.node.default_child + ), }, 'PathPart': 'live', }, ) - # Get the live compacts resource - live_compacts_resource_id = api_stack.get_logical_id( - api_stack.api.v1_api.live_compacts_resource.node.default_child + # Get the live compacts jurisdictions resource + live_compacts_jurisdictions_resource_id = api_stack.get_logical_id( + api_stack.api.v1_api.live_compacts_jurisdictions_resource.node.default_child ) # Ensure the GET method is configured with the lambda integration (no authorizer since it's public) @@ -172,7 +186,7 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): type=CfnMethod.CFN_RESOURCE_TYPE_NAME, props={ 'HttpMethod': 'GET', - 'ResourceId': {'Ref': live_compacts_resource_id}, + 'ResourceId': {'Ref': live_compacts_jurisdictions_resource_id}, # ensure the lambda integration is configured with the expected handler 'Integration': TestApi.generate_expected_integration_object( api_stack.get_logical_id( From 986f8ef1e3ddb30db0453f6206580b82cae8743b Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Mon, 6 Oct 2025 15:23:42 -0600 Subject: [PATCH 05/19] lint fixes --- .../tests/app/test_api/test_compact_configuration_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index fa0c567aa..c51deedc2 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -146,7 +146,8 @@ def test_synth_generates_get_public_compact_jurisdictions_resource(self): ) def test_synth_generates_get_live_compact_jurisdictions_resource(self): - """Test that the GET /v1/public/compacts/jurisdictions/live endpoint is properly configured as a public endpoint""" + """Test that the GET /v1/public/compacts/jurisdictions/live + endpoint is properly configured as a public endpoint""" api_stack = self.app.sandbox_backend_stage.api_stack api_stack_template = Template.from_stack(api_stack) From cfce8393262bcb31489f3fba8756ec5930f9a429 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 15 Oct 2025 14:45:39 -0600 Subject: [PATCH 06/19] pr fixes --- .../handlers/compact_configuration.py | 6 +++--- .../function/test_compact_configuration.py | 18 ++++++------------ .../stacks/api_stack/v1_api/api.py | 4 ++-- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py index 4c1211467..fe60fc2b8 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py @@ -28,7 +28,7 @@ def compact_configuration_api_handler(event: dict, context: LambdaContext): # n return _get_staff_users_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/public/compacts/{compact}/jurisdictions': return _get_public_compact_jurisdictions(event, context) - if event['httpMethod'] == 'GET' and event['resource'] == '/v1/public/compacts/jurisdictions/live': + if event['httpMethod'] == 'GET' and event['resource'] == '/v1/public/jurisdictions/live': return _get_live_public_compact_jurisdictions(event, context) if event['httpMethod'] == 'GET' and event['resource'] == '/v1/compacts/{compact}': return _get_staff_users_compact_configuration(event, context) @@ -139,8 +139,8 @@ def _get_live_public_compact_jurisdictions(event: dict, context: LambdaContext): compacts_to_query = [compact_filter.lower()] logger.info('Getting live jurisdictions for specific compact', compact=compact_filter) else: - logger.info('Invalid compact provided, returning data for all compacts', compact=compact_filter) - compacts_to_query = config.compacts + logger.error('Invalid compact provided', compact=compact_filter) + raise CCInvalidRequestException(f'Invalid request query param: {compact_filter}') else: logger.info('Getting live jurisdictions for all compacts') compacts_to_query = config.compacts diff --git a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py index 20fff5f66..870a2319f 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py @@ -12,7 +12,7 @@ STAFF_USERS_COMPACT_JURISDICTION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/jurisdictions' PUBLIC_COMPACT_JURISDICTION_ENDPOINT_RESOURCE = '/v1/public/compacts/{compact}/jurisdictions' -LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE = '/v1/public/compacts/jurisdictions/live' +LIVE_JURISDICTIONS_ENDPOINT_RESOURCE = '/v1/public/jurisdictions/live' COMPACT_CONFIGURATION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}' JURISDICTION_CONFIGURATION_ENDPOINT_RESOURCE = '/v1/compacts/{compact}/jurisdictions/{jurisdiction}' @@ -231,7 +231,7 @@ def test_get_public_live_compact_jurisdictions_returns_list_of_all_live_jurisdic ) # Create event without query params - event = generate_test_event('GET', LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE) + event = generate_test_event('GET', LIVE_JURISDICTIONS_ENDPOINT_RESOURCE) event['queryStringParameters'] = None response = compact_configuration_api_handler(event, self.mock_context) @@ -274,7 +274,7 @@ def test_get_public_live_compact_jurisdictions_returns_list_of_live_jurisdiction ) # Create event with compact query param - event = generate_test_event('GET', LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE) + event = generate_test_event('GET', LIVE_JURISDICTIONS_ENDPOINT_RESOURCE) event['queryStringParameters'] = {'compact': 'aslp'} response = compact_configuration_api_handler(event, self.mock_context) @@ -313,21 +313,15 @@ def test_get_public_live_compact_jurisdictions_returns_list_of_all_live_jurisdic ) # Create event with invalid compact query param - event = generate_test_event('GET', LIVE_COMPACT_JURISDICTIONS_ENDPOINT_RESOURCE) + event = generate_test_event('GET', LIVE_JURISDICTIONS_ENDPOINT_RESOURCE) event['queryStringParameters'] = {'compact': 'invalid_compact'} response = compact_configuration_api_handler(event, self.mock_context) - self.assertEqual(200, response['statusCode'], msg=json.loads(response['body'])) + self.assertEqual(400, response['statusCode'], msg=json.loads(response['body'])) response_body = json.loads(response['body']) - # Should return all compacts when invalid compact is provided - self.assertIn('aslp', response_body) - self.assertIn('octp', response_body) - self.assertIn('coun', response_body) - # Verify the live jurisdictions - self.assertCountEqual(['ky'], response_body['aslp']) - self.assertCountEqual(['oh'], response_body['octp']) + self.assertCountEqual({'message': 'Invalid request query param: invalid_compact'}, response_body) @mock_aws diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 77202f503..8f6ea36de 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -128,9 +128,9 @@ def __init__( # GET /v1/public/compacts/{compact}/providers/{providerId} self.public_compacts_resource = self.public_resource.add_resource('compacts') # /v1/public/compacts/jurisdictions - self.public_compacts_jurisdictions_resource = self.public_compacts_resource.add_resource('jurisdictions') + self.public_jurisdictions_resource = self.public_resource.add_resource('jurisdictions') # /v1/public/compacts/jurisdictions/live - self.live_compacts_jurisdictions_resource = self.public_compacts_jurisdictions_resource.add_resource('live') + self.live_jurisdictions_resource = self.public_jurisdictions_resource.add_resource('live') self.public_compacts_compact_resource = self.public_compacts_resource.add_resource('{compact}') self.public_compacts_compact_providers_resource = self.public_compacts_compact_resource.add_resource( 'providers' From d67d48d093220d630e26b803ef317a225df81de8 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 15 Oct 2025 14:51:46 -0600 Subject: [PATCH 07/19] update docs --- .../docs/internal/api-specification/latest-oas30.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json index e8e6b5037..d601032d9 100644 --- a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json +++ b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json @@ -10,16 +10,16 @@ } ], "paths": { - "/v1/public/compacts/jurisdictions/live": { + "/v1/public/jurisdictions/live": { "get": { - "summary": "Get live compact jurisdictions", + "summary": "Get live jurisdictions", "description": "Returns all jurisdictions that are live (enabled for operations) across all compacts or for a specific compact if the optional compact query parameter is provided.", "parameters": [ { "name": "compact", "in": "query", "required": false, - "description": "Optional compact abbreviation to filter results. If not provided or invalid, returns data for all compacts.", + "description": "Optional compact abbreviation to filter results. If not provided, returns data for all compacts.", "schema": { "type": "string" } From e3d953832ba4d5e5307854c5a3eb95b289cfd07c Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 15 Oct 2025 14:52:36 -0600 Subject: [PATCH 08/19] fix --- backend/compact-connect/stacks/api_stack/v1_api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 8f6ea36de..a1eda0ed4 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -209,7 +209,7 @@ def __init__( self.compact_configuration_api = CompactConfigurationApi( api=self.api, compact_resource=self.compact_resource, - live_compacts_jurisdictions_resource=self.live_compacts_jurisdictions_resource, + live_compacts_jurisdictions_resource=self.live_jurisdictions_resource, jurisdictions_resource=self.jurisdictions_resource, public_jurisdictions_resource=self.public_compacts_compact_jurisdictions_resource, jurisdiction_resource=self.jurisdiction_resource, From f1ffd13387a472edbbee9a4ec53f332813175b8e Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 15 Oct 2025 15:24:00 -0600 Subject: [PATCH 09/19] fix tests --- .../tests/app/test_api/test_compact_configuration_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index c51deedc2..040f66f47 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -151,7 +151,7 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): api_stack = self.app.sandbox_backend_stage.api_stack api_stack_template = Template.from_stack(api_stack) - # Ensure the /v1/public/compacts/jurisdictions resource is created + # Ensure the /v1/public/jurisdictions resource is created api_stack_template.has_resource_properties( type=CfnResource.CFN_RESOURCE_TYPE_NAME, props={ @@ -170,7 +170,7 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): 'ParentId': { # Verify the parent id matches the expected 'public/compacts/jurisdictions' resource 'Ref': api_stack.get_logical_id( - api_stack.api.v1_api.public_compacts_jurisdictions_resource.node.default_child + api_stack.api.v1_api.public_jurisdictions_resource.node.default_child ), }, 'PathPart': 'live', @@ -179,7 +179,7 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): # Get the live compacts jurisdictions resource live_compacts_jurisdictions_resource_id = api_stack.get_logical_id( - api_stack.api.v1_api.live_compacts_jurisdictions_resource.node.default_child + api_stack.api.v1_api.live_jurisdictions_resource.node.default_child ) # Ensure the GET method is configured with the lambda integration (no authorizer since it's public) @@ -187,7 +187,7 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): type=CfnMethod.CFN_RESOURCE_TYPE_NAME, props={ 'HttpMethod': 'GET', - 'ResourceId': {'Ref': live_compacts_jurisdictions_resource_id}, + 'ResourceId': {'Ref': live_jurisdictions_resource_id}, # ensure the lambda integration is configured with the expected handler 'Integration': TestApi.generate_expected_integration_object( api_stack.get_logical_id( From c597434fffac1bc1134cd1c7ecc9a3e3e773b6bf Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 15 Oct 2025 15:26:11 -0600 Subject: [PATCH 10/19] fix variable name --- .../tests/app/test_api/test_compact_configuration_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index 040f66f47..9ea480e4f 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -178,7 +178,7 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): ) # Get the live compacts jurisdictions resource - live_compacts_jurisdictions_resource_id = api_stack.get_logical_id( + live_jurisdictions_resource_id = api_stack.get_logical_id( api_stack.api.v1_api.live_jurisdictions_resource.node.default_child ) From dc286b3804045d59dc64987906821e2147d10a40 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 15 Oct 2025 15:29:04 -0600 Subject: [PATCH 11/19] fix comment --- backend/compact-connect/stacks/api_stack/v1_api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index a1eda0ed4..56a82a176 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -127,7 +127,7 @@ def __init__( # POST /v1/public/compacts/{compact}/providers/query # GET /v1/public/compacts/{compact}/providers/{providerId} self.public_compacts_resource = self.public_resource.add_resource('compacts') - # /v1/public/compacts/jurisdictions + # /v1/public/jurisdictions self.public_jurisdictions_resource = self.public_resource.add_resource('jurisdictions') # /v1/public/compacts/jurisdictions/live self.live_jurisdictions_resource = self.public_jurisdictions_resource.add_resource('live') From ca4eda341a597759779b824bb067987db2188ab5 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 15 Oct 2025 15:33:55 -0600 Subject: [PATCH 12/19] log level --- .../compact-configuration/handlers/compact_configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py index fe60fc2b8..304c848a2 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py @@ -139,7 +139,7 @@ def _get_live_public_compact_jurisdictions(event: dict, context: LambdaContext): compacts_to_query = [compact_filter.lower()] logger.info('Getting live jurisdictions for specific compact', compact=compact_filter) else: - logger.error('Invalid compact provided', compact=compact_filter) + logger.info('Invalid compact provided', compact=compact_filter) raise CCInvalidRequestException(f'Invalid request query param: {compact_filter}') else: logger.info('Getting live jurisdictions for all compacts') From 270ebb4d1de908272638ae4cbfce3879012423c4 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Thu, 16 Oct 2025 07:50:49 -0600 Subject: [PATCH 13/19] fixes --- .../handlers/compact_configuration.py | 2 +- .../compact-connect/stacks/api_stack/v1_api/api.py | 4 ++-- .../api_stack/v1_api/compact_configuration_api.py | 12 ++++++------ .../app/test_api/test_compact_configuration_api.py | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py index 304c848a2..0b939f671 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/handlers/compact_configuration.py @@ -134,7 +134,7 @@ def _get_live_public_compact_jurisdictions(event: dict, context: LambdaContext): # Determine which compacts to query compacts_to_query = [] if compact_filter: - # Validate the compact, if invalid treat as if no filter was provided + # Validate the compact if compact_filter.lower() in config.compacts: compacts_to_query = [compact_filter.lower()] logger.info('Getting live jurisdictions for specific compact', compact=compact_filter) diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 56a82a176..2d7c3294d 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -129,7 +129,7 @@ def __init__( self.public_compacts_resource = self.public_resource.add_resource('compacts') # /v1/public/jurisdictions self.public_jurisdictions_resource = self.public_resource.add_resource('jurisdictions') - # /v1/public/compacts/jurisdictions/live + # /v1/public/jurisdictions/live self.live_jurisdictions_resource = self.public_jurisdictions_resource.add_resource('live') self.public_compacts_compact_resource = self.public_compacts_resource.add_resource('{compact}') self.public_compacts_compact_providers_resource = self.public_compacts_compact_resource.add_resource( @@ -209,7 +209,7 @@ def __init__( self.compact_configuration_api = CompactConfigurationApi( api=self.api, compact_resource=self.compact_resource, - live_compacts_jurisdictions_resource=self.live_jurisdictions_resource, + live_jurisdictions_resource=self.live_jurisdictions_resource, jurisdictions_resource=self.jurisdictions_resource, public_jurisdictions_resource=self.public_compacts_compact_jurisdictions_resource, jurisdiction_resource=self.jurisdiction_resource, diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index ce938dfab..d1111a322 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -25,7 +25,7 @@ def __init__( *, api: CCApi, compact_resource: Resource, - live_compacts_jurisdictions_resource: Resource, + live_jurisdictions_resource: Resource, jurisdictions_resource: Resource, public_jurisdictions_resource: Resource, jurisdiction_resource: Resource, @@ -39,8 +39,8 @@ def __init__( self.api = api # /v1/compacts/{compact} self.staff_users_compact_resource = compact_resource - # /v1/public/compacts/jurisdictions/live - self.live_compacts_jurisdictions_resource = live_compacts_jurisdictions_resource + # /v1/public/jurisdictions/live + self.live_jurisdictions_resource = live_jurisdictions_resource # /v1/compacts/{compact}/jurisdictions self.staff_users_jurisdictions_resource = jurisdictions_resource # /v1/compacts/{compact}/jurisdictions/{jurisdiction} @@ -161,9 +161,9 @@ def _add_public_get_compact_jurisdictions_endpoint(self, compact_configuration_a ], ) - def _add_get_live_compact_jurisdictions_endpoint(self, compact_configuration_api_handler: PythonFunction): - """Add GET endpoint for /v1/public/compacts/jurisdictions/live""" - get_live_compact_jurisdictions_method = self.live_compacts_jurisdictions_resource.add_method( + def _add_get_live_jurisdictions_endpoint(self, compact_configuration_api_handler: PythonFunction): + """Add GET endpoint for /v1/public/jurisdictions/live""" + get_live_compact_jurisdictions_method = self.live_jurisdictions_resource.add_method( 'GET', LambdaIntegration(compact_configuration_api_handler), method_responses=[ diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index 9ea480e4f..345eccb47 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -145,8 +145,8 @@ def test_synth_generates_get_public_compact_jurisdictions_resource(self): overwrite_snapshot=False, ) - def test_synth_generates_get_live_compact_jurisdictions_resource(self): - """Test that the GET /v1/public/compacts/jurisdictions/live + def test_synth_generates_get_live_jurisdictions_resource(self): + """Test that the GET /v1/public/jurisdictions/live endpoint is properly configured as a public endpoint""" api_stack = self.app.sandbox_backend_stage.api_stack api_stack_template = Template.from_stack(api_stack) @@ -156,14 +156,14 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): type=CfnResource.CFN_RESOURCE_TYPE_NAME, props={ 'ParentId': { - # Verify the parent id matches the expected 'public/compacts' resource - 'Ref': api_stack.get_logical_id(api_stack.api.v1_api.public_compacts_resource.node.default_child), + # Verify the parent id matches the expected 'public/' resource + 'Ref': api_stack.get_logical_id(api_stack.api.v1_api.public_resource.node.default_child), }, 'PathPart': 'jurisdictions', }, ) - # Ensure the /v1/public/compacts/jurisdictions/live resource is created + # Ensure the /v1/public/jurisdictions/live resource is created api_stack_template.has_resource_properties( type=CfnResource.CFN_RESOURCE_TYPE_NAME, props={ @@ -177,7 +177,7 @@ def test_synth_generates_get_live_compact_jurisdictions_resource(self): }, ) - # Get the live compacts jurisdictions resource + # Get the live jurisdictions resource live_jurisdictions_resource_id = api_stack.get_logical_id( api_stack.api.v1_api.live_jurisdictions_resource.node.default_child ) From a0aeb4312a53f6d2f4a5badee96f6d5b032bab7d Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Thu, 16 Oct 2025 08:09:19 -0600 Subject: [PATCH 14/19] fixes --- .../docs/api-specification/latest-oas30.json | 55 +++++++++++++++++++ .../api-specification/latest-oas30.json | 18 +++++- .../v1_api/compact_configuration_api.py | 2 +- .../test_compact_configuration_api.py | 2 +- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/backend/compact-connect/docs/api-specification/latest-oas30.json b/backend/compact-connect/docs/api-specification/latest-oas30.json index 9efceb7ba..8755ac246 100644 --- a/backend/compact-connect/docs/api-specification/latest-oas30.json +++ b/backend/compact-connect/docs/api-specification/latest-oas30.json @@ -10,6 +10,61 @@ } ], "paths": { + "/v1/public/jurisdictions/live": { + "get": { + "summary": "Get live jurisdictions", + "description": "Returns all jurisdictions that are live (enabled for operations) across all compacts or for a specific compact if the optional compact query parameter is provided.", + "parameters": [ + { + "name": "compact", + "in": "query", + "required": false, + "description": "Optional compact abbreviation to filter results. If not provided, returns data for all compacts. If an invalid compact is provided, returns a 400 error.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "200 response - Returns a dictionary with compact abbreviations as keys and arrays of live jurisdiction postal abbreviations as values", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": { + "aslp": ["co", "ne", "wy"], + "octp": ["ak", "ky"] + } + } + } + } + }, + "400": { + "description": "400 response - Invalid compact abbreviation provided", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Invalid request query param: invalid_compact" + } + } + } + } + } + } + } + } + }, "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses": { "post": { "parameters": [ diff --git a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json index d601032d9..2e937582a 100644 --- a/backend/compact-connect/docs/internal/api-specification/latest-oas30.json +++ b/backend/compact-connect/docs/internal/api-specification/latest-oas30.json @@ -19,7 +19,7 @@ "name": "compact", "in": "query", "required": false, - "description": "Optional compact abbreviation to filter results. If not provided, returns data for all compacts.", + "description": "Optional compact abbreviation to filter results. If not provided, returns data for all compacts. If an invalid compact is provided, returns a 400 error.", "schema": { "type": "string" } @@ -45,6 +45,22 @@ } } } + }, + "400": { + "description": "400 response - Invalid compact abbreviation provided", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Invalid request query param: invalid_compact" + } + } + } + } + } } } } diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index d1111a322..45f7dc699 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -91,7 +91,7 @@ def __init__( compact_configuration_api_handler=self.compact_configuration_api_function, ) - self._add_get_live_compact_jurisdictions_endpoint( + self._add_get_live_jurisdictions_endpoint( compact_configuration_api_handler=self.compact_configuration_api_function, ) diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index 345eccb47..39ed51836 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -168,7 +168,7 @@ def test_synth_generates_get_live_jurisdictions_resource(self): type=CfnResource.CFN_RESOURCE_TYPE_NAME, props={ 'ParentId': { - # Verify the parent id matches the expected 'public/compacts/jurisdictions' resource + # Verify the parent id matches the expected 'public/jurisdictions' resource 'Ref': api_stack.get_logical_id( api_stack.api.v1_api.public_jurisdictions_resource.node.default_child ), From e3434bdaa47594422c08d7fb4fb2bff441d0db98 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 22 Oct 2025 12:16:59 -0600 Subject: [PATCH 15/19] comment and name fix --- .../tests/function/test_compact_configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py index 870a2319f..e65cf3b4a 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py @@ -289,8 +289,8 @@ def test_get_public_live_compact_jurisdictions_returns_list_of_live_jurisdiction # Verify the live jurisdictions self.assertCountEqual(['ky', 'oh'], response_body['aslp']) - def test_get_public_live_compact_jurisdictions_returns_list_of_all_live_jurisdictions_if_bad_compact_param(self): - """Test getting list of live jurisdictions across all compacts when invalid query param provided""" + def test_get_public_live_compact_jurisdictions_returns_400_if_bad_compact_param(self): + """Test getting list of live jurisdictions across all compacts when invalid query param provided returns 400""" from handlers.compact_configuration import compact_configuration_api_handler # Create compact configurations From f9bb7e8f7f94a86572d59b6e466c83d97d72721c Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 22 Oct 2025 12:18:15 -0600 Subject: [PATCH 16/19] comment fix --- .../tests/function/test_compact_configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py index e65cf3b4a..1b4339e93 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py @@ -290,7 +290,7 @@ def test_get_public_live_compact_jurisdictions_returns_list_of_live_jurisdiction self.assertCountEqual(['ky', 'oh'], response_body['aslp']) def test_get_public_live_compact_jurisdictions_returns_400_if_bad_compact_param(self): - """Test getting list of live jurisdictions across all compacts when invalid query param provided returns 400""" + """Test getting list of live jurisdictions returns 400 when invalid query param provided""" from handlers.compact_configuration import compact_configuration_api_handler # Create compact configurations From 03d66572b97cf7739385b62f552c1ce4d568153e Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 22 Oct 2025 13:13:56 -0600 Subject: [PATCH 17/19] fix merge issues --- .../api_stack/v1_api/compact_configuration_api.py | 2 +- .../app/test_api/test_compact_configuration_api.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index 0c403020e..f46bada4f 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -61,7 +61,7 @@ def __init__( ) self._add_get_live_jurisdictions_endpoint( - compact_configuration_api_handler=self.compact_configuration_api_function, + compact_configuration_api_handler=compact_configuration_api_function, ) self._add_staff_users_get_compact_configuration_endpoint( diff --git a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py index 6211155ec..4881784f6 100644 --- a/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py +++ b/backend/compact-connect/tests/app/test_api/test_compact_configuration_api.py @@ -155,6 +155,8 @@ def test_synth_generates_get_live_jurisdictions_resource(self): endpoint is properly configured as a public endpoint""" api_stack = self.app.sandbox_backend_stage.api_stack api_stack_template = Template.from_stack(api_stack) + api_lambda_stack = self.app.sandbox_backend_stage.api_lambda_stack + api_lambda_stack_template = Template.from_stack(api_lambda_stack) # Ensure the /v1/public/jurisdictions resource is created api_stack_template.has_resource_properties( @@ -194,10 +196,10 @@ def test_synth_generates_get_live_jurisdictions_resource(self): 'HttpMethod': 'GET', 'ResourceId': {'Ref': live_jurisdictions_resource_id}, # ensure the lambda integration is configured with the expected handler - 'Integration': TestApi.generate_expected_integration_object( - api_stack.get_logical_id( - api_stack.api.v1_api.compact_configuration_api.compact_configuration_api_function.node.default_child, - ), + 'Integration': TestApi.generate_expected_integration_object_for_imported_lambda( + api_lambda_stack, + api_lambda_stack_template, + api_lambda_stack.compact_configuration_lambdas.compact_configuration_api_handler, ), 'MethodResponses': [ { From c3969157bc5dadffed70fb10218c3258ee83563e Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 22 Oct 2025 19:07:51 -0600 Subject: [PATCH 18/19] code rabbit fixes --- .../function/test_compact_configuration.py | 4 ++-- .../stacks/api_stack/v1_api/api_model.py | 24 +++++++++++++++++++ .../v1_api/compact_configuration_api.py | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py index 1b4339e93..2b18e44a9 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py +++ b/backend/compact-connect/lambdas/python/compact-configuration/tests/function/test_compact_configuration.py @@ -320,8 +320,8 @@ def test_get_public_live_compact_jurisdictions_returns_400_if_bad_compact_param( self.assertEqual(400, response['statusCode'], msg=json.loads(response['body'])) response_body = json.loads(response['body']) - # Verify the live jurisdictions - self.assertCountEqual({'message': 'Invalid request query param: invalid_compact'}, response_body) + # Verify the error message + self.assertEqual({'message': 'Invalid request query param: invalid_compact'}, response_body) @mock_aws diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py index 1dd14a3bc..f43d58b3d 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py @@ -2360,6 +2360,30 @@ def public_provider_response_model(self) -> Model: ) return self.api._v1_public_provider_response_model + @property + def get_live_jurisdiction_model(self) -> Model: + """Return the get live jurisdiction response model, which should only be created once per API""" + if hasattr(self.api, '_v1_get_live_jurisdiction_response_model'): + return self.api._v1_get_live_jurisdiction_response_model + + # Shape: { "": ["ky", "oh", ...], ... } + # Keys are dynamic compact abbreviations; values are arrays of jurisdiction abbreviations + self.api._v1_get_live_jurisdiction_response_model = self.api.add_model( + 'V1GetLiveJurisdictionsResponseModel', + description='Dictionary keyed by compact abbreviations with arrays of live jurisdiction abbreviations', + schema=JsonSchema( + type=JsonSchemaType.OBJECT, + additional_properties=JsonSchema( + type=JsonSchemaType.ARRAY, + items=JsonSchema( + type=JsonSchemaType.STRING, + enum=self.api.node.get_context('jurisdictions'), + ), + ), + ), + ) + return self.api._v1_get_live_jurisdiction_response_model + @property def _public_provider_detailed_response_schema(self): """Schema for public provider responses based on ProviderPublicResponseSchema""" diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index f46bada4f..955c06abb 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -138,6 +138,7 @@ def _add_get_live_jurisdictions_endpoint(self, compact_configuration_api_handler method_responses=[ MethodResponse( status_code='200', + response_models={'application/json': self.api_model.get_live_jurisdiction_model}, ), ], request_parameters={ From fe7f010f98f3209ab295799c64304af722ba7612 Mon Sep 17 00:00:00 2001 From: Dana Stiefel Date: Wed, 22 Oct 2025 19:09:55 -0600 Subject: [PATCH 19/19] plural --- .../stacks/api_stack/v1_api/api_model.py | 12 ++++++------ .../api_stack/v1_api/compact_configuration_api.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py index f43d58b3d..d29b30def 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py @@ -2361,14 +2361,14 @@ def public_provider_response_model(self) -> Model: return self.api._v1_public_provider_response_model @property - def get_live_jurisdiction_model(self) -> Model: - """Return the get live jurisdiction response model, which should only be created once per API""" - if hasattr(self.api, '_v1_get_live_jurisdiction_response_model'): - return self.api._v1_get_live_jurisdiction_response_model + def get_live_jurisdictions_model(self) -> Model: + """Return the get live jurisdictions response model, which should only be created once per API""" + if hasattr(self.api, '_v1_get_live_jurisdictions_response_model'): + return self.api._v1_get_live_jurisdictions_response_model # Shape: { "": ["ky", "oh", ...], ... } # Keys are dynamic compact abbreviations; values are arrays of jurisdiction abbreviations - self.api._v1_get_live_jurisdiction_response_model = self.api.add_model( + self.api._v1_get_live_jurisdictions_response_model = self.api.add_model( 'V1GetLiveJurisdictionsResponseModel', description='Dictionary keyed by compact abbreviations with arrays of live jurisdiction abbreviations', schema=JsonSchema( @@ -2382,7 +2382,7 @@ def get_live_jurisdiction_model(self) -> Model: ), ), ) - return self.api._v1_get_live_jurisdiction_response_model + return self.api._v1_get_live_jurisdictions_response_model @property def _public_provider_detailed_response_schema(self): diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index 955c06abb..a0c46e7b3 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -138,7 +138,7 @@ def _add_get_live_jurisdictions_endpoint(self, compact_configuration_api_handler method_responses=[ MethodResponse( status_code='200', - response_models={'application/json': self.api_model.get_live_jurisdiction_model}, + response_models={'application/json': self.api_model.get_live_jurisdictions_model}, ), ], request_parameters={