diff --git a/app/src/helpers/requests/getDocumentSearchResults.ts b/app/src/helpers/requests/getDocumentSearchResults.ts index bbcd8ed000..6afb16317f 100644 --- a/app/src/helpers/requests/getDocumentSearchResults.ts +++ b/app/src/helpers/requests/getDocumentSearchResults.ts @@ -21,7 +21,7 @@ const getDocumentSearchResults = async ({ nhsNumber, baseUrl, baseHeaders, - docType = DOCUMENT_TYPE.ALL, + docType, }: DocumentSearchResultsArgs): Promise> => { const gatewayUrl = baseUrl + endpoints.DOCUMENT_SEARCH; @@ -32,7 +32,7 @@ const getDocumentSearchResults = async ({ }, params: { patientId: nhsNumber?.replaceAll(/\s/g, ''), // replace whitespace - docType: docType, + docType: docType == DOCUMENT_TYPE.ALL ? undefined : docType, }, }); return response?.data; diff --git a/lambdas/handlers/document_reference_search_handler.py b/lambdas/handlers/document_reference_search_handler.py index 7e1aafdef0..74c5d37bff 100755 --- a/lambdas/handlers/document_reference_search_handler.py +++ b/lambdas/handlers/document_reference_search_handler.py @@ -1,6 +1,7 @@ import json from enums.feature_flags import FeatureFlags +from enums.lambda_error import LambdaError from enums.logging_app_interaction import LoggingAppInteraction from services.document_reference_search_service import DocumentReferenceSearchService from services.feature_flags_service import FeatureFlagService @@ -13,6 +14,8 @@ extract_nhs_number_from_event, validate_patient_id, ) +from utils.document_type_utils import extract_document_type_to_enum +from utils.lambda_exceptions import DocumentRefSearchException from utils.lambda_response import ApiGatewayResponse from utils.request_context import request_context @@ -29,6 +32,13 @@ def lambda_handler(event, context): logger.info("Starting document reference search process") nhs_number = extract_nhs_number_from_event(event) + + doc_type = event.get("queryStringParameters", {}).get("docType", None) + try: + document_snomed_code = extract_document_type_to_enum(doc_type) if doc_type else None + except ValueError: + raise DocumentRefSearchException(400, LambdaError.DocTypeInvalid) + request_context.patient_nhs_no = nhs_number document_reference_search_service = DocumentReferenceSearchService() @@ -38,11 +48,17 @@ def lambda_handler(event, context): doc_upload_iteration2_enabled = upload_lambda_enabled_flag_object[ FeatureFlags.UPLOAD_DOCUMENT_ITERATION_2_ENABLED ] - doc_status_filter = ( - {"doc_status": "final"} if doc_upload_iteration2_enabled else None - ) + + additional_filters = {} + if doc_upload_iteration2_enabled: + additional_filters["doc_status"] = "final" + if document_snomed_code: + additional_filters["document_snomed_code"] = document_snomed_code[0].value + response = document_reference_search_service.get_document_references( - nhs_number, check_upload_completed=True, additional_filters=doc_status_filter + nhs_number, + check_upload_completed=True, + additional_filters=additional_filters ) logger.info("User is able to view docs", {"Result": "Successful viewing docs"}) diff --git a/lambdas/services/document_reference_search_service.py b/lambdas/services/document_reference_search_service.py index 0784c7dabf..a4046bb45e 100644 --- a/lambdas/services/document_reference_search_service.py +++ b/lambdas/services/document_reference_search_service.py @@ -124,7 +124,7 @@ def _search_tables_for_documents( return document_resources or None def _get_filter_expression( - self, filters: dict[str, str] = None, upload_completed=False + self, filters: dict[str, str | None] = None, upload_completed=False ): if filters: return self._build_filter_expression(filters) @@ -207,6 +207,14 @@ def _build_filter_expression(self, filter_values: dict[str, str]): attr_operator=AttributeOperator.EQUAL, filter_value=filter_value, ) + elif filter_key == "document_snomed_code": + filter_builder.add_condition( + attribute=str( + DocumentReferenceMetadataFields.DOCUMENT_SNOMED_CODE_TYPE.value + ), + attr_operator=AttributeOperator.EQUAL, + filter_value=filter_value, + ) if filter_values: filter_expression = filter_builder.build() & NotDeleted else: diff --git a/lambdas/tests/unit/enums/test_metadata_field_names.py b/lambdas/tests/unit/enums/test_metadata_field_names.py index b4c1708130..72e616f2c6 100755 --- a/lambdas/tests/unit/enums/test_metadata_field_names.py +++ b/lambdas/tests/unit/enums/test_metadata_field_names.py @@ -21,8 +21,9 @@ def test_returns_all_as_list(): assert DocumentReferenceMetadataFields.UPLOADED.value in subject assert DocumentReferenceMetadataFields.UPLOADING.value in subject assert DocumentReferenceMetadataFields.LAST_UPDATED.value in subject + assert DocumentReferenceMetadataFields.FILE_SIZE.value in subject assert DocumentReferenceMetadataFields.DOC_STATUS.value in subject - assert DocumentReferenceMetadataFields.DOCUMENT_SCAN_CREATION.value in subject assert DocumentReferenceMetadataFields.CUSTODIAN.value in subject - assert DocumentReferenceMetadataFields.FILE_SIZE.value in subject - assert DocumentReferenceMetadataFields.DOCUMENT_SNOMED_CODE_TYPE.value in subject \ No newline at end of file + assert DocumentReferenceMetadataFields.DOCUMENT_SCAN_CREATION.value in subject + assert DocumentReferenceMetadataFields.DOCUMENT_SNOMED_CODE_TYPE.value in subject + diff --git a/lambdas/tests/unit/handlers/test_document_reference_search_handler.py b/lambdas/tests/unit/handlers/test_document_reference_search_handler.py index c9411818f5..4b6523dad2 100755 --- a/lambdas/tests/unit/handlers/test_document_reference_search_handler.py +++ b/lambdas/tests/unit/handlers/test_document_reference_search_handler.py @@ -128,7 +128,6 @@ def test_lambda_handler_when_dynamo_tables_env_variable_not_supplied_then_return def test_lambda_handler_with_feature_flag_enabled_applies_doc_status_filter( set_env, mocker, valid_id_event_without_auth_header, context ): - """Test that when feature flag is ON, doc_status filter is applied""" mocked_service_class = mocker.patch( "handlers.document_reference_search_handler.DocumentReferenceSearchService" ) @@ -163,7 +162,6 @@ def test_lambda_handler_with_feature_flag_enabled_applies_doc_status_filter( def test_lambda_handler_with_feature_flag_disabled_no_doc_status_filter( set_env, mocker, valid_id_event_without_auth_header, context ): - """Test that when feature flag is OFF, no doc_status filter is applied""" mocked_service_class = mocker.patch( "handlers.document_reference_search_handler.DocumentReferenceSearchService" ) @@ -191,5 +189,70 @@ def test_lambda_handler_with_feature_flag_disabled_no_doc_status_filter( mocked_service.get_document_references.assert_called_once_with( "9000000009", check_upload_completed=True, - additional_filters=None, + additional_filters={}, ) + +def test_lambda_handler_with_doc_type_applies_doc_type_filter( + set_env, mocker, valid_id_event_without_auth_header, context +): + mocked_service_class = mocker.patch( + "handlers.document_reference_search_handler.DocumentReferenceSearchService" + ) + mocked_service = mocked_service_class.return_value + mocked_service.get_document_references.return_value = EXPECTED_RESPONSE + + mocked_feature_flag_service = mocker.patch( + "handlers.document_reference_search_handler.FeatureFlagService" + ) + mocked_feature_flag_instance = mocked_feature_flag_service.return_value + mocked_feature_flag_instance.get_feature_flags_by_flag.return_value = { + FeatureFlags.UPLOAD_DOCUMENT_ITERATION_2_ENABLED: False + } + + expected = ApiGatewayResponse( + 200, json.dumps(EXPECTED_RESPONSE), "GET" + ).create_api_gateway_response() + + doc_type = "16521000000101" + valid_id_event_without_auth_header["queryStringParameters"]["docType"] = doc_type + + actual = lambda_handler(valid_id_event_without_auth_header, context) + + assert expected == actual + mocked_feature_flag_instance.get_feature_flags_by_flag.assert_called_once_with( + FeatureFlags.UPLOAD_DOCUMENT_ITERATION_2_ENABLED + ) + mocked_service.get_document_references.assert_called_once_with( + "9000000009", + check_upload_completed=True, + additional_filters={"document_snomed_code": doc_type}, + ) + + +def test_lambda_handler_with_invalid_doc_type_returns_400( + set_env, mocker, valid_id_event_without_auth_header, context +): + mocker.patch( + "handlers.document_reference_search_handler.DocumentReferenceSearchService" + ) + mocker.patch( + "handlers.document_reference_search_handler.FeatureFlagService" + ) + + invalid_doc_type = "invalid_doc_type" + valid_id_event_without_auth_header["queryStringParameters"]["docType"] = invalid_doc_type + + expected_body = json.dumps( + { + "message": "Invalid document type requested", + "err_code": "VDT_4002", + "interaction_id": "88888888-4444-4444-4444-121212121212", + } + ) + expected = ApiGatewayResponse( + 400, expected_body, "GET" + ).create_api_gateway_response() + + actual = lambda_handler(valid_id_event_without_auth_header, context) + + assert expected == actual \ No newline at end of file diff --git a/lambdas/tests/unit/services/test_document_reference_search_service.py b/lambdas/tests/unit/services/test_document_reference_search_service.py index 93fecdcd74..65396a4490 100644 --- a/lambdas/tests/unit/services/test_document_reference_search_service.py +++ b/lambdas/tests/unit/services/test_document_reference_search_service.py @@ -503,3 +503,13 @@ def test_build_filter_expression_defaults(mock_document_service): actual_filter = mock_document_service._build_filter_expression(filter_values) assert actual_filter == expected_filter + +def test_build_filter_expression_document_snomed_code(mock_document_service): + filter_values = {"document_snomed_code": "16521000000101"} + expected_filter = Attr("DocumentSnomedCodeType").eq("16521000000101") & ( + Attr("Deleted").eq("") | Attr("Deleted").not_exists() + ) + + actual_filter = mock_document_service._build_filter_expression(filter_values) + + assert expected_filter == actual_filter