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..58b55af37 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,25 @@ 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 = 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 + 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 4f5c3333a..dde8ec0e2 100644 --- a/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py +++ b/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py @@ -719,6 +719,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 @patch("api.consumer.searchDocumentReference.search_document_reference.logger") diff --git a/api/consumer/searchPostDocumentReference/search_post_document_reference.py b/api/consumer/searchPostDocumentReference/search_post_document_reference.py index 4dc3ca4e3..fd970111a 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,25 @@ 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.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.CONPOSTSEARCH999) + 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 127a6dc12..82a604c60 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 @@ -479,6 +479,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 @patch("api.consumer.searchPostDocumentReference.search_post_document_reference.logger") 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/layer/nrlf/consumer/fhir/r4/model.py b/layer/nrlf/consumer/fhir/r4/model.py index 965bbf7f0..898ddfe53 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-13T14:43:32+00:00 from __future__ import annotations @@ -434,6 +434,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 +514,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 c071b0981..4df8a25ff 100644 --- a/layer/nrlf/core/log_references.py +++ b/layer/nrlf/core/log_references.py @@ -195,6 +195,10 @@ class LogReference(Enum): 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" ) @@ -219,6 +223,12 @@ class LogReference(Enum): CONPOSTSEARCH005 = _Reference( "EXCEPTION", "The DocumentReference resource could not be parsed" ) + CONPOSTSEARCH006 = _Reference( + "DEBUG", "Search with summary count parameter enabled" + ) + CONPOSTSEARCH007 = _Reference( + "INFO", "Search with summary count parameter returned total results" + ) CONPOSTSEARCH999 = _Reference( "INFO", "Successfully completed consumer searchPostDocumentReference" ) @@ -372,6 +382,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" ) @@ -396,6 +407,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/terraform/infrastructure/lambda.tf b/terraform/infrastructure/lambda.tf index feb2f8729..4830dd5cf 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 = local.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/performance/consumer/baseline.js b/tests/performance/consumer/baseline.js index ffa06c58a..fe720d8eb 100644 --- a/tests/performance/consumer/baseline.js +++ b/tests/performance/consumer/baseline.js @@ -19,6 +19,17 @@ export const options = { { 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 7f5ac6031..d8bc3cbd2 100644 --- a/tests/performance/consumer/client.js +++ b/tests/performance/consumer/client.js @@ -42,7 +42,7 @@ export function countDocumentReference() { `https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}` ); const res = http.get( - `https://${__ENV.HOST}/consumer/DocumentReference/_count?subject:identifier=${identifier}`, + `https://${__ENV.HOST}/consumer/DocumentReference?_summary=count&subject:identifier=${identifier}`, { headers: getHeaders(), } @@ -142,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 34495d153..5004fc67b 100644 --- a/tests/performance/consumer/soak.js +++ b/tests/performance/consumer/soak.js @@ -20,6 +20,18 @@ export const options = { { 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 1ee5a7df0..fb33a438e 100644 --- a/tests/performance/consumer/stress.js +++ b/tests/performance/consumer/stress.js @@ -17,6 +17,15 @@ export const options = { { 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", diff --git a/tests/smoke/scenarios/consumer_count_search_read.py b/tests/smoke/scenarios/consumer_search_read.py similarity index 76% rename from tests/smoke/scenarios/consumer_count_search_read.py rename to tests/smoke/scenarios/consumer_search_read.py index 4f4e75a7a..969e37b42 100644 --- a/tests/smoke/scenarios/consumer_count_search_read.py +++ b/tests/smoke/scenarios/consumer_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 @@ -17,7 +16,7 @@ def test_data( test_ods_code = smoke_test_parameters.ods_code test_pointers = [ upsert_test_pointer( - f"{test_ods_code}-smoketest_consumer_count_search_read_pointer_{n}", + f"{test_ods_code}-smoketest_consumer_search_read_pointer_{n}", docref=build_document_reference( nhs_number=test_nhs_numbers[0], custodian=test_ods_code ), @@ -37,22 +36,13 @@ def test_data( producer_client.delete(test_pointer.id) -def test_consumer_count_search_read( - consumer_client: ConsumerTestClient, test_data: dict -): +def test_consumer_search_read(consumer_client: ConsumerTestClient, test_data: dict): """ - Smoke test scenario for a consumer count, search and read behaviour + Smoke test scenario for a consumer search and read behaviour """ 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 0c21b7f47..bb522cbe3 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,