From 1c99789421411daf7dfa56465bb63000ee5feff1 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Thu, 12 Jun 2025 14:41:08 +0100 Subject: [PATCH 1/9] NRL-497 remove count endpoint and add _summary filter to search endpoints --- .../count_document_reference.py | 45 ------ .../tests/test_count_document_reference.py | 128 ------------------ .../search_document_reference.py | 25 ++++ ...test_search_document_reference_consumer.py | 40 ++++++ .../search_post_document_reference.py | 25 ++++ ...search_post_document_reference_consumer.py | 42 ++++++ api/consumer/swagger.yaml | 50 +++---- .../search_document_reference.py | 18 +++ ...test_search_document_reference_producer.py | 34 +++++ .../search_post_document_reference.py | 16 +++ api/producer/swagger.yaml | 17 +++ layer/nrlf/consumer/fhir/r4/model.py | 21 ++- layer/nrlf/core/log_references.py | 8 ++ layer/nrlf/producer/fhir/r4/model.py | 21 ++- layer/nrlf/producer/fhir/r4/strict_model.py | 21 ++- terraform/infrastructure/lambda.tf | 27 ---- .../countDocumentReference-failure.feature | 104 -------------- .../countDocumentReference-success.feature | 96 ------------- .../searchDocumentReference-success.feature | 37 +++++ ...earchPostDocumentReference-success.feature | 47 +++++++ .../searchDocumentReference-success.feature | 37 +++++ ...earchPostDocumentReference-success.feature | 48 +++++++ 22 files changed, 441 insertions(+), 466 deletions(-) delete mode 100644 api/consumer/countDocumentReference/count_document_reference.py delete mode 100644 api/consumer/countDocumentReference/tests/test_count_document_reference.py delete mode 100644 tests/features/consumer/countDocumentReference-failure.feature delete mode 100644 tests/features/consumer/countDocumentReference-success.feature diff --git a/api/consumer/countDocumentReference/count_document_reference.py b/api/consumer/countDocumentReference/count_document_reference.py deleted file mode 100644 index 921df6ed0..000000000 --- a/api/consumer/countDocumentReference/count_document_reference.py +++ /dev/null @@ -1,45 +0,0 @@ -from nrlf.consumer.fhir.r4.model import Bundle -from nrlf.core.decorators import request_handler -from nrlf.core.dynamodb.repository import DocumentPointerRepository -from nrlf.core.logger import LogReference, logger -from nrlf.core.model import ConnectionMetadata, CountRequestParams -from nrlf.core.response import Response, SpineErrorResponse - - -@request_handler(params=CountRequestParams) -def handler( - metadata: ConnectionMetadata, - params: CountRequestParams, - repository: DocumentPointerRepository, -) -> Response: - """ - Counts the number of document references for a given NHS number. - - Args: - metadata (ConnectionMetadata): The connection metadata. - params (CountRequestParams): The count request parameters. - repository (DocumentPointerRepository): The document pointer repository. - - Returns: - Response: The response containing the count of document references. - """ - logger.log(LogReference.CONCOUNT000) - - if not (nhs_number := params.nhs_number): - logger.log( - LogReference.CONCOUNT001, subject_identifier=params.subject_identifier - ) - return SpineErrorResponse.INVALID_IDENTIFIER_VALUE( - diagnostics="Invalid NHS number provided in the query parameters", - expression="subject:identifier", - ) - - total = repository.count_by_nhs_number( - nhs_number=nhs_number, pointer_types=metadata.pointer_types - ) - - bundle = Bundle(resourceType="Bundle", type="searchset", total=total) - response = Response.from_resource(bundle) - - logger.log(LogReference.CONCOUNT999) - return response diff --git a/api/consumer/countDocumentReference/tests/test_count_document_reference.py b/api/consumer/countDocumentReference/tests/test_count_document_reference.py deleted file mode 100644 index d832589cd..000000000 --- a/api/consumer/countDocumentReference/tests/test_count_document_reference.py +++ /dev/null @@ -1,128 +0,0 @@ -import json - -from moto import mock_aws - -from api.consumer.countDocumentReference.count_document_reference import handler -from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository -from nrlf.tests.data import load_document_reference -from nrlf.tests.dynamodb import mock_repository -from nrlf.tests.events import ( - create_headers, - create_mock_context, - create_test_api_gateway_event, - default_response_headers, -) - - -@mock_aws -@mock_repository -def test_count_document_reference_happy_path(repository: DocumentPointerRepository): - event = create_test_api_gateway_event( - headers=create_headers(), - query_string_parameters={ - "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", - }, - ) - - result = handler(event, create_mock_context()) # type: ignore - body = result.pop("body") - - assert result == { - "statusCode": "200", - "headers": default_response_headers(), - "isBase64Encoded": False, - } - - parsed_body = json.loads(body) - assert parsed_body == {"resourceType": "Bundle", "type": "searchset", "total": 0} - - doc_ref = load_document_reference("Y05868-736253002-Valid") - doc_pointer = DocumentPointer.from_document_reference(doc_ref) - repository.create(doc_pointer) - - result = handler(event, create_mock_context()) # type: ignore - body = result.pop("body") - - assert result == { - "statusCode": "200", - "headers": default_response_headers(), - "isBase64Encoded": False, - } - - parsed_body = json.loads(body) - assert parsed_body == {"resourceType": "Bundle", "type": "searchset", "total": 1} - - -def test_count_document_reference_missing_nhs_number(): - event = create_test_api_gateway_event(headers=create_headers()) - - result = handler(event, create_mock_context()) # type: ignore - body = result.pop("body") - - assert result == { - "statusCode": "400", - "headers": default_response_headers(), - "isBase64Encoded": False, - } - - parsed_body = json.loads(body) - assert parsed_body == { - "resourceType": "OperationOutcome", - "issue": [ - { - "severity": "error", - "code": "invalid", - "details": { - "coding": [ - { - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "INVALID_PARAMETER", - "display": "Invalid parameter", - } - ] - }, - "diagnostics": "Invalid query parameter (subject:identifier: Field required)", - "expression": ["subject:identifier"], - } - ], - } - - -def test_count_document_reference_invalid_nhs_number(): - event = create_test_api_gateway_event( - headers=create_headers(), - query_string_parameters={ - "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|123" - }, - ) - - result = handler(event, create_mock_context()) # type: ignore - body = result.pop("body") - - assert result == { - "statusCode": "400", - "headers": default_response_headers(), - "isBase64Encoded": False, - } - - parsed_body = json.loads(body) - assert parsed_body == { - "resourceType": "OperationOutcome", - "issue": [ - { - "severity": "error", - "code": "invalid", - "details": { - "coding": [ - { - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "INVALID_IDENTIFIER_VALUE", - "display": "Invalid identifier value", - } - ] - }, - "diagnostics": "Invalid NHS number provided in the query parameters", - "expression": ["subject:identifier"], - } - ], - } diff --git a/api/consumer/searchDocumentReference/search_document_reference.py b/api/consumer/searchDocumentReference/search_document_reference.py index c5c211aa1..b69518011 100644 --- a/api/consumer/searchDocumentReference/search_document_reference.py +++ b/api/consumer/searchDocumentReference/search_document_reference.py @@ -87,6 +87,9 @@ def handler( if params.category: self_link += f"&category={params.category.root}" + if params.field_summary: + self_link += f"&_summary={params.field_summary.root}" + bundle = { "resourceType": "Bundle", "type": "searchset", @@ -102,6 +105,28 @@ def handler( pointer_types=pointer_types, ) + if params.field_summary and params.field_summary.root == "count": + bundle = { + "resourceType": "Bundle", + "type": "searchset", + "link": [{"relation": "self", "url": self_link}], + "total": 0, + } + logger.log(LogReference.CONSEARCH006) + total = sum( + 1 + for _ in repository.search( + nhs_number=params.nhs_number, + custodian=custodian_id, + pointer_types=pointer_types, + categories=categories, + ) + ) + bundle["total"] = total + response = Response.from_resource(Bundle.model_validate(bundle)) + logger.log(LogReference.CONSEARCH999) + return response + for result in repository.search( nhs_number=params.nhs_number, custodian=custodian_id, diff --git a/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py b/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py index 3d6e65ba4..30403304e 100644 --- a/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py +++ b/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py @@ -718,6 +718,46 @@ def test_search_document_reference_invalid_category( } +@mock_aws +@mock_repository +def test_search_document_reference_filters_by_summary_count( + repository: DocumentPointerRepository, +): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_pointer = DocumentPointer.from_document_reference(doc_ref) + repository.create(doc_pointer) + + event = create_test_api_gateway_event( + headers=create_headers(), + query_string_parameters={ + "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", + "_summary": "count", + }, + ) + + result = handler(event, create_mock_context()) + body = result.pop("body") + + assert result == { + "statusCode": "200", + "headers": default_response_headers(), + "isBase64Encoded": False, + } + + parsed_body = json.loads(body) + assert parsed_body == { + "resourceType": "Bundle", + "type": "searchset", + "total": 1, + "link": [ + { + "relation": "self", + "url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191&_summary=count", + } + ], + } + + @mock_aws @mock_repository def test_search_document_reference_invalid_json(repository: DocumentPointerRepository): diff --git a/api/consumer/searchPostDocumentReference/search_post_document_reference.py b/api/consumer/searchPostDocumentReference/search_post_document_reference.py index 4dc3ca4e3..58fcbda67 100644 --- a/api/consumer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/consumer/searchPostDocumentReference/search_post_document_reference.py @@ -87,6 +87,9 @@ def handler( if body.category: self_link += f"&category={body.category.root}" + if body.field_summary: + self_link += f"&_summary={body.field_summary.root}" + bundle = { "resourceType": "Bundle", "type": "searchset", @@ -102,6 +105,28 @@ def handler( pointer_types=pointer_types, ) + if body.field_summary and body.field_summary.root == "count": + bundle = { + "resourceType": "Bundle", + "type": "searchset", + "link": [{"relation": "self", "url": self_link}], + "total": 0, + } + logger.log(LogReference.CONSEARCH006) + total = sum( + 1 + for _ in repository.search( + nhs_number=body.nhs_number, + custodian=custodian_id, + pointer_types=pointer_types, + categories=categories, + ) + ) + bundle["total"] = total + response = Response.from_resource(Bundle.model_validate(bundle)) + logger.log(LogReference.CONSEARCH999) + return response + for result in repository.search( nhs_number=body.nhs_number, custodian=custodian_id, diff --git a/api/consumer/searchPostDocumentReference/tests/test_search_post_document_reference_consumer.py b/api/consumer/searchPostDocumentReference/tests/test_search_post_document_reference_consumer.py index 4a181fdc7..896831dc8 100644 --- a/api/consumer/searchPostDocumentReference/tests/test_search_post_document_reference_consumer.py +++ b/api/consumer/searchPostDocumentReference/tests/test_search_post_document_reference_consumer.py @@ -478,6 +478,48 @@ def test_search_document_reference_invalid_category( } +@mock_aws +@mock_repository +def test_search_document_reference_filters_by_summary_count( + repository: DocumentPointerRepository, +): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_pointer = DocumentPointer.from_document_reference(doc_ref) + repository.create(doc_pointer) + + event = create_test_api_gateway_event( + headers=create_headers(), + body=json.dumps( + { + "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", + "_summary": "count", + } + ), + ) + + result = handler(event, create_mock_context()) + body = result.pop("body") + + assert result == { + "statusCode": "200", + "headers": default_response_headers(), + "isBase64Encoded": False, + } + + parsed_body = json.loads(body) + assert parsed_body == { + "resourceType": "Bundle", + "type": "searchset", + "total": 1, + "link": [ + { + "relation": "self", + "url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191&_summary=count", + } + ], + } + + @mock_aws @mock_repository def test_search_post_document_reference_invalid_json_adds_operation_outcome( diff --git a/api/consumer/swagger.yaml b/api/consumer/swagger.yaml index b37540e26..e75f2fc47 100644 --- a/api/consumer/swagger.yaml +++ b/api/consumer/swagger.yaml @@ -611,39 +611,6 @@ paths: description: | Read a single document pointer by specifying the `DocumentReference.id`. Note that you will only be able to retrieve document pointers that have the `type` that was agreed during the [onboarding](#api-description__onboarding) process. - /DocumentReference/_count: - get: - tags: - - DocumentReference - summary: Count document pointers - operationId: countDocumentReference - parameters: - - $ref: "#/components/parameters/subject" - - $ref: "#/components/parameters/odsCode" - - $ref: "#/components/parameters/odsCodeExtension" - - $ref: "#/components/parameters/requestId" - - $ref: "#/components/parameters/correlationId" - responses: - "200": - description: Count DocumentReference operation successful - headers: - X-Correlation-Id: - $ref: "#/components/headers/CorrelationId" - X-Request-Id: - $ref: "#/components/headers/RequestId" - content: - application/fhir+json: - schema: - $ref: "#/components/schemas/Bundle" - x-amazon-apigateway-integration: - type: aws_proxy - httpMethod: POST - uri: ${method_countDocumentReference} - responses: - default: - statusCode: "200" - passthroughBehavior: when_no_match - contentHandling: CONVERT_TO_TEXT /_status: get: tags: @@ -1475,6 +1442,8 @@ components: $ref: "#/components/schemas/RequestQueryType" category: $ref: "#/components/schemas/RequestQueryCategory" + _summary: + $ref: "#/components/schemas/RequestQuerySummary" next-page-token: $ref: "#/components/schemas/NextPageToken" required: @@ -1500,6 +1469,9 @@ components: RequestQueryCategory: type: string example: "http://snomed.info/sct|103693007" + RequestQuerySummary: + type: string + example: "count" NextPageToken: type: string RequestHeaderOdsCode: @@ -1637,6 +1609,18 @@ components: invalid: summary: Unknown value: http://snomed.info/sct|410970009 + _summary: + name: summary + in: query + schema: + $ref: "#/components/schemas/RequestQuerySummary" + examples: + none: + summary: None + value: "" + SNOMED_CODES_CARE_PLAN: + summary: Search only, just returns a count of the matching resources, without returning the actual matches + value: count nextPageToken: name: next-page-token description: | diff --git a/api/producer/searchDocumentReference/search_document_reference.py b/api/producer/searchDocumentReference/search_document_reference.py index 190e2d93a..0167a1c40 100644 --- a/api/producer/searchDocumentReference/search_document_reference.py +++ b/api/producer/searchDocumentReference/search_document_reference.py @@ -86,6 +86,24 @@ def handler( categories=params.category.root.split(",") if params.category else [], ) + if params.field_summary and params.field_summary.root == "count": + logger.log(LogReference.PROSEARCH006) + bundle = {"resourceType": "Bundle", "type": "searchset", "total": 0} + total = sum( + 1 + for _ in repository.search( + custodian=metadata.ods_code, + custodian_suffix=metadata.ods_code_extension, + nhs_number=params.nhs_number, + pointer_types=pointer_types, + categories=categories, + ) + ) + bundle["total"] = total + response = Response.from_resource(Bundle.model_validate(bundle)) + logger.log(LogReference.PROSEARCH999) + return response + for result in repository.search( custodian=metadata.ods_code, custodian_suffix=metadata.ods_code_extension, diff --git a/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py b/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py index 92de1ef8e..c6137d7b5 100644 --- a/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py +++ b/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py @@ -455,6 +455,40 @@ def test_search_document_reference_filters_by_pointer_types( } +@mock_aws +@mock_repository +def test_search_document_reference_filters_by_summary_count( + repository: DocumentPointerRepository, +): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_pointer = DocumentPointer.from_document_reference(doc_ref) + repository.create(doc_pointer) + + event = create_test_api_gateway_event( + headers=create_headers(), + query_string_parameters={ + "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", + "_summary": "count", + }, + ) + + result = handler(event, create_mock_context()) + body = result.pop("body") + + assert result == { + "statusCode": "200", + "headers": default_response_headers(), + "isBase64Encoded": False, + } + + parsed_body = json.loads(body) + assert parsed_body == { + "resourceType": "Bundle", + "type": "searchset", + "total": 1, + } + + @mock_aws @mock_repository def test_search_document_reference_invalid_json(repository: DocumentPointerRepository): diff --git a/api/producer/searchPostDocumentReference/search_post_document_reference.py b/api/producer/searchPostDocumentReference/search_post_document_reference.py index 45324c018..c0e335883 100644 --- a/api/producer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/producer/searchPostDocumentReference/search_post_document_reference.py @@ -79,6 +79,22 @@ def handler( pointer_types=pointer_types, categories=categories, ) + if body.field_summary and body.field_summary.root == "count": + logger.log(LogReference.PROPOSTSEARCH006) + total = sum( + 1 + for _ in repository.search( + custodian=metadata.ods_code, + custodian_suffix=metadata.ods_code_extension, + nhs_number=body.nhs_number, + pointer_types=pointer_types, + categories=categories, + ) + ) + bundle["total"] = total + response = Response.from_resource(Bundle.model_validate(bundle)) + logger.log(LogReference.PROPOSTSEARCH999) + return response for result in repository.search( custodian=metadata.ods_code, diff --git a/api/producer/swagger.yaml b/api/producer/swagger.yaml index 3d44a8f7e..ad807bd9f 100644 --- a/api/producer/swagger.yaml +++ b/api/producer/swagger.yaml @@ -2091,6 +2091,8 @@ components: $ref: "#/components/schemas/RequestQueryType" category: $ref: "#/components/schemas/RequestQueryCategory" + _summary: + $ref: "#/components/schemas/RequestQuerySummary" next-page-token: $ref: "#/components/schemas/NextPageToken" RequestQuerySubject: @@ -2103,6 +2105,9 @@ components: RequestQueryCategory: type: string example: "http://snomed.info/sct|103693007" + RequestQuerySummary: + type: string + example: "count" NextPageToken: type: string RequestHeaderOdsCode: @@ -2224,6 +2229,18 @@ components: invalid: summary: Unknown value: http://snomed.info/sct|410970009 + _summary: + name: summary + in: query + schema: + $ref: "#/components/schemas/RequestQuerySummary" + examples: + none: + summary: None + value: "" + SNOMED_CODES_CARE_PLAN: + summary: Search only, just returns a count of the matching resources, without returning the actual matches + value: count nextPageToken: name: next-page-token in: query diff --git a/layer/nrlf/consumer/fhir/r4/model.py b/layer/nrlf/consumer/fhir/r4/model.py index 965bbf7f0..259f14efb 100644 --- a/layer/nrlf/consumer/fhir/r4/model.py +++ b/layer/nrlf/consumer/fhir/r4/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-05-01T01:11:14+00:00 +# timestamp: 2025-06-10T22:58:27+00:00 from __future__ import annotations @@ -246,19 +246,11 @@ class NRLFormatCode(Coding): Field(description="The system URL for the NRLF Format Code."), ] code: Annotated[ - Literal[ - "urn:nhs-ic:record-contact", - "urn:nhs-ic:unstructured", - "urn:nhs-ic:structured", - ], + Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"], Field(description="The code representing the format of the document."), ] display: Annotated[ - Literal[ - "Contact details (HTTP Unsecured)", - "Unstructured Document", - "Structured Document", - ], + Literal["Contact details (HTTP Unsecured)", "Unstructured Document"], Field(description="The display text for the code."), ] @@ -434,6 +426,10 @@ class RequestQueryCategory(RootModel[str]): root: Annotated[str, Field(examples=["http://snomed.info/sct|103693007"])] +class RequestQuerySummary(RootModel[str]): + root: Annotated[str, Field(examples=["count"])] + + class NextPageToken(RootModel[str]): root: str @@ -510,6 +506,9 @@ class RequestParams(Parent): ] = None type: Optional[RequestQueryType] = None category: Optional[RequestQueryCategory] = None + field_summary: Annotated[Optional[RequestQuerySummary], Field(alias="_summary")] = ( + None + ) next_page_token: Annotated[ Optional[NextPageToken], Field(alias="next-page-token") ] = None diff --git a/layer/nrlf/core/log_references.py b/layer/nrlf/core/log_references.py index 5c812c66a..753b9820b 100644 --- a/layer/nrlf/core/log_references.py +++ b/layer/nrlf/core/log_references.py @@ -184,6 +184,7 @@ class LogReference(Enum): CONSEARCH004 = _Reference( "DEBUG", "Parsed DocumentReference and added to search results" ) + CONSEARCH006 = _Reference("DEBUG", "Search with summary count parameter enabled") CONSEARCH005 = _Reference( "EXCEPTION", "The DocumentReference resource could not be parsed" ) @@ -208,6 +209,9 @@ class LogReference(Enum): CONPOSTSEARCH004 = _Reference( "DEBUG", "Parsed DocumentReference and added to search results" ) + CONPOSTSEARCH006 = _Reference( + "DEBUG", "Search with summary count parameter enabled" + ) CONPOSTSEARCH005 = _Reference( "EXCEPTION", "The DocumentReference resource could not be parsed" ) @@ -364,6 +368,7 @@ class LogReference(Enum): PROSEARCH005 = _Reference( "EXCEPTION", "The DocumentReference resource could not be parsed" ) + PROSEARCH006 = _Reference("DEBUG", "Search with summary count parameter enabled") PROSEARCH999 = _Reference( "INFO", "Successfully completed producer searchDocumentReference" ) @@ -388,6 +393,9 @@ class LogReference(Enum): PROPOSTSEARCH005 = _Reference( "EXCEPTION", "The DocumentReference resource could not be parsed" ) + PROPOSTSEARCH006 = _Reference( + "DEBUG", "Search with summary count parameter enabled" + ) PROPOSTSEARCH999 = _Reference( "INFO", "Successfully completed producer searchDocumentReference" ) diff --git a/layer/nrlf/producer/fhir/r4/model.py b/layer/nrlf/producer/fhir/r4/model.py index 514792e24..1e3bdb0fe 100644 --- a/layer/nrlf/producer/fhir/r4/model.py +++ b/layer/nrlf/producer/fhir/r4/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-05-22T18:53:21+00:00 +# timestamp: 2025-06-10T22:58:21+00:00 from __future__ import annotations @@ -290,19 +290,11 @@ class NRLFormatCode(Coding): Field(description="The system URL for the NRLF Format Code."), ] code: Annotated[ - Literal[ - "urn:nhs-ic:record-contact", - "urn:nhs-ic:unstructured", - "urn:nhs-ic:structured", - ], + Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"], Field(description="The code representing the format of the document."), ] display: Annotated[ - Literal[ - "Contact details (HTTP Unsecured)", - "Unstructured Document", - "Structured Document", - ], + Literal["Contact details (HTTP Unsecured)", "Unstructured Document"], Field(description="The display text for the code."), ] @@ -468,6 +460,10 @@ class RequestQueryCategory(RootModel[str]): root: Annotated[str, Field(examples=["http://snomed.info/sct|103693007"])] +class RequestQuerySummary(RootModel[str]): + root: Annotated[str, Field(examples=["count"])] + + class NextPageToken(RootModel[str]): root: str @@ -559,6 +555,9 @@ class RequestParams(Parent): ] = None type: Optional[RequestQueryType] = None category: Optional[RequestQueryCategory] = None + field_summary: Annotated[Optional[RequestQuerySummary], Field(alias="_summary")] = ( + None + ) next_page_token: Annotated[ Optional[NextPageToken], Field(alias="next-page-token") ] = None diff --git a/layer/nrlf/producer/fhir/r4/strict_model.py b/layer/nrlf/producer/fhir/r4/strict_model.py index e7081e66b..00f45242e 100644 --- a/layer/nrlf/producer/fhir/r4/strict_model.py +++ b/layer/nrlf/producer/fhir/r4/strict_model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-05-22T18:53:21+00:00 +# timestamp: 2025-06-10T22:58:24+00:00 from __future__ import annotations @@ -261,19 +261,11 @@ class NRLFormatCode(Coding): Field(description="The system URL for the NRLF Format Code."), ] code: Annotated[ - Literal[ - "urn:nhs-ic:record-contact", - "urn:nhs-ic:unstructured", - "urn:nhs-ic:structured", - ], + Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"], Field(description="The code representing the format of the document."), ] display: Annotated[ - Literal[ - "Contact details (HTTP Unsecured)", - "Unstructured Document", - "Structured Document", - ], + Literal["Contact details (HTTP Unsecured)", "Unstructured Document"], Field(description="The display text for the code."), ] @@ -416,6 +408,10 @@ class RequestQueryCategory(RootModel[StrictStr]): root: Annotated[StrictStr, Field(examples=["http://snomed.info/sct|103693007"])] +class RequestQuerySummary(RootModel[StrictStr]): + root: Annotated[StrictStr, Field(examples=["count"])] + + class NextPageToken(RootModel[StrictStr]): root: StrictStr @@ -496,6 +492,9 @@ class RequestParams(Parent): ] = None type: Optional[RequestQueryType] = None category: Optional[RequestQueryCategory] = None + field_summary: Annotated[Optional[RequestQuerySummary], Field(alias="_summary")] = ( + None + ) next_page_token: Annotated[ Optional[NextPageToken], Field(alias="next-page-token") ] = None diff --git a/terraform/infrastructure/lambda.tf b/terraform/infrastructure/lambda.tf index 4658b4f78..f7e396f55 100644 --- a/terraform/infrastructure/lambda.tf +++ b/terraform/infrastructure/lambda.tf @@ -25,33 +25,6 @@ module "consumer__readDocumentReference" { retention = var.log_retention_period } -module "consumer__countDocumentReference" { - source = "./modules/lambda" - parent_path = "api/consumer" - name = "countDocumentReference" - region = local.region - prefix = local.prefix - layers = [module.nrlf.layer_arn, module.third_party.layer_arn, module.nrlf_permissions.layer_arn] - api_gateway_source_arn = ["arn:aws:execute-api:${local.region}:${local.aws_account_id}:${module.consumer__gateway.api_gateway_id}/*/GET/DocumentReference/_count"] - kms_key_id = module.kms__cloudwatch.kms_arn - environment_variables = { - PREFIX = "${local.prefix}--" - ENVIRONMENT = local.environment - AUTH_STORE = local.auth_store_id - POWERTOOLS_LOG_LEVEL = local.log_level - SPLUNK_INDEX = module.firehose__processor.splunk.index - TABLE_NAME = local.pointers_table_name - } - additional_policies = [ - local.pointers_table_read_policy_arn, - local.pointers_kms_read_write_arn, - local.auth_store_read_policy_arn - ] - firehose_subscriptions = local.firehose_lambda_subscriptions - handler = "count_document_reference.handler" - retention = var.log_retention_period -} - module "consumer__searchDocumentReference" { source = "./modules/lambda" parent_path = "api/consumer" diff --git a/tests/features/consumer/countDocumentReference-failure.feature b/tests/features/consumer/countDocumentReference-failure.feature deleted file mode 100644 index 1278f968f..000000000 --- a/tests/features/consumer/countDocumentReference-failure.feature +++ /dev/null @@ -1,104 +0,0 @@ -Feature: Consumer - countDocumentReference - Failure Scenarios - - Scenario: No query parameters provided - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - | http://snomed.info/sct | 736253002 | - When consumer 'RX898' counts DocumentReferences with parameters: - | parameter | value | - Then the response status code is 400 - And the response is an OperationOutcome with 1 issue - And the OperationOutcome contains the issue: - """ - { - "severity": "error", - "code": "invalid", - "details": { - "coding": [{ - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "INVALID_PARAMETER", - "display": "Invalid parameter" - }] - }, - "diagnostics": "Invalid query parameter (subject:identifier: Field required)", - "expression": ["subject:identifier"] - } - """ - - Scenario: Invalid NHS number provided - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - | http://snomed.info/sct | 736253002 | - When consumer 'RX898' counts DocumentReferences with parameters: - | parameter | value | - | subject:identifier | https://fhir.nhs.uk/Id/nhs-number\|123 | - Then the response status code is 400 - And the response is an OperationOutcome with 1 issue - And the OperationOutcome contains the issue: - """ - { - "severity": "error", - "code": "invalid", - "details": { - "coding": [{ - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "INVALID_IDENTIFIER_VALUE", - "display": "Invalid identifier value" - }] - }, - "diagnostics": "Invalid NHS number provided in the query parameters", - "expression": ["subject:identifier"] - } - """ - - Scenario: Organisation has no permissions - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - When consumer 'RX898' counts DocumentReferences with parameters: - | parameter | value | - | subject:identifier | https://fhir.nhs.uk/Id/nhs-number\|999999999 | - Then the response status code is 403 - And the response is an OperationOutcome with 1 issue - And the OperationOutcome contains the issue: - """ - { - "severity": "error", - "code": "forbidden", - "details": { - "coding": [{ - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "ACCESS DENIED", - "display": "Access has been denied to process this request" - }] - }, - "diagnostics": "Your organisation 'RX898' does not have permission to access this resource. Contact the onboarding team." - } - """ - - Scenario: Organisation has no permissions in S3 - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types - | system | value | - When consumer 'RX898' counts DocumentReferences with parameters: - | parameter | value | - | subject:identifier | https://fhir.nhs.uk/Id/nhs-number\|999999999 | - Then the response status code is 403 - And the response is an OperationOutcome with 1 issue - And the OperationOutcome contains the issue: - """ - { - "severity": "error", - "code": "forbidden", - "details": { - "coding": [{ - "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", - "code": "ACCESS DENIED", - "display": "Access has been denied to process this request" - }] - }, - "diagnostics": "Your organisation 'RX898' does not have permission to access this resource. Contact the onboarding team." - } - """ diff --git a/tests/features/consumer/countDocumentReference-success.feature b/tests/features/consumer/countDocumentReference-success.feature deleted file mode 100644 index 7ce0d5bd6..000000000 --- a/tests/features/consumer/countDocumentReference-success.feature +++ /dev/null @@ -1,96 +0,0 @@ -Feature: Consumer - countDocumentReference - Success Scenarios - - Scenario: Single pointer found for patient - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - | http://snomed.info/sct | 736253002 | - And a DocumentReference resource exists with values: - | property | value | - | id | 8FW23-1114567890-CountDocRefTest | - | subject | 9278693472 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc.pdf | - | custodian | 8FW23 | - | author | 8FW23 | - When consumer 'RX898' counts DocumentReferences with parameters: - | parameter | value | - | subject:identifier | https://fhir.nhs.uk/Id/nhs-number\|9278693472 | - Then the response status code is 200 - And the response is a searchset Bundle - And the Bundle has a total of 1 - And the response does not contain the key 'entry' - - Scenario: No pointers found for patient - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - | http://snomed.info/sct | 736253002 | - And a DocumentReference resource exists with values: - | property | value | - | id | 8FW23-1114567890-CountNoPointers | - | subject | 9999999999 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc.pdf | - | custodian | 8FW23 | - | author | 8FW23 | - When consumer 'RX898' counts DocumentReferences with parameters: - | parameter | value | - | subject:identifier | https://fhir.nhs.uk/Id/nhs-number\|9995001624 | - Then the response status code is 200 - And the response is a searchset Bundle - And the Bundle has a total of 0 - And the response does not contain the key 'entry' - - Scenario: Multiple pointers found for patient - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - | http://snomed.info/sct | 736253002 | - | http://snomed.info/sct | 887701000000100 | - And a DocumentReference resource exists with values: - | property | value | - | id | 8FW23-1114567890-CountMultiple | - | subject | 9278693472 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc.pdf | - | custodian | 8FW23 | - | author | 8FW23 | - And a DocumentReference resource exists with values: - | property | value | - | id | 8FW23-1114567890-CountMultiple2 | - | subject | 9278693472 | - | status | current | - | type | 887701000000100 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc2.pdf | - | custodian | 8FW23 | - | author | 8FW23 | - And a DocumentReference resource exists with values: - | property | value | - | id | 8FW23-1114567890-CountMultiple3 | - | subject | 9278693472 | - | status | current | - | type | 887701000000100 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc3.pdf | - | custodian | 8FW23 | - | author | 8FW23 | - When consumer 'RX898' counts DocumentReferences with parameters: - | parameter | value | - | subject:identifier | https://fhir.nhs.uk/Id/nhs-number\|9278693472 | - Then the response status code is 200 - And the response is a searchset Bundle - And the Bundle has a total of 3 - And the response does not contain the key 'entry' diff --git a/tests/features/consumer/searchDocumentReference-success.feature b/tests/features/consumer/searchDocumentReference-success.feature index b72b00a61..a35fa3273 100644 --- a/tests/features/consumer/searchDocumentReference-success.feature +++ b/tests/features/consumer/searchDocumentReference-success.feature @@ -422,6 +422,43 @@ Feature: Consumer - searchDocumentReference - Success Scenarios And the Bundle does not contain a DocumentReference with ID '02V-1111111111-SearchMultipleRefTest1' And the Bundle does not contain a DocumentReference with ID '02V-1111111111-SearchMultipleRefTest3' + Scenario: Search for DocumentReference by NHS number and Summary + Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API + And the organisation 'RX898' is authorised to access pointer types: + | system | value | + | http://snomed.info/sct | 736253002 | + | http://snomed.info/sct | 1363501000000100 | + And a DocumentReference resource exists with values: + | property | value | + | id | 02V-1111111111-SearchMultipleRefTest1 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-1.pdf | + | custodian | 02V | + | author | 02V | + And a DocumentReference resource exists with values: + | property | value | + | id | 02V-1111111111-SearchMultipleRefTest3 | + | subject | 9278693472 | + | status | current | + | type | 1363501000000100 | + | category | 1102421000000108 | + | contentType | application/pdf | + | url | https://example.org/my-doc-3.pdf | + | custodian | 02V | + | author | 02V | + When consumer 'RX898' searches for DocumentReferences with parameters: + | parameter | value | + | subject | 9278693472 | + | _summary | count | + Then the response status code is 200 + And the response is a searchset Bundle + And the Bundle has a self link matching 'DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9278693472&_summary=count' + And the Bundle has a total of 2 + Scenario: Search for multiple DocumentReferences by NHS number and Multiple Categories Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API And the organisation 'RX898' is authorised to access pointer types: diff --git a/tests/features/consumer/searchPostDocumentReference-success.feature b/tests/features/consumer/searchPostDocumentReference-success.feature index d080e8f93..c8fcecaa5 100644 --- a/tests/features/consumer/searchPostDocumentReference-success.feature +++ b/tests/features/consumer/searchPostDocumentReference-success.feature @@ -314,6 +314,53 @@ Feature: Consumer - searchDocumentReference - Success Scenarios | author | X26 | And the Bundle does not contain a DocumentReference with ID 'x26-1111111111-SearchMultipleRefTest3' + Scenario: Search for multiple DocumentReferences by NHS number and Summary + Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API + And the organisation 'RX898' is authorised to access pointer types: + | system | value | + | http://snomed.info/sct | 736253002 | + | http://snomed.info/sct | 1363501000000100 | + And a DocumentReference resource exists with values: + | property | value | + | id | X26-1111111111-SearchMultipleRefTest1 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-1.pdf | + | custodian | X26 | + | author | X26 | + And a DocumentReference resource exists with values: + | property | value | + | id | X26-1111111111-SearchMultipleRefTest2 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-2.pdf | + | custodian | X26 | + | author | X26 | + And a DocumentReference resource exists with values: + | property | value | + | id | x26-1111111111-SearchMultipleRefTest3 | + | subject | 9278693472 | + | status | current | + | type | 1363501000000100 | + | category | 1102421000000108 | + | contentType | application/pdf | + | url | https://example.org/my-doc-3.pdf | + | custodian | x26 | + | author | x26 | + When consumer 'RX898' searches for DocumentReferences using POST with request body: + | key | value | + | subject | 9278693472 | + | _summary | count | + Then the response status code is 200 + And the response is a searchset Bundle + And the Bundle has a total of 3 + Scenario: Search for multiple DocumentReferences by NHS number and Multiple Categories Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API And the organisation 'RX898' is authorised to access pointer types: diff --git a/tests/features/producer/searchDocumentReference-success.feature b/tests/features/producer/searchDocumentReference-success.feature index 2fe5e31df..637cdcbba 100644 --- a/tests/features/producer/searchDocumentReference-success.feature +++ b/tests/features/producer/searchDocumentReference-success.feature @@ -107,3 +107,40 @@ Feature: Producer - searchDocumentReference - Success Scenarios | author | X26 | And the Bundle does not contain a DocumentReference with ID 'RX898-1111111111-SearchNHSDocRefTest1' And the Bundle does not contain a DocumentReference with ID 'RX898-1111111111-SearchNHSDocRefTest3' + + Scenario: Search for DocumentReference by NHS number and Summary + Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API + And the organisation 'RX898' is authorised to access pointer types: + | system | value | + | http://snomed.info/sct | 736253002 | + | http://snomed.info/sct | 1363501000000100 | + And a DocumentReference resource exists with values: + | property | value | + | id | 02V-1111111111-SearchMultipleRefTest1 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-1.pdf | + | custodian | 02V | + | author | 02V | + And a DocumentReference resource exists with values: + | property | value | + | id | 02V-1111111111-SearchMultipleRefTest3 | + | subject | 9278693472 | + | status | current | + | type | 1363501000000100 | + | category | 1102421000000108 | + | contentType | application/pdf | + | url | https://example.org/my-doc-3.pdf | + | custodian | 02V | + | author | 02V | + When producer 'RX898' searches for DocumentReferences with parameters: + | parameter | value | + | subject | 9278693472 | + | _summary | count | + Then the response status code is 200 + And the response is a searchset Bundle + And the Bundle has a self link matching 'DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9278693472&_summary=count' + And the Bundle has a total of 2 diff --git a/tests/features/producer/searchPostDocumentReference-success.feature b/tests/features/producer/searchPostDocumentReference-success.feature index e69de29bb..be5ab35ed 100644 --- a/tests/features/producer/searchPostDocumentReference-success.feature +++ b/tests/features/producer/searchPostDocumentReference-success.feature @@ -0,0 +1,48 @@ +Feature: Producer - searchPostDocumentReference - Success Scenarios + + Scenario: Search for multiple DocumentReferences by NHS number and Summary + Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API + And the organisation 'RX898' is authorised to access pointer types: + | system | value | + | http://snomed.info/sct | 736253002 | + | http://snomed.info/sct | 1363501000000100 | + And a DocumentReference resource exists with values: + | property | value | + | id | X26-1111111111-SearchMultipleRefTest1 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-1.pdf | + | custodian | X26 | + | author | X26 | + And a DocumentReference resource exists with values: + | property | value | + | id | X26-1111111111-SearchMultipleRefTest2 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-2.pdf | + | custodian | X26 | + | author | X26 | + And a DocumentReference resource exists with values: + | property | value | + | id | x26-1111111111-SearchMultipleRefTest3 | + | subject | 9278693472 | + | status | current | + | type | 1363501000000100 | + | category | 1102421000000108 | + | contentType | application/pdf | + | url | https://example.org/my-doc-3.pdf | + | custodian | x26 | + | author | x26 | + When producer 'RX898' searches for DocumentReferences using POST with request body: + | key | value | + | subject | 9278693472 | + | _summary | count | + Then the response status code is 200 + And the response is a searchset Bundle + And the Bundle has a total of 3 From 6dec8065847b828477fc40888311868a968c46f6 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 13 Jun 2025 14:35:23 +0100 Subject: [PATCH 2/9] NRL-497 update producer models --- layer/nrlf/producer/fhir/r4/model.py | 14 +++++++++++--- layer/nrlf/producer/fhir/r4/strict_model.py | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/layer/nrlf/producer/fhir/r4/model.py b/layer/nrlf/producer/fhir/r4/model.py index 1e3bdb0fe..0dec5e19e 100644 --- a/layer/nrlf/producer/fhir/r4/model.py +++ b/layer/nrlf/producer/fhir/r4/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-06-10T22:58:21+00:00 +# timestamp: 2025-06-13T14:34:21+00:00 from __future__ import annotations @@ -290,11 +290,19 @@ class NRLFormatCode(Coding): Field(description="The system URL for the NRLF Format Code."), ] code: Annotated[ - Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"], + Literal[ + "Contact details (HTTP Unsecured)", + "Unstructured Document", + "Structured Document", + ], Field(description="The code representing the format of the document."), ] display: Annotated[ - Literal["Contact details (HTTP Unsecured)", "Unstructured Document"], + Literal[ + "Contact details (HTTP Unsecured)", + "Unstructured Document", + "Structured Document", + ], Field(description="The display text for the code."), ] diff --git a/layer/nrlf/producer/fhir/r4/strict_model.py b/layer/nrlf/producer/fhir/r4/strict_model.py index 00f45242e..b1c891b29 100644 --- a/layer/nrlf/producer/fhir/r4/strict_model.py +++ b/layer/nrlf/producer/fhir/r4/strict_model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-06-10T22:58:24+00:00 +# timestamp: 2025-06-13T14:34:21+00:00 from __future__ import annotations @@ -261,11 +261,19 @@ class NRLFormatCode(Coding): Field(description="The system URL for the NRLF Format Code."), ] code: Annotated[ - Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"], + Literal[ + "Contact details (HTTP Unsecured)", + "Unstructured Document", + "Structured Document", + ], Field(description="The code representing the format of the document."), ] display: Annotated[ - Literal["Contact details (HTTP Unsecured)", "Unstructured Document"], + Literal[ + "Contact details (HTTP Unsecured)", + "Unstructured Document", + "Structured Document", + ], Field(description="The display text for the code."), ] From 6954abbc4342561bea63501ea959c66f325fd486 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 13 Jun 2025 14:44:28 +0100 Subject: [PATCH 3/9] NRL-497 update consumer model --- layer/nrlf/consumer/fhir/r4/model.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/layer/nrlf/consumer/fhir/r4/model.py b/layer/nrlf/consumer/fhir/r4/model.py index 259f14efb..6a3c0ca63 100644 --- a/layer/nrlf/consumer/fhir/r4/model.py +++ b/layer/nrlf/consumer/fhir/r4/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-06-10T22:58:27+00:00 +# timestamp: 2025-06-13T14:43:32+00:00 from __future__ import annotations @@ -246,11 +246,19 @@ class NRLFormatCode(Coding): Field(description="The system URL for the NRLF Format Code."), ] code: Annotated[ - Literal["urn:nhs-ic:record-contact", "urn:nhs-ic:unstructured"], + Literal[ + "Contact details (HTTP Unsecured)", + "Unstructured Document", + "Structured Document", + ], Field(description="The code representing the format of the document."), ] display: Annotated[ - Literal["Contact details (HTTP Unsecured)", "Unstructured Document"], + Literal[ + "Contact details (HTTP Unsecured)", + "Unstructured Document", + "Structured Document", + ], Field(description="The display text for the code."), ] From e3cadc82a94428dad6eb74e3c4450d6735fa59eb Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 13 Jun 2025 14:55:17 +0100 Subject: [PATCH 4/9] NRL-497 revert models --- layer/nrlf/consumer/fhir/r4/model.py | 6 +++--- layer/nrlf/producer/fhir/r4/model.py | 6 +++--- layer/nrlf/producer/fhir/r4/strict_model.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/layer/nrlf/consumer/fhir/r4/model.py b/layer/nrlf/consumer/fhir/r4/model.py index 6a3c0ca63..898ddfe53 100644 --- a/layer/nrlf/consumer/fhir/r4/model.py +++ b/layer/nrlf/consumer/fhir/r4/model.py @@ -247,9 +247,9 @@ class NRLFormatCode(Coding): ] code: Annotated[ Literal[ - "Contact details (HTTP Unsecured)", - "Unstructured Document", - "Structured Document", + "urn:nhs-ic:record-contact", + "urn:nhs-ic:unstructured", + "urn:nhs-ic:structured", ], Field(description="The code representing the format of the document."), ] diff --git a/layer/nrlf/producer/fhir/r4/model.py b/layer/nrlf/producer/fhir/r4/model.py index 0dec5e19e..344c48862 100644 --- a/layer/nrlf/producer/fhir/r4/model.py +++ b/layer/nrlf/producer/fhir/r4/model.py @@ -291,9 +291,9 @@ class NRLFormatCode(Coding): ] code: Annotated[ Literal[ - "Contact details (HTTP Unsecured)", - "Unstructured Document", - "Structured Document", + "urn:nhs-ic:record-contact", + "urn:nhs-ic:unstructured", + "urn:nhs-ic:structured", ], Field(description="The code representing the format of the document."), ] diff --git a/layer/nrlf/producer/fhir/r4/strict_model.py b/layer/nrlf/producer/fhir/r4/strict_model.py index b1c891b29..cdf0cc53a 100644 --- a/layer/nrlf/producer/fhir/r4/strict_model.py +++ b/layer/nrlf/producer/fhir/r4/strict_model.py @@ -262,9 +262,9 @@ class NRLFormatCode(Coding): ] code: Annotated[ Literal[ - "Contact details (HTTP Unsecured)", - "Unstructured Document", - "Structured Document", + "urn:nhs-ic:record-contact", + "urn:nhs-ic:unstructured", + "urn:nhs-ic:structured", ], Field(description="The code representing the format of the document."), ] From 39364ff96ea352f7d1af985eb4aa37765c874aa5 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 13 Jun 2025 15:14:20 +0100 Subject: [PATCH 5/9] NRL-497 remove test --- ...earchPostDocumentReference-success.feature | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/tests/features/producer/searchPostDocumentReference-success.feature b/tests/features/producer/searchPostDocumentReference-success.feature index be5ab35ed..e69de29bb 100644 --- a/tests/features/producer/searchPostDocumentReference-success.feature +++ b/tests/features/producer/searchPostDocumentReference-success.feature @@ -1,48 +0,0 @@ -Feature: Producer - searchPostDocumentReference - Success Scenarios - - Scenario: Search for multiple DocumentReferences by NHS number and Summary - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - | http://snomed.info/sct | 736253002 | - | http://snomed.info/sct | 1363501000000100 | - And a DocumentReference resource exists with values: - | property | value | - | id | X26-1111111111-SearchMultipleRefTest1 | - | subject | 9278693472 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc-1.pdf | - | custodian | X26 | - | author | X26 | - And a DocumentReference resource exists with values: - | property | value | - | id | X26-1111111111-SearchMultipleRefTest2 | - | subject | 9278693472 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc-2.pdf | - | custodian | X26 | - | author | X26 | - And a DocumentReference resource exists with values: - | property | value | - | id | x26-1111111111-SearchMultipleRefTest3 | - | subject | 9278693472 | - | status | current | - | type | 1363501000000100 | - | category | 1102421000000108 | - | contentType | application/pdf | - | url | https://example.org/my-doc-3.pdf | - | custodian | x26 | - | author | x26 | - When producer 'RX898' searches for DocumentReferences using POST with request body: - | key | value | - | subject | 9278693472 | - | _summary | count | - Then the response status code is 200 - And the response is a searchset Bundle - And the Bundle has a total of 3 From 3ad161c228873be91cf2fdef7c2537a58f849b4d Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 13 Jun 2025 15:32:15 +0100 Subject: [PATCH 6/9] NRL-497 update author custodian --- .../searchDocumentReference-success.feature | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tests/features/producer/searchDocumentReference-success.feature b/tests/features/producer/searchDocumentReference-success.feature index 637cdcbba..cb84843db 100644 --- a/tests/features/producer/searchDocumentReference-success.feature +++ b/tests/features/producer/searchDocumentReference-success.feature @@ -115,32 +115,31 @@ Feature: Producer - searchDocumentReference - Success Scenarios | http://snomed.info/sct | 736253002 | | http://snomed.info/sct | 1363501000000100 | And a DocumentReference resource exists with values: - | property | value | - | id | 02V-1111111111-SearchMultipleRefTest1 | - | subject | 9278693472 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc-1.pdf | - | custodian | 02V | - | author | 02V | + | property | value | + | id | RX898-1111111111-SearchMultipleRefTest1 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-1.pdf | + | custodian | RX898 | + | author | X26 | And a DocumentReference resource exists with values: - | property | value | - | id | 02V-1111111111-SearchMultipleRefTest3 | - | subject | 9278693472 | - | status | current | - | type | 1363501000000100 | - | category | 1102421000000108 | - | contentType | application/pdf | - | url | https://example.org/my-doc-3.pdf | - | custodian | 02V | - | author | 02V | + | property | value | + | id | RX898-1111111111-SearchMultipleRefTest3 | + | subject | 9278693472 | + | status | current | + | type | 1363501000000100 | + | category | 1102421000000108 | + | contentType | application/pdf | + | url | https://example.org/my-doc-3.pdf | + | custodian | RX898 | + | author | X26 | When producer 'RX898' searches for DocumentReferences with parameters: | parameter | value | | subject | 9278693472 | | _summary | count | Then the response status code is 200 And the response is a searchset Bundle - And the Bundle has a self link matching 'DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9278693472&_summary=count' And the Bundle has a total of 2 From 4a9a5d396a0b27baaa630985ca7b4b1d49e1fff3 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 13 Jun 2025 16:26:07 +0100 Subject: [PATCH 7/9] NRL-497 remove count from smoke and perf tests --- tests/performance/consumer/baseline.js | 11 ----------- tests/performance/consumer/client.js | 16 ---------------- tests/performance/consumer/soak.js | 12 ------------ tests/performance/consumer/stress.js | 9 --------- .../scenarios/consumer_count_search_read.py | 8 -------- tests/utilities/api_clients.py | 9 --------- 6 files changed, 65 deletions(-) diff --git a/tests/performance/consumer/baseline.js b/tests/performance/consumer/baseline.js index ffa06c58a..7123a7145 100644 --- a/tests/performance/consumer/baseline.js +++ b/tests/performance/consumer/baseline.js @@ -8,17 +8,6 @@ export const options = { }, ], scenarios: { - countDocumentReference: { - exec: "countDocumentReference", - executor: "ramping-arrival-rate", - startRate: 1, - timeUnit: "1s", - preAllocatedVUs: 5, - stages: [ - { target: 5, duration: "30s" }, - { target: 5, duration: "1m" }, - ], - }, readDocumentReference: { exec: "readDocumentReference", executor: "ramping-arrival-rate", diff --git a/tests/performance/consumer/client.js b/tests/performance/consumer/client.js index 7f5ac6031..7145ad10f 100644 --- a/tests/performance/consumer/client.js +++ b/tests/performance/consumer/client.js @@ -34,22 +34,6 @@ function checkResponse(res) { } } -export function countDocumentReference() { - const choice = Math.floor(Math.random() * NHS_NUMBERS.length); - const nhsNumber = NHS_NUMBERS[choice]; - - const identifier = encodeURIComponent( - `https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}` - ); - const res = http.get( - `https://${__ENV.HOST}/consumer/DocumentReference/_count?subject:identifier=${identifier}`, - { - headers: getHeaders(), - } - ); - checkResponse(res); -} - export function readDocumentReference() { const choice = Math.floor(Math.random() * POINTER_IDS.length); const id = POINTER_IDS[choice]; diff --git a/tests/performance/consumer/soak.js b/tests/performance/consumer/soak.js index 34495d153..f0b5e558f 100644 --- a/tests/performance/consumer/soak.js +++ b/tests/performance/consumer/soak.js @@ -8,18 +8,6 @@ export const options = { }, ], scenarios: { - countDocumentReference: { - exec: "countDocumentReference", - executor: "ramping-arrival-rate", - startRate: 0, - timeUnit: "1s", - preAllocatedVUs: 5, - stages: [ - { target: 10, duration: "5m" }, - { target: 10, duration: "30m" }, - { target: 0, duration: "1m" }, - ], - }, readDocumentReference: { exec: "readDocumentReference", executor: "ramping-arrival-rate", diff --git a/tests/performance/consumer/stress.js b/tests/performance/consumer/stress.js index 1ee5a7df0..94a124c51 100644 --- a/tests/performance/consumer/stress.js +++ b/tests/performance/consumer/stress.js @@ -8,15 +8,6 @@ export const options = { }, ], scenarios: { - countDocumentReference: { - exec: "countDocumentReference", - executor: "ramping-vus", - startVUs: 1, - stages: [ - { target: 10, duration: "30s" }, - { target: 10, duration: "1m" }, - ], - }, readDocumentReference: { exec: "readDocumentReference", executor: "ramping-vus", diff --git a/tests/smoke/scenarios/consumer_count_search_read.py b/tests/smoke/scenarios/consumer_count_search_read.py index 4f4e75a7a..17f94b3d6 100644 --- a/tests/smoke/scenarios/consumer_count_search_read.py +++ b/tests/smoke/scenarios/consumer_count_search_read.py @@ -2,7 +2,6 @@ import pytest -from nrlf.core.constants import NHS_NUMBER_SYSTEM_URL from tests.smoke.environment import SmokeTestParameters from tests.smoke.setup import build_document_reference, upsert_test_pointer from tests.utilities.api_clients import ConsumerTestClient, ProducerTestClient @@ -46,13 +45,6 @@ def test_consumer_count_search_read( patient_id = test_data["patient_nhs_number"] test_pointers = test_data["pointers"] - # Count - count_response = consumer_client.count( - {"subject:identifier": f"{NHS_NUMBER_SYSTEM_URL}|{patient_id}"} - ) - assert count_response.ok - assert count_response.json()["total"] >= len(test_pointers) - # Search search_response = consumer_client.search(patient_id) assert search_response.ok diff --git a/tests/utilities/api_clients.py b/tests/utilities/api_clients.py index 21b91f9c5..52ba39ed1 100644 --- a/tests/utilities/api_clients.py +++ b/tests/utilities/api_clients.py @@ -100,15 +100,6 @@ def read(self, doc_ref_id: str) -> Response: cert=self.config.client_cert, ) - @retry_if([502]) - def count(self, params: dict[str, str]) -> Response: - return requests.get( - f"{self.api_url}/DocumentReference/_count", - params=params, - headers=self.request_headers, - cert=self.config.client_cert, - ) - @retry_if([502]) def search( self, From c055a09ccf8cb38b0843258ac3a45a7d67f405aa Mon Sep 17 00:00:00 2001 From: "Axel Garcia K." Date: Thu, 26 Jun 2025 17:01:33 +0100 Subject: [PATCH 8/9] NRL-497 Count endpoint using dynamoDB count, remove producer count, add performance tests --- .../search_document_reference.py | 13 +++---- .../search_post_document_reference.py | 17 ++++----- .../search_document_reference.py | 18 ---------- ...test_search_document_reference_producer.py | 34 ------------------ .../search_post_document_reference.py | 16 --------- api/producer/swagger.yaml | 17 --------- layer/nrlf/core/log_references.py | 12 +++++-- layer/nrlf/producer/fhir/r4/model.py | 9 +---- layer/nrlf/producer/fhir/r4/strict_model.py | 9 +---- .../searchDocumentReference-success.feature | 36 ------------------- tests/performance/consumer/baseline.js | 22 ++++++++++++ tests/performance/consumer/client.js | 33 +++++++++++++++++ tests/performance/consumer/soak.js | 24 +++++++++++++ tests/performance/consumer/stress.js | 18 ++++++++++ 14 files changed, 120 insertions(+), 158 deletions(-) diff --git a/api/consumer/searchDocumentReference/search_document_reference.py b/api/consumer/searchDocumentReference/search_document_reference.py index b69518011..58b55af37 100644 --- a/api/consumer/searchDocumentReference/search_document_reference.py +++ b/api/consumer/searchDocumentReference/search_document_reference.py @@ -113,16 +113,13 @@ def handler( "total": 0, } logger.log(LogReference.CONSEARCH006) - total = sum( - 1 - for _ in repository.search( - nhs_number=params.nhs_number, - custodian=custodian_id, - pointer_types=pointer_types, - categories=categories, - ) + + total = repository.count_by_nhs_number( + nhs_number=params.nhs_number, + pointer_types=pointer_types, ) bundle["total"] = total + logger.log(LogReference.CONSEARCH007, total=total) response = Response.from_resource(Bundle.model_validate(bundle)) logger.log(LogReference.CONSEARCH999) return response diff --git a/api/consumer/searchPostDocumentReference/search_post_document_reference.py b/api/consumer/searchPostDocumentReference/search_post_document_reference.py index 58fcbda67..fd970111a 100644 --- a/api/consumer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/consumer/searchPostDocumentReference/search_post_document_reference.py @@ -112,19 +112,16 @@ def handler( "link": [{"relation": "self", "url": self_link}], "total": 0, } - logger.log(LogReference.CONSEARCH006) - total = sum( - 1 - for _ in repository.search( - nhs_number=body.nhs_number, - custodian=custodian_id, - pointer_types=pointer_types, - categories=categories, - ) + logger.log(LogReference.CONPOSTSEARCH006) + + total = repository.count_by_nhs_number( + nhs_number=body.nhs_number, + pointer_types=pointer_types, ) bundle["total"] = total + logger.log(LogReference.CONPOSTSEARCH007, total=total) response = Response.from_resource(Bundle.model_validate(bundle)) - logger.log(LogReference.CONSEARCH999) + logger.log(LogReference.CONPOSTSEARCH999) return response for result in repository.search( diff --git a/api/producer/searchDocumentReference/search_document_reference.py b/api/producer/searchDocumentReference/search_document_reference.py index 0167a1c40..190e2d93a 100644 --- a/api/producer/searchDocumentReference/search_document_reference.py +++ b/api/producer/searchDocumentReference/search_document_reference.py @@ -86,24 +86,6 @@ def handler( categories=params.category.root.split(",") if params.category else [], ) - if params.field_summary and params.field_summary.root == "count": - logger.log(LogReference.PROSEARCH006) - bundle = {"resourceType": "Bundle", "type": "searchset", "total": 0} - total = sum( - 1 - for _ in repository.search( - custodian=metadata.ods_code, - custodian_suffix=metadata.ods_code_extension, - nhs_number=params.nhs_number, - pointer_types=pointer_types, - categories=categories, - ) - ) - bundle["total"] = total - response = Response.from_resource(Bundle.model_validate(bundle)) - logger.log(LogReference.PROSEARCH999) - return response - for result in repository.search( custodian=metadata.ods_code, custodian_suffix=metadata.ods_code_extension, diff --git a/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py b/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py index c6137d7b5..92de1ef8e 100644 --- a/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py +++ b/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py @@ -455,40 +455,6 @@ def test_search_document_reference_filters_by_pointer_types( } -@mock_aws -@mock_repository -def test_search_document_reference_filters_by_summary_count( - repository: DocumentPointerRepository, -): - doc_ref = load_document_reference("Y05868-736253002-Valid") - doc_pointer = DocumentPointer.from_document_reference(doc_ref) - repository.create(doc_pointer) - - event = create_test_api_gateway_event( - headers=create_headers(), - query_string_parameters={ - "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", - "_summary": "count", - }, - ) - - result = handler(event, create_mock_context()) - body = result.pop("body") - - assert result == { - "statusCode": "200", - "headers": default_response_headers(), - "isBase64Encoded": False, - } - - parsed_body = json.loads(body) - assert parsed_body == { - "resourceType": "Bundle", - "type": "searchset", - "total": 1, - } - - @mock_aws @mock_repository def test_search_document_reference_invalid_json(repository: DocumentPointerRepository): diff --git a/api/producer/searchPostDocumentReference/search_post_document_reference.py b/api/producer/searchPostDocumentReference/search_post_document_reference.py index c0e335883..45324c018 100644 --- a/api/producer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/producer/searchPostDocumentReference/search_post_document_reference.py @@ -79,22 +79,6 @@ def handler( pointer_types=pointer_types, categories=categories, ) - if body.field_summary and body.field_summary.root == "count": - logger.log(LogReference.PROPOSTSEARCH006) - total = sum( - 1 - for _ in repository.search( - custodian=metadata.ods_code, - custodian_suffix=metadata.ods_code_extension, - nhs_number=body.nhs_number, - pointer_types=pointer_types, - categories=categories, - ) - ) - bundle["total"] = total - response = Response.from_resource(Bundle.model_validate(bundle)) - logger.log(LogReference.PROPOSTSEARCH999) - return response for result in repository.search( custodian=metadata.ods_code, diff --git a/api/producer/swagger.yaml b/api/producer/swagger.yaml index ad807bd9f..3d44a8f7e 100644 --- a/api/producer/swagger.yaml +++ b/api/producer/swagger.yaml @@ -2091,8 +2091,6 @@ components: $ref: "#/components/schemas/RequestQueryType" category: $ref: "#/components/schemas/RequestQueryCategory" - _summary: - $ref: "#/components/schemas/RequestQuerySummary" next-page-token: $ref: "#/components/schemas/NextPageToken" RequestQuerySubject: @@ -2105,9 +2103,6 @@ components: RequestQueryCategory: type: string example: "http://snomed.info/sct|103693007" - RequestQuerySummary: - type: string - example: "count" NextPageToken: type: string RequestHeaderOdsCode: @@ -2229,18 +2224,6 @@ components: invalid: summary: Unknown value: http://snomed.info/sct|410970009 - _summary: - name: summary - in: query - schema: - $ref: "#/components/schemas/RequestQuerySummary" - examples: - none: - summary: None - value: "" - SNOMED_CODES_CARE_PLAN: - summary: Search only, just returns a count of the matching resources, without returning the actual matches - value: count nextPageToken: name: next-page-token in: query diff --git a/layer/nrlf/core/log_references.py b/layer/nrlf/core/log_references.py index 753b9820b..8954c1a5d 100644 --- a/layer/nrlf/core/log_references.py +++ b/layer/nrlf/core/log_references.py @@ -184,10 +184,13 @@ class LogReference(Enum): CONSEARCH004 = _Reference( "DEBUG", "Parsed DocumentReference and added to search results" ) - CONSEARCH006 = _Reference("DEBUG", "Search with summary count parameter enabled") CONSEARCH005 = _Reference( "EXCEPTION", "The DocumentReference resource could not be parsed" ) + CONSEARCH006 = _Reference("DEBUG", "Search with summary count parameter enabled") + CONSEARCH007 = _Reference( + "INFO", "Search with summary count parameter returned total results" + ) CONSEARCH999 = _Reference( "INFO", "Successfully completed consumer searchDocumentReference" ) @@ -209,11 +212,14 @@ class LogReference(Enum): CONPOSTSEARCH004 = _Reference( "DEBUG", "Parsed DocumentReference and added to search results" ) + CONPOSTSEARCH005 = _Reference( + "EXCEPTION", "The DocumentReference resource could not be parsed" + ) CONPOSTSEARCH006 = _Reference( "DEBUG", "Search with summary count parameter enabled" ) - CONPOSTSEARCH005 = _Reference( - "EXCEPTION", "The DocumentReference resource could not be parsed" + CONPOSTSEARCH007 = _Reference( + "INFO", "Search with summary count parameter returned total results" ) CONPOSTSEARCH999 = _Reference( "INFO", "Successfully completed consumer searchPostDocumentReference" diff --git a/layer/nrlf/producer/fhir/r4/model.py b/layer/nrlf/producer/fhir/r4/model.py index 344c48862..514792e24 100644 --- a/layer/nrlf/producer/fhir/r4/model.py +++ b/layer/nrlf/producer/fhir/r4/model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-06-13T14:34:21+00:00 +# timestamp: 2025-05-22T18:53:21+00:00 from __future__ import annotations @@ -468,10 +468,6 @@ class RequestQueryCategory(RootModel[str]): root: Annotated[str, Field(examples=["http://snomed.info/sct|103693007"])] -class RequestQuerySummary(RootModel[str]): - root: Annotated[str, Field(examples=["count"])] - - class NextPageToken(RootModel[str]): root: str @@ -563,9 +559,6 @@ class RequestParams(Parent): ] = None type: Optional[RequestQueryType] = None category: Optional[RequestQueryCategory] = None - field_summary: Annotated[Optional[RequestQuerySummary], Field(alias="_summary")] = ( - None - ) next_page_token: Annotated[ Optional[NextPageToken], Field(alias="next-page-token") ] = None diff --git a/layer/nrlf/producer/fhir/r4/strict_model.py b/layer/nrlf/producer/fhir/r4/strict_model.py index cdf0cc53a..e7081e66b 100644 --- a/layer/nrlf/producer/fhir/r4/strict_model.py +++ b/layer/nrlf/producer/fhir/r4/strict_model.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: swagger.yaml -# timestamp: 2025-06-13T14:34:21+00:00 +# timestamp: 2025-05-22T18:53:21+00:00 from __future__ import annotations @@ -416,10 +416,6 @@ class RequestQueryCategory(RootModel[StrictStr]): root: Annotated[StrictStr, Field(examples=["http://snomed.info/sct|103693007"])] -class RequestQuerySummary(RootModel[StrictStr]): - root: Annotated[StrictStr, Field(examples=["count"])] - - class NextPageToken(RootModel[StrictStr]): root: StrictStr @@ -500,9 +496,6 @@ class RequestParams(Parent): ] = None type: Optional[RequestQueryType] = None category: Optional[RequestQueryCategory] = None - field_summary: Annotated[Optional[RequestQuerySummary], Field(alias="_summary")] = ( - None - ) next_page_token: Annotated[ Optional[NextPageToken], Field(alias="next-page-token") ] = None diff --git a/tests/features/producer/searchDocumentReference-success.feature b/tests/features/producer/searchDocumentReference-success.feature index cb84843db..2fe5e31df 100644 --- a/tests/features/producer/searchDocumentReference-success.feature +++ b/tests/features/producer/searchDocumentReference-success.feature @@ -107,39 +107,3 @@ Feature: Producer - searchDocumentReference - Success Scenarios | author | X26 | And the Bundle does not contain a DocumentReference with ID 'RX898-1111111111-SearchNHSDocRefTest1' And the Bundle does not contain a DocumentReference with ID 'RX898-1111111111-SearchNHSDocRefTest3' - - Scenario: Search for DocumentReference by NHS number and Summary - Given the application 'DataShare' (ID 'z00z-y11y-x22x') is registered to access the API - And the organisation 'RX898' is authorised to access pointer types: - | system | value | - | http://snomed.info/sct | 736253002 | - | http://snomed.info/sct | 1363501000000100 | - And a DocumentReference resource exists with values: - | property | value | - | id | RX898-1111111111-SearchMultipleRefTest1 | - | subject | 9278693472 | - | status | current | - | type | 736253002 | - | category | 734163000 | - | contentType | application/pdf | - | url | https://example.org/my-doc-1.pdf | - | custodian | RX898 | - | author | X26 | - And a DocumentReference resource exists with values: - | property | value | - | id | RX898-1111111111-SearchMultipleRefTest3 | - | subject | 9278693472 | - | status | current | - | type | 1363501000000100 | - | category | 1102421000000108 | - | contentType | application/pdf | - | url | https://example.org/my-doc-3.pdf | - | custodian | RX898 | - | author | X26 | - When producer 'RX898' searches for DocumentReferences with parameters: - | parameter | value | - | subject | 9278693472 | - | _summary | count | - Then the response status code is 200 - And the response is a searchset Bundle - And the Bundle has a total of 2 diff --git a/tests/performance/consumer/baseline.js b/tests/performance/consumer/baseline.js index 7123a7145..fe720d8eb 100644 --- a/tests/performance/consumer/baseline.js +++ b/tests/performance/consumer/baseline.js @@ -8,6 +8,28 @@ export const options = { }, ], scenarios: { + countDocumentReference: { + exec: "countDocumentReference", + executor: "ramping-arrival-rate", + startRate: 1, + timeUnit: "1s", + preAllocatedVUs: 5, + stages: [ + { target: 5, duration: "30s" }, + { target: 5, duration: "1m" }, + ], + }, + countPostDocumentReference: { + exec: "countPostDocumentReference", + executor: "ramping-arrival-rate", + startRate: 1, + timeUnit: "1s", + preAllocatedVUs: 5, + stages: [ + { target: 5, duration: "30s" }, + { target: 5, duration: "1m" }, + ], + }, readDocumentReference: { exec: "readDocumentReference", executor: "ramping-arrival-rate", diff --git a/tests/performance/consumer/client.js b/tests/performance/consumer/client.js index 7145ad10f..d8bc3cbd2 100644 --- a/tests/performance/consumer/client.js +++ b/tests/performance/consumer/client.js @@ -34,6 +34,22 @@ function checkResponse(res) { } } +export function countDocumentReference() { + const choice = Math.floor(Math.random() * NHS_NUMBERS.length); + const nhsNumber = NHS_NUMBERS[choice]; + + const identifier = encodeURIComponent( + `https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}` + ); + const res = http.get( + `https://${__ENV.HOST}/consumer/DocumentReference?_summary=count&subject:identifier=${identifier}`, + { + headers: getHeaders(), + } + ); + checkResponse(res); +} + export function readDocumentReference() { const choice = Math.floor(Math.random() * POINTER_IDS.length); const id = POINTER_IDS[choice]; @@ -126,3 +142,20 @@ export function searchPostDocumentReferenceByCategory() { ); checkResponse(res); } + +export function countPostDocumentReference() { + const choice = Math.floor(Math.random() * NHS_NUMBERS.length); + const nhsNumber = NHS_NUMBERS[choice]; + + const body = JSON.stringify({ + "subject:identifier": `https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}`, + }); + const res = http.post( + `https://${__ENV.HOST}/consumer/DocumentReference/_search?_summary=count`, + body, + { + headers: getHeaders(), + } + ); + checkResponse(res); +} diff --git a/tests/performance/consumer/soak.js b/tests/performance/consumer/soak.js index f0b5e558f..5004fc67b 100644 --- a/tests/performance/consumer/soak.js +++ b/tests/performance/consumer/soak.js @@ -8,6 +8,30 @@ export const options = { }, ], scenarios: { + countDocumentReference: { + exec: "countDocumentReference", + executor: "ramping-arrival-rate", + startRate: 0, + timeUnit: "1s", + preAllocatedVUs: 5, + stages: [ + { target: 10, duration: "5m" }, + { target: 10, duration: "30m" }, + { target: 0, duration: "1m" }, + ], + }, + countPostDocumentReference: { + exec: "countPostDocumentReference", + executor: "ramping-arrival-rate", + startRate: 0, + timeUnit: "1s", + preAllocatedVUs: 5, + stages: [ + { target: 10, duration: "5m" }, + { target: 10, duration: "30m" }, + { target: 0, duration: "1m" }, + ], + }, readDocumentReference: { exec: "readDocumentReference", executor: "ramping-arrival-rate", diff --git a/tests/performance/consumer/stress.js b/tests/performance/consumer/stress.js index 94a124c51..fb33a438e 100644 --- a/tests/performance/consumer/stress.js +++ b/tests/performance/consumer/stress.js @@ -8,6 +8,24 @@ export const options = { }, ], scenarios: { + countDocumentReference: { + exec: "countDocumentReference", + executor: "ramping-vus", + startVUs: 1, + stages: [ + { target: 10, duration: "30s" }, + { target: 10, duration: "1m" }, + ], + }, + countPostDocumentReference: { + exec: "countPostDocumentReference", + executor: "ramping-vus", + startVUs: 1, + stages: [ + { target: 10, duration: "30s" }, + { target: 10, duration: "1m" }, + ], + }, readDocumentReference: { exec: "readDocumentReference", executor: "ramping-vus", From 55898b8c0ebbc18dabf6d10dd31eaa1aa878a8c0 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Thu, 3 Jul 2025 15:55:43 +0100 Subject: [PATCH 9/9] NRL-753 fix apply --- .../infrastructure/modules/api_gateway/api_gateway.tf | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/terraform/infrastructure/modules/api_gateway/api_gateway.tf b/terraform/infrastructure/modules/api_gateway/api_gateway.tf index 40dca31cf..1c2b39888 100644 --- a/terraform/infrastructure/modules/api_gateway/api_gateway.tf +++ b/terraform/infrastructure/modules/api_gateway/api_gateway.tf @@ -16,6 +16,9 @@ resource "aws_api_gateway_method" "capability" { resource_id = aws_api_gateway_resource.capability.id http_method = "GET" authorization = "NONE" + request_parameters = { + "method.request.header.Authorization" = false + } } resource "aws_api_gateway_integration" "capability" { @@ -60,7 +63,12 @@ resource "aws_api_gateway_deployment" "api_gateway_deployment" { } depends_on = [ - aws_api_gateway_rest_api.api_gateway_rest_api + aws_api_gateway_rest_api.api_gateway_rest_api, + aws_api_gateway_resource.capability, + aws_api_gateway_method.capability, + aws_api_gateway_integration.capability, + aws_api_gateway_method_response.capability_200, + aws_api_gateway_integration_response.capability ] }