From d6376e754b0b6192067c54703388f5a3780de711 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 22 Nov 2024 02:37:02 +0000 Subject: [PATCH 1/4] NRL-1075 split category by list for multi category search --- .../searchDocumentReference/search_document_reference.py | 2 +- .../search_post_document_reference.py | 2 +- .../searchDocumentReference/search_document_reference.py | 4 ++-- .../search_post_document_reference.py | 4 ++-- layer/nrlf/core/validators.py | 4 +++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/api/consumer/searchDocumentReference/search_document_reference.py b/api/consumer/searchDocumentReference/search_document_reference.py index b420c7132..3638274f2 100644 --- a/api/consumer/searchDocumentReference/search_document_reference.py +++ b/api/consumer/searchDocumentReference/search_document_reference.py @@ -102,7 +102,7 @@ def handler( nhs_number=params.nhs_number, custodian=custodian_id, pointer_types=pointer_types, - categories=[params.category.root] if params.category else [], + categories=params.category.root.split(",") if params.category else [], ): try: document_reference = DocumentReference.model_validate_json(result.document) diff --git a/api/consumer/searchPostDocumentReference/search_post_document_reference.py b/api/consumer/searchPostDocumentReference/search_post_document_reference.py index 18ac8e606..c6c89033c 100644 --- a/api/consumer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/consumer/searchPostDocumentReference/search_post_document_reference.py @@ -105,7 +105,7 @@ def handler( nhs_number=body.nhs_number, custodian=custodian_id, pointer_types=pointer_types, - categories=[body.category.root] if body.category else [], + categories=body.category.root.split(",") if body.category else [], ): try: document_reference = DocumentReference.model_validate_json(result.document) diff --git a/api/producer/searchDocumentReference/search_document_reference.py b/api/producer/searchDocumentReference/search_document_reference.py index d2dad8fd5..c931f1fb5 100644 --- a/api/producer/searchDocumentReference/search_document_reference.py +++ b/api/producer/searchDocumentReference/search_document_reference.py @@ -78,7 +78,7 @@ def handler( custodian_suffix=metadata.ods_code_extension, nhs_number=params.nhs_number, pointer_types=pointer_types, - categories=[params.category.root] if params.category else [], + categories=params.category.root.split(",") if params.category else [], ) for result in repository.search( @@ -86,7 +86,7 @@ def handler( custodian_suffix=metadata.ods_code_extension, nhs_number=params.nhs_number, pointer_types=pointer_types, - categories=[params.category.root] if params.category else [], + categories=params.category.root.split(",") if params.category else [], ): try: document_reference = DocumentReference.model_validate_json(result.document) diff --git a/api/producer/searchPostDocumentReference/search_post_document_reference.py b/api/producer/searchPostDocumentReference/search_post_document_reference.py index 12c1144e1..b6d1cd264 100644 --- a/api/producer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/producer/searchPostDocumentReference/search_post_document_reference.py @@ -72,7 +72,7 @@ def handler( custodian_suffix=metadata.ods_code_extension, nhs_number=body.nhs_number, pointer_types=pointer_types, - categories=[body.category.root] if body.category else [], + categories=body.category.root.split(",") if body.category else [], ) for result in repository.search( @@ -80,7 +80,7 @@ def handler( custodian_suffix=metadata.ods_code_extension, nhs_number=body.nhs_number, pointer_types=pointer_types, - categories=[body.category.root] if body.category else [], + categories=body.category.root.split(",") if body.category else [], ): try: document_reference = DocumentReference.model_validate_json(result.document) diff --git a/layer/nrlf/core/validators.py b/layer/nrlf/core/validators.py index 45627ccc7..b061a16fd 100644 --- a/layer/nrlf/core/validators.py +++ b/layer/nrlf/core/validators.py @@ -43,7 +43,9 @@ def validate_category(category_: Optional[RequestQueryCategory]) -> bool: if not category_: return True - return category_.root in Categories.list() + categories = category_.root.split(",") + + return all(category in Categories.list() for category in categories) @dataclass From 22e931b21dbaa8cd2e5252d82cdd68ae111246ca Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 22 Nov 2024 02:46:28 +0000 Subject: [PATCH 2/4] NRL-1075 less duplication --- .../searchDocumentReference/search_document_reference.py | 5 +++-- .../search_post_document_reference.py | 7 ++++--- .../searchDocumentReference/search_document_reference.py | 5 +++-- .../search_post_document_reference.py | 9 +++++---- layer/nrlf/core/validators.py | 6 ++---- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/api/consumer/searchDocumentReference/search_document_reference.py b/api/consumer/searchDocumentReference/search_document_reference.py index 3638274f2..aee43c7ec 100644 --- a/api/consumer/searchDocumentReference/search_document_reference.py +++ b/api/consumer/searchDocumentReference/search_document_reference.py @@ -58,7 +58,8 @@ def handler( expression="type", ) - if not validate_category(params.category): + categories = params.category.root.split(",") if params.category else [] + if not validate_category(categories): logger.log( LogReference.CONSEARCH002b, category=params.category, @@ -102,7 +103,7 @@ def handler( nhs_number=params.nhs_number, custodian=custodian_id, pointer_types=pointer_types, - categories=params.category.root.split(",") if params.category else [], + categories=categories, ): try: document_reference = DocumentReference.model_validate_json(result.document) diff --git a/api/consumer/searchPostDocumentReference/search_post_document_reference.py b/api/consumer/searchPostDocumentReference/search_post_document_reference.py index c6c89033c..39964f608 100644 --- a/api/consumer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/consumer/searchPostDocumentReference/search_post_document_reference.py @@ -61,10 +61,11 @@ def handler( expression="type", ) - if not validate_category(body.category): + categories = body.category.root.split(",") if body.category else [] + if not validate_category(categories): logger.log( LogReference.CONPOSTSEARCH002b, - type=body.category, + category=body.category, ) # TODO - Should update error message once permissioning by category is implemented return SpineErrorResponse.INVALID_CODE_SYSTEM( diagnostics="The provided category is not valid", @@ -105,7 +106,7 @@ def handler( nhs_number=body.nhs_number, custodian=custodian_id, pointer_types=pointer_types, - categories=body.category.root.split(",") if body.category else [], + categories=categories, ): try: document_reference = DocumentReference.model_validate_json(result.document) diff --git a/api/producer/searchDocumentReference/search_document_reference.py b/api/producer/searchDocumentReference/search_document_reference.py index c931f1fb5..403d22b8e 100644 --- a/api/producer/searchDocumentReference/search_document_reference.py +++ b/api/producer/searchDocumentReference/search_document_reference.py @@ -59,10 +59,11 @@ def handler( expression="type", ) - if not validate_category(params.category): + categories = params.category.root.split(",") if params.category else [] + if not validate_category(categories): logger.log( LogReference.PROSEARCH002b, - type=params.category, + category=params.category, ) # TODO - Should update error message once permissioning by category is implemented return SpineErrorResponse.INVALID_CODE_SYSTEM( diagnostics="Invalid query parameter (The provided category is not valid)", diff --git a/api/producer/searchPostDocumentReference/search_post_document_reference.py b/api/producer/searchPostDocumentReference/search_post_document_reference.py index b6d1cd264..2c1159653 100644 --- a/api/producer/searchPostDocumentReference/search_post_document_reference.py +++ b/api/producer/searchPostDocumentReference/search_post_document_reference.py @@ -53,10 +53,11 @@ def handler( expression="type", ) - if not validate_category(body.category): + categories = body.category.root.split(",") if body.category else [] + if not validate_category(categories): logger.log( LogReference.PROPOSTSEARCH002b, - type=body.category, + category=body.category, ) # TODO - Should update error message once permissioning by category is implemented return SpineErrorResponse.INVALID_CODE_SYSTEM( diagnostics="The provided category is not valid", @@ -72,7 +73,7 @@ def handler( custodian_suffix=metadata.ods_code_extension, nhs_number=body.nhs_number, pointer_types=pointer_types, - categories=body.category.root.split(",") if body.category else [], + categories=categories, ) for result in repository.search( @@ -80,7 +81,7 @@ def handler( custodian_suffix=metadata.ods_code_extension, nhs_number=body.nhs_number, pointer_types=pointer_types, - categories=body.category.root.split(",") if body.category else [], + categories=categories, ): try: document_reference = DocumentReference.model_validate_json(result.document) diff --git a/layer/nrlf/core/validators.py b/layer/nrlf/core/validators.py index b061a16fd..09f2c6370 100644 --- a/layer/nrlf/core/validators.py +++ b/layer/nrlf/core/validators.py @@ -36,15 +36,13 @@ def validate_type_system( # TODO - Validate category is in set permissions once permissioning by category is done. -def validate_category(category_: Optional[RequestQueryCategory]) -> bool: +def validate_category(categories: Optional[RequestQueryCategory]) -> bool: """ Validates if the given category is valid. """ - if not category_: + if not categories: return True - categories = category_.root.split(",") - return all(category in Categories.list() for category in categories) From dd524465af5e51d2f699273e895c910f1bd112d5 Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 22 Nov 2024 15:39:09 +0000 Subject: [PATCH 3/4] NRL-1075 add tests --- ...test_search_document_reference_consumer.py | 77 ++++++++++++++- ...search_post_document_reference_consumer.py | 78 +++++++++++++++ ...test_search_document_reference_producer.py | 71 +++++++++++++- ...search_post_document_reference_producer.py | 73 +++++++++++++- .../searchDocumentReference-failure.feature | 28 ++++++ .../searchDocumentReference-success.feature | 95 +++++++++++++++++++ ...earchPostDocumentReference-failure.feature | 33 ++++++- ...earchPostDocumentReference-success.feature | 94 ++++++++++++++++++ 8 files changed, 543 insertions(+), 6 deletions(-) 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 1b8cd23b1..1f30aafc5 100644 --- a/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py +++ b/api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py @@ -3,6 +3,12 @@ from moto import mock_aws from api.consumer.searchDocumentReference.search_document_reference import handler +from nrlf.core.constants import ( + CATEGORY_ATTRIBUTES, + TYPE_ATTRIBUTES, + Categories, + PointerTypes, +) from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository from nrlf.tests.data import load_document_reference from nrlf.tests.dynamodb import mock_repository @@ -144,6 +150,19 @@ def test_search_document_reference_happy_path_with_category( doc_pointer = DocumentPointer.from_document_reference(doc_ref) repository.create(doc_pointer) + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + event = create_test_api_gateway_event( headers=create_headers(), query_string_parameters={ @@ -160,7 +179,6 @@ def test_search_document_reference_happy_path_with_category( "headers": default_response_headers(), "isBase64Encoded": False, } - parsed_body = json.loads(body) assert parsed_body == { "resourceType": "Bundle", @@ -176,6 +194,63 @@ def test_search_document_reference_happy_path_with_category( } +@mock_aws +@mock_repository +def test_search_document_reference_happy_path_with_multiple_categories( + repository: DocumentPointerRepository, +): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_pointer = DocumentPointer.from_document_reference(doc_ref) + repository.create(doc_pointer) + + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + + event = create_test_api_gateway_event( + headers=create_headers(), + query_string_parameters={ + "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", + "category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108", + }, + ) + + 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", + "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&category=http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108", + } + ], + "total": 2, + "entry": [ + {"resource": doc_ref2.model_dump(exclude_none=True)}, + {"resource": doc_ref.model_dump(exclude_none=True)}, + ], + } + + @mock_aws @mock_repository def test_search_document_reference_happy_path_with_nicip_type( 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 3dc41ba45..4e3ed4e4d 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 @@ -5,6 +5,12 @@ from api.consumer.searchPostDocumentReference.search_post_document_reference import ( handler, ) +from nrlf.core.constants import ( + CATEGORY_ATTRIBUTES, + TYPE_ATTRIBUTES, + Categories, + PointerTypes, +) from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository from nrlf.tests.data import load_document_reference from nrlf.tests.dynamodb import mock_repository @@ -153,6 +159,19 @@ def test_search_post_document_reference_happy_path_with_category( doc_pointer = DocumentPointer.from_document_reference(doc_ref) repository.create(doc_pointer) + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + event = create_test_api_gateway_event( headers=create_headers(), body=json.dumps( @@ -187,6 +206,65 @@ def test_search_post_document_reference_happy_path_with_category( } +@mock_aws +@mock_repository +def test_search_post_document_reference_happy_path_with_multiple_categories( + repository: DocumentPointerRepository, +): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_pointer = DocumentPointer.from_document_reference(doc_ref) + repository.create(doc_pointer) + + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + + event = create_test_api_gateway_event( + headers=create_headers(), + body=json.dumps( + { + "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", + "category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108", + } + ), + ) + + 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", + "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&category=http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108", + } + ], + "total": 2, + "entry": [ + {"resource": doc_ref2.model_dump(exclude_none=True)}, + {"resource": doc_ref.model_dump(exclude_none=True)}, + ], + } + + @mock_aws @mock_repository def test_search_document_reference_no_results(repository: DocumentPointerRepository): 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 7f3b89165..d7a46ece7 100644 --- a/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py +++ b/api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py @@ -3,7 +3,12 @@ from moto import mock_aws from api.producer.searchDocumentReference.search_document_reference import handler -from nrlf.core.constants import Categories, PointerTypes +from nrlf.core.constants import ( + CATEGORY_ATTRIBUTES, + TYPE_ATTRIBUTES, + Categories, + PointerTypes, +) from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository from nrlf.tests.data import load_document_reference from nrlf.tests.dynamodb import mock_repository @@ -326,6 +331,19 @@ def test_search_document_reference_filters_by_category( doc_pointer = DocumentPointer.from_document_reference(doc_ref) repository.create(doc_pointer) + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + event = create_test_api_gateway_event( headers=create_headers(), query_string_parameters={ @@ -352,6 +370,57 @@ def test_search_document_reference_filters_by_category( } +@mock_aws +@mock_repository +def test_search_document_reference_filters_with_multiple_categories( + repository: DocumentPointerRepository, +): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_pointer = DocumentPointer.from_document_reference(doc_ref) + repository.create(doc_pointer) + + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + + event = create_test_api_gateway_event( + headers=create_headers(), + query_string_parameters={ + "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", + "category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108", + }, + ) + + 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": 2, + "entry": [ + {"resource": doc_ref2.model_dump(exclude_none=True)}, + {"resource": doc_ref.model_dump(exclude_none=True)}, + ], + } + + @mock_aws @mock_repository def test_search_document_reference_filters_by_pointer_types( diff --git a/api/producer/searchPostDocumentReference/tests/test_search_post_document_reference_producer.py b/api/producer/searchPostDocumentReference/tests/test_search_post_document_reference_producer.py index 66bd579a8..f17de3fb1 100644 --- a/api/producer/searchPostDocumentReference/tests/test_search_post_document_reference_producer.py +++ b/api/producer/searchPostDocumentReference/tests/test_search_post_document_reference_producer.py @@ -5,7 +5,12 @@ from api.producer.searchPostDocumentReference.search_post_document_reference import ( handler, ) -from nrlf.core.constants import Categories, PointerTypes +from nrlf.core.constants import ( + CATEGORY_ATTRIBUTES, + TYPE_ATTRIBUTES, + Categories, + PointerTypes, +) from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository from nrlf.tests.data import load_document_reference from nrlf.tests.dynamodb import mock_repository @@ -337,6 +342,19 @@ def test_search_document_reference_filters_by_category( doc_pointer = DocumentPointer.from_document_reference(doc_ref) repository.create(doc_pointer) + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + event = create_test_api_gateway_event( headers=create_headers(), body=json.dumps( @@ -365,6 +383,59 @@ def test_search_document_reference_filters_by_category( } +@mock_aws +@mock_repository +def test_search_post_document_reference_filters_with_multiple_categories( + repository: DocumentPointerRepository, +): + doc_ref = load_document_reference("Y05868-736253002-Valid") + doc_pointer = DocumentPointer.from_document_reference(doc_ref) + repository.create(doc_pointer) + + # Second pointer different category + doc_ref2 = load_document_reference("Y05868-736253002-Valid") + doc_ref2.id = "Y05868-736253002-Valid2" + doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value() + doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get( + PointerTypes.NEWS2_CHART.value + ).get("display") + doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value() + doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get( + Categories.OBSERVATIONS.value + ).get("display") + repository.create(DocumentPointer.from_document_reference(doc_ref2)) + + event = create_test_api_gateway_event( + headers=create_headers(), + body=json.dumps( + { + "subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191", + "category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108", + } + ), + ) + + 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": 2, + "entry": [ + {"resource": doc_ref2.model_dump(exclude_none=True)}, + {"resource": doc_ref.model_dump(exclude_none=True)}, + ], + } + + @mock_aws @mock_repository def test_search_document_reference_filters_by_pointer_types( diff --git a/tests/features/consumer/searchDocumentReference-failure.feature b/tests/features/consumer/searchDocumentReference-failure.feature index 8ed4300e6..1de20623a 100644 --- a/tests/features/consumer/searchDocumentReference-failure.feature +++ b/tests/features/consumer/searchDocumentReference-failure.feature @@ -219,3 +219,31 @@ Feature: Consumer - searchDocumentReference - Failure Scenarios "expression": ["category"] } """ + + Scenario: Search rejects request with multiple categories and one invalid category + 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' searches for DocumentReferences with parameters: + | parameter | value | + | subject | 9278693472 | + | category | http://snomed.info/sct\|734163000,http://snomed.info/sct\|invalid | + 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": "code-invalid", + "details": { + "coding": [{ + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_CODE_SYSTEM", + "display": "Invalid code system" + }] + }, + "diagnostics": "Invalid query parameter (The provided category is not valid)", + "expression": ["category"] + } + """ diff --git a/tests/features/consumer/searchDocumentReference-success.feature b/tests/features/consumer/searchDocumentReference-success.feature index 960dfb823..d94cbb9d9 100644 --- a/tests/features/consumer/searchDocumentReference-success.feature +++ b/tests/features/consumer/searchDocumentReference-success.feature @@ -343,6 +343,101 @@ Feature: Consumer - searchDocumentReference - Success Scenarios | author | 02V | And the Bundle does not contain a DocumentReference with ID '02V-1111111111-SearchMultipleRefTest3' + 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: + | system | value | + | http://snomed.info/sct | 736253002 | + | http://snomed.info/sct | 1363501000000100 | + | http://snomed.info/sct | 824321000000109 | + 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-SearchMultipleRefTest2 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-2.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 | + And a DocumentReference resource exists with values: + | property | value | + | id | 02V-1111111111-SearchMultipleRefTest4 | + | subject | 9278693472 | + | status | current | + | type | 824321000000109 | + | category | 823651000000106 | + | contentType | application/pdf | + | url | https://example.org/my-doc-4.pdf | + | custodian | 02V | + | author | 02V | + When consumer 'RX898' searches for DocumentReferences with parameters: + | parameter | value | + | subject | 9278693472 | + | category | http://snomed.info/sct\|734163000,http://snomed.info/sct\|823651000000106 | + 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&category=http://snomed.info/sct|734163000,http://snomed.info/sct\|823651000000106' + And the Bundle has a total of 3 + And the Bundle has 3 entries + And the Bundle contains an DocumentReference 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 the Bundle contains an DocumentReference with values + | property | value | + | id | 02V-1111111111-SearchMultipleRefTest2 | + | subject | 9278693472 | + | status | current | + | type | 736253002 | + | category | 734163000 | + | contentType | application/pdf | + | url | https://example.org/my-doc-2.pdf | + | custodian | 02V | + | author | 02V | + And the Bundle contains an DocumentReference with values + | property | value | + | id | 02V-1111111111-SearchMultipleRefTest4 | + | subject | 9278693472 | + | status | current | + | type | 824321000000109 | + | category | 823651000000106 | + | contentType | application/pdf | + | url | https://example.org/my-doc-4.pdf | + | custodian | 02V | + | author | 02V | + And the Bundle does not contain a DocumentReference with ID '02V-1111111111-SearchMultipleRefTest3' + # No pointers found - done # Pointers exist but no permissions - covered in failure scenarios # Search by custodian - done diff --git a/tests/features/consumer/searchPostDocumentReference-failure.feature b/tests/features/consumer/searchPostDocumentReference-failure.feature index 8f5b507bd..91380c764 100644 --- a/tests/features/consumer/searchPostDocumentReference-failure.feature +++ b/tests/features/consumer/searchPostDocumentReference-failure.feature @@ -195,9 +195,8 @@ Feature: Consumer - searchDocumentReference - Failure Scenarios Scenario: Search rejects request with category system they are not allowed to use 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 | + | system | value | + | http://snomed.info/sct | 736253002 | When consumer 'RX898' searches for DocumentReferences using POST with request body: | key | value | | subject | 9278693472 | @@ -220,3 +219,31 @@ Feature: Consumer - searchDocumentReference - Failure Scenarios "expression": ["category"] } """ + + Scenario: Search rejects request with multiple categories and one that is invalid + 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' searches for DocumentReferences using POST with request body: + | key | value | + | subject | 9278693472 | + | category | http://snomed.info/sct\|734163000,http://snomed.info/sct\|invalid | + 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": "code-invalid", + "details": { + "coding": [{ + "system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1", + "code": "INVALID_CODE_SYSTEM", + "display": "Invalid code system" + }] + }, + "diagnostics": "The provided category is not valid", + "expression": ["category"] + } + """ diff --git a/tests/features/consumer/searchPostDocumentReference-success.feature b/tests/features/consumer/searchPostDocumentReference-success.feature index 27d3fff18..d080e8f93 100644 --- a/tests/features/consumer/searchPostDocumentReference-success.feature +++ b/tests/features/consumer/searchPostDocumentReference-success.feature @@ -313,3 +313,97 @@ Feature: Consumer - searchDocumentReference - Success Scenarios | custodian | X26 | | author | X26 | And the Bundle does not contain a DocumentReference with ID 'x26-1111111111-SearchMultipleRefTest3' + + 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: + | system | value | + | http://snomed.info/sct | 736253002 | + | http://snomed.info/sct | 1363501000000100 | + | http://snomed.info/sct | 824321000000109 | + 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 | + And a DocumentReference resource exists with values: + | property | value | + | id | X26-1111111111-SearchMultipleRefTest4 | + | subject | 9278693472 | + | status | current | + | type | 824321000000109 | + | category | 823651000000106 | + | contentType | application/pdf | + | url | https://example.org/my-doc-4.pdf | + | custodian | X26 | + | author | X26 | + When consumer 'RX898' searches for DocumentReferences using POST with request body: + | key | value | + | subject | 9278693472 | + | category | http://snomed.info/sct\|734163000,http://snomed.info/sct\|823651000000106 | + Then the response status code is 200 + And the response is a searchset Bundle + And the Bundle has a total of 3 + And the Bundle has 3 entries + And the Bundle contains an DocumentReference 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 the Bundle contains an DocumentReference 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 the Bundle contains an DocumentReference with values + | property | value | + | id | X26-1111111111-SearchMultipleRefTest4 | + | subject | 9278693472 | + | status | current | + | type | 824321000000109 | + | category | 823651000000106 | + | contentType | application/pdf | + | url | https://example.org/my-doc-4.pdf | + | custodian | X26 | + | author | X26 | + And the Bundle does not contain a DocumentReference with ID 'x26-1111111111-SearchMultipleRefTest3' From 29e4dae17ef87fd5ac93b664d8d43fdb5e1778ca Mon Sep 17 00:00:00 2001 From: eesa456 Date: Fri, 22 Nov 2024 15:56:16 +0000 Subject: [PATCH 4/4] NRL-1075 fix test --- tests/features/consumer/searchDocumentReference-success.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/features/consumer/searchDocumentReference-success.feature b/tests/features/consumer/searchDocumentReference-success.feature index d94cbb9d9..ead47a40e 100644 --- a/tests/features/consumer/searchDocumentReference-success.feature +++ b/tests/features/consumer/searchDocumentReference-success.feature @@ -400,7 +400,7 @@ Feature: Consumer - searchDocumentReference - Success Scenarios | category | http://snomed.info/sct\|734163000,http://snomed.info/sct\|823651000000106 | 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&category=http://snomed.info/sct|734163000,http://snomed.info/sct\|823651000000106' + And the Bundle has a self link matching 'DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9278693472&category=http://snomed.info/sct|734163000,http://snomed.info/sct|823651000000106' And the Bundle has a total of 3 And the Bundle has 3 entries And the Bundle contains an DocumentReference with values