diff --git a/lambdas/tests/e2e/api/fhir/__init__.py b/lambdas/tests/e2e/api/fhir_core/__init__.py similarity index 100% rename from lambdas/tests/e2e/api/fhir/__init__.py rename to lambdas/tests/e2e/api/fhir_core/__init__.py diff --git a/lambdas/tests/e2e/api/fhir/conftest.py b/lambdas/tests/e2e/api/fhir_core/conftest.py similarity index 63% rename from lambdas/tests/e2e/api/fhir/conftest.py rename to lambdas/tests/e2e/api/fhir_core/conftest.py index 989ae5aa37..dbaebb957c 100644 --- a/lambdas/tests/e2e/api/fhir/conftest.py +++ b/lambdas/tests/e2e/api/fhir_core/conftest.py @@ -27,7 +27,7 @@ def test_data(): def fetch_with_retry_mtls( - session, url, headers, condition_func=None, max_retries=5, delay=10 + session, url, headers, condition_func=None, max_retries=5, delay=10, ): retries = 0 while retries < max_retries: @@ -47,7 +47,7 @@ def fetch_with_retry_mtls( def create_mtls_session( - client_cert_path=CLIENT_CERT_PATH, client_key_path=CLIENT_KEY_PATH + client_cert_path=CLIENT_CERT_PATH, client_key_path=CLIENT_KEY_PATH, ): session = requests.Session() session.cert = (client_cert_path, client_key_path) @@ -86,49 +86,6 @@ def temp_cert_and_key(): shutil.rmtree(temp_dir) -def get_pdm_document_reference( - record_id="", - client_cert_path=None, - client_key_path=None, - resource_type="DocumentReference", - pdm_snomed=PDM_SNOMED, - endpoint_override=None, -): - if not endpoint_override: - url = f"https://{MTLS_ENDPOINT}/{resource_type}/{pdm_snomed}~{record_id}" - else: - url = f"https://{MTLS_ENDPOINT}/{resource_type}/{endpoint_override}" - headers = { - "X-Correlation-Id": "1234", - } - - # Call with invalid or unauthorised certs - if client_cert_path and client_key_path: - session = create_mtls_session(client_cert_path, client_key_path) - else: - # Call with default valid certs - session = create_mtls_session() - - response = session.get(url, headers=headers) - return response - - -def delete_document_reference(endpoint, client_cert_path=None, client_key_path=None): - """Helper to perform a DELETE by NHS number.""" - url = f"https://{MTLS_ENDPOINT}/DocumentReference{endpoint}" - headers = { - "X-Correlation-Id": "1234", - } - - # Use provided certs if available, else defaults - if client_cert_path and client_key_path: - session = create_mtls_session(client_cert_path, client_key_path) - else: - session = create_mtls_session() - - return session.delete(url=url, headers=headers) - - def create_and_store_pdm_record( test_data, nhs_number: str = "9912003071", @@ -138,7 +95,7 @@ def create_and_store_pdm_record( ): """Helper to create metadata and resource for a record.""" record = pdm_data_helper.build_record( - nhs_number=nhs_number, doc_status=doc_status, size=size + nhs_number=nhs_number, doc_status=doc_status, size=size, ) test_data.append(record) pdm_data_helper.create_metadata(record, **dynamo_kwargs) @@ -146,15 +103,6 @@ def create_and_store_pdm_record( return record -def upload_document(payload, resource_type="DocumentReference"): - """Helper to upload DocumentReference.""" - url = f"https://{MTLS_ENDPOINT}/{resource_type}" - headers = { - "X-Correlation-Id": "1234", - } - session = create_mtls_session() - return session.post(url, headers=headers, data=payload) - def retrieve_document_with_retry(doc_id, condition): """Poll until condition is met on DocumentReference retrieval.""" diff --git a/lambdas/tests/e2e/api/fhir/files/big-dummy.pdf b/lambdas/tests/e2e/api/fhir_core/files/big-dummy.pdf similarity index 100% rename from lambdas/tests/e2e/api/fhir/files/big-dummy.pdf rename to lambdas/tests/e2e/api/fhir_core/files/big-dummy.pdf diff --git a/lambdas/tests/e2e/api/fhir/files/dummy.pdf b/lambdas/tests/e2e/api/fhir_core/files/dummy.pdf similarity index 100% rename from lambdas/tests/e2e/api/fhir/files/dummy.pdf rename to lambdas/tests/e2e/api/fhir_core/files/dummy.pdf diff --git a/lambdas/tests/e2e/api/fhir/test_delete_document_reference_fhir_api_failure.py b/lambdas/tests/e2e/api/fhir_core/test_delete_document_reference_fhir_api_failure.py similarity index 90% rename from lambdas/tests/e2e/api/fhir/test_delete_document_reference_fhir_api_failure.py rename to lambdas/tests/e2e/api/fhir_core/test_delete_document_reference_fhir_api_failure.py index 4517d7cddb..c1c01bce2d 100644 --- a/lambdas/tests/e2e/api/fhir/test_delete_document_reference_fhir_api_failure.py +++ b/lambdas/tests/e2e/api/fhir_core/test_delete_document_reference_fhir_api_failure.py @@ -1,15 +1,14 @@ import uuid -from tests.e2e.api.fhir.conftest import ( - delete_document_reference, -) + from tests.e2e.helpers.data_helper import PdmDataHelper +from tests.e2e.helpers.rest_helper import delete_document_reference pdm_data_helper = PdmDataHelper() def test_no_documents_found(test_data): response = delete_document_reference( - f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={uuid.uuid4()}" + f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={uuid.uuid4()}", ) assert response.status_code == 404 response_json = response.json() @@ -19,7 +18,7 @@ def test_no_documents_found(test_data): def test_malformatted_nhs_id(test_data): response = delete_document_reference( - f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|991200&_id={uuid.uuid4()}" + f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|991200&_id={uuid.uuid4()}", ) assert response.status_code == 400 response_json = response.json() @@ -31,7 +30,7 @@ def test_malformatted_nhs_id(test_data): def test_malformatted_document_id(test_data): response = delete_document_reference( - "?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id=1234" + "?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id=1234", ) assert response.status_code == 400 response_json = response.json() @@ -51,7 +50,7 @@ def test_no_query_params(test_data): def test_incorrect_query_params(test_data): response = delete_document_reference( - "?foo=https://fhir.nhs.uk/Id/nhs-number|9912003071" + "?foo=https://fhir.nhs.uk/Id/nhs-number|9912003071", ) assert response.status_code == 400 response_json = response.json() @@ -63,7 +62,7 @@ def test_incorrect_query_params(test_data): def test_correct_query_params_with_incorrect_params(test_data): response = delete_document_reference( - f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={uuid.uuid4()}&foo=1234" + f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={uuid.uuid4()}&foo=1234", ) assert response.status_code == 404 response_json = response.json() diff --git a/lambdas/tests/e2e/api/fhir/test_delete_document_reference_fhir_api_success.py b/lambdas/tests/e2e/api/fhir_core/test_delete_document_reference_fhir_api_success.py similarity index 93% rename from lambdas/tests/e2e/api/fhir/test_delete_document_reference_fhir_api_success.py rename to lambdas/tests/e2e/api/fhir_core/test_delete_document_reference_fhir_api_success.py index 75d7e38e3e..c6b2e6bb5e 100644 --- a/lambdas/tests/e2e/api/fhir/test_delete_document_reference_fhir_api_success.py +++ b/lambdas/tests/e2e/api/fhir_core/test_delete_document_reference_fhir_api_success.py @@ -1,9 +1,11 @@ from tests.e2e.api.fhir.conftest import ( create_and_store_pdm_record, - get_pdm_document_reference, - delete_document_reference, ) from tests.e2e.helpers.data_helper import PdmDataHelper +from tests.e2e.helpers.rest_helper import ( + delete_document_reference, + get_pdm_document_reference, +) pdm_data_helper = PdmDataHelper() @@ -16,7 +18,7 @@ def test_delete_record_by_patient_details_and_doc_id(test_data): assert get_response_1.status_code == 200 response = delete_document_reference( - f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={expected_record_id}" + f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={expected_record_id}", ) assert response.status_code == 204 @@ -38,7 +40,7 @@ def test_delete_only_one_record_by_patient_details_and_doc_id(test_data): assert get_response_2.status_code == 200 response = delete_document_reference( - f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={expected_record_id_1}" + f"?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|9912003071&_id={expected_record_id_1}", ) assert response.status_code == 204 diff --git a/lambdas/tests/e2e/api/fhir/test_mtls_routing.py b/lambdas/tests/e2e/api/fhir_core/test_mtls_routing.py similarity index 86% rename from lambdas/tests/e2e/api/fhir/test_mtls_routing.py rename to lambdas/tests/e2e/api/fhir_core/test_mtls_routing.py index 987a731916..db4f05c2c5 100644 --- a/lambdas/tests/e2e/api/fhir/test_mtls_routing.py +++ b/lambdas/tests/e2e/api/fhir_core/test_mtls_routing.py @@ -1,7 +1,7 @@ import os import uuid -from tests.e2e.api.fhir.conftest import get_pdm_document_reference +from tests.e2e.helpers.rest_helper import get_pdm_document_reference UNAUTHORISED_CLIENT_CERT_PATH = os.environ.get("UNAUTHORISED_CLIENT_CERT_PATH") UNAUTHORISED_CLIENT_KEY_PATH = os.environ.get("UNAUTHORISED_CLIENT_KEY_PATH") @@ -10,7 +10,7 @@ def test_mtls_invalid_common_name(): record_id = str(uuid.uuid4()) response = get_pdm_document_reference( - record_id, UNAUTHORISED_CLIENT_CERT_PATH, UNAUTHORISED_CLIENT_KEY_PATH + record_id, UNAUTHORISED_CLIENT_CERT_PATH, UNAUTHORISED_CLIENT_KEY_PATH, ) assert response.status_code == 400 diff --git a/lambdas/tests/e2e/api/test_retrieve_document_api.py b/lambdas/tests/e2e/api/fhir_core/test_retrieve_document_api.py similarity index 100% rename from lambdas/tests/e2e/api/test_retrieve_document_api.py rename to lambdas/tests/e2e/api/fhir_core/test_retrieve_document_api.py diff --git a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_failure.py b/lambdas/tests/e2e/api/fhir_core/test_retrieve_document_fhir_api_failure.py similarity index 98% rename from lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_failure.py rename to lambdas/tests/e2e/api/fhir_core/test_retrieve_document_fhir_api_failure.py index e1cfebe33a..5ed25fcd3a 100644 --- a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_failure.py +++ b/lambdas/tests/e2e/api/fhir_core/test_retrieve_document_fhir_api_failure.py @@ -5,9 +5,9 @@ from enums.document_retention import DocumentRetentionDays from tests.e2e.api.fhir.conftest import ( create_and_store_pdm_record, - get_pdm_document_reference, ) from tests.e2e.helpers.data_helper import PdmDataHelper +from tests.e2e.helpers.rest_helper import get_pdm_document_reference pdm_data_helper = PdmDataHelper() diff --git a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py b/lambdas/tests/e2e/api/fhir_core/test_retrieve_document_fhir_api_success.py similarity index 92% rename from lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py rename to lambdas/tests/e2e/api/fhir_core/test_retrieve_document_fhir_api_success.py index eb083012d8..03b68a17ba 100644 --- a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py +++ b/lambdas/tests/e2e/api/fhir_core/test_retrieve_document_fhir_api_success.py @@ -3,11 +3,11 @@ import pytest from tests.e2e.api.fhir.conftest import ( PDM_S3_BUCKET, - create_and_store_pdm_record, - get_pdm_document_reference, PDM_SNOMED, + create_and_store_pdm_record, ) from tests.e2e.helpers.data_helper import PdmDataHelper +from tests.e2e.helpers.rest_helper import get_pdm_document_reference pdm_data_helper = PdmDataHelper() @@ -34,7 +34,7 @@ def assert_returned_document_reference(pdm_record, response): ], ) def test_successful_retrieval_of_document_reference( - test_data, doc_status, response_status + test_data, doc_status, response_status, ): pdm_record = create_and_store_pdm_record(test_data, doc_status=doc_status) @@ -50,7 +50,7 @@ def test_successful_retrieval_of_document_reference( def test_file_retrieval(test_data, file_size): """Test retrieval for small and large files.""" pdm_record = create_and_store_pdm_record( - test_data, size=file_size if file_size else None + test_data, size=file_size if file_size else None, ) response = get_pdm_document_reference(pdm_record["id"]) assert response.status_code == 200 diff --git a/lambdas/tests/e2e/api/fhir/test_search_patient_fhir_api.py b/lambdas/tests/e2e/api/fhir_core/test_search_patient_fhir_api.py similarity index 88% rename from lambdas/tests/e2e/api/fhir/test_search_patient_fhir_api.py rename to lambdas/tests/e2e/api/fhir_core/test_search_patient_fhir_api.py index 92783b115c..d6eaba9bd5 100644 --- a/lambdas/tests/e2e/api/fhir/test_search_patient_fhir_api.py +++ b/lambdas/tests/e2e/api/fhir_core/test_search_patient_fhir_api.py @@ -3,38 +3,15 @@ import pytest from enums.document_retention import DocumentRetentionDays from tests.e2e.api.fhir.conftest import ( - MTLS_ENDPOINT, PDM_SNOMED, create_and_store_pdm_record, - create_mtls_session, ) from tests.e2e.conftest import APIM_ENDPOINT from tests.e2e.helpers.data_helper import PdmDataHelper +from tests.e2e.helpers.rest_helper import search_document_reference pdm_data_helper = PdmDataHelper() - -def search_document_reference( - nhs_number, - client_cert_path=None, - client_key_path=None, - resource_type="DocumentReference", -): - """Helper to perform search by NHS number with optional mTLS certs.""" - url = f"https://{MTLS_ENDPOINT}/{resource_type}?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{nhs_number}" - headers = { - "X-Correlation-Id": "1234", - } - - # Use provided certs if available, else defaults - if client_cert_path and client_key_path: - session = create_mtls_session(client_cert_path, client_key_path) - else: - session = create_mtls_session() - - return session.get(url, headers=headers) - - def test_search_nonexistent_document_references_for_patient_details(): response = search_document_reference("9449305943") assert response.status_code == 200 diff --git a/lambdas/tests/e2e/api/test_upload_document_api.py b/lambdas/tests/e2e/api/fhir_core/test_upload_document_api.py similarity index 100% rename from lambdas/tests/e2e/api/test_upload_document_api.py rename to lambdas/tests/e2e/api/fhir_core/test_upload_document_api.py diff --git a/lambdas/tests/e2e/api/fhir/test_upload_document_fhir_api_failure.py b/lambdas/tests/e2e/api/fhir_core/test_upload_document_fhir_api_failure.py similarity index 92% rename from lambdas/tests/e2e/api/fhir/test_upload_document_fhir_api_failure.py rename to lambdas/tests/e2e/api/fhir_core/test_upload_document_fhir_api_failure.py index 930df91133..046aaa934d 100644 --- a/lambdas/tests/e2e/api/fhir/test_upload_document_fhir_api_failure.py +++ b/lambdas/tests/e2e/api/fhir_core/test_upload_document_fhir_api_failure.py @@ -8,9 +8,9 @@ from tests.e2e.api.fhir.conftest import ( MTLS_ENDPOINT, retrieve_document_with_retry, - upload_document, ) from tests.e2e.helpers.data_helper import PdmDataHelper +from tests.e2e.helpers.rest_helper import upload_document_reference pdm_data_helper = PdmDataHelper() @@ -26,7 +26,7 @@ def test_create_document_presign_fails(): record["data"] = base64.b64encode(f.read()).decode("utf-8") payload = pdm_data_helper.create_upload_payload(record) - upload_response = upload_document(payload) + upload_response = upload_document_reference(payload) assert upload_response.status_code == 413 assert upload_response.text == "HTTP content length exceeded 10485760 bytes." @@ -44,7 +44,7 @@ def test_create_document_virus(test_data): record["data"] = base64.b64encode(eicar_string.encode()).decode() payload = pdm_data_helper.create_upload_payload(record) - raw_upload_response = upload_document(payload) + raw_upload_response = upload_document_reference(payload) assert raw_upload_response.status_code == 201 upload_response = raw_upload_response.json() record["id"] = upload_response["id"].split("~")[1] @@ -59,7 +59,7 @@ def condition(response_json): ) raw_retrieve_response = retrieve_document_with_retry( - upload_response["id"], condition + upload_response["id"], condition, ) retrieve_response = raw_retrieve_response.json() @@ -84,7 +84,7 @@ def condition(response_json): ], ) def test_search_edge_cases( - nhs_number, expected_status, expected_code, expected_diagnostics + nhs_number, expected_status, expected_code, expected_diagnostics, ): record = { "ods": "H81109", @@ -95,7 +95,7 @@ def test_search_edge_cases( record["data"] = base64.b64encode(sample_pdf_bytes).decode("utf-8") payload = pdm_data_helper.create_upload_payload(record) - response = upload_document(payload) + response = upload_document_reference(payload) assert response.status_code == expected_status body = response.json() @@ -123,7 +123,7 @@ def test_forbidden_with_invalid_cert(temp_cert_and_key): headers = {"Authorization": "Bearer 123", "X-Correlation-Id": "1234"} response = requests.post( - url, headers=headers, cert=(cert_path, key_path), data=payload + url, headers=headers, cert=(cert_path, key_path), data=payload, ) body = response.json() assert response.status_code == 403 @@ -147,7 +147,7 @@ def test_create_document_with_invalid_author_returns_error(test_data, author_pay payload["author"][0] = author_payload payload = json.dumps(payload) - raw_upload_response = upload_document(payload) + raw_upload_response = upload_document_reference(payload) response_json = raw_upload_response.json() assert raw_upload_response.status_code == 400 assert response_json["resourceType"] == "OperationOutcome" @@ -168,7 +168,7 @@ def test_upload_invalid_resource_type(test_data): payload = pdm_data_helper.create_upload_payload(record=record, return_json=True) payload = json.dumps(payload) - raw_upload_response = upload_document(payload, resource_type="FooBar") + raw_upload_response = upload_document_reference(payload, resource_type="FooBar") assert raw_upload_response.status_code == 403 response_json = raw_upload_response.json() diff --git a/lambdas/tests/e2e/api/fhir/test_upload_document_fhir_api_success.py b/lambdas/tests/e2e/api/fhir_core/test_upload_document_fhir_api_success.py similarity index 70% rename from lambdas/tests/e2e/api/fhir/test_upload_document_fhir_api_success.py rename to lambdas/tests/e2e/api/fhir_core/test_upload_document_fhir_api_success.py index dd1a7d4204..083282f373 100644 --- a/lambdas/tests/e2e/api/fhir/test_upload_document_fhir_api_success.py +++ b/lambdas/tests/e2e/api/fhir_core/test_upload_document_fhir_api_success.py @@ -1,15 +1,14 @@ import base64 -import json import logging import os from tests.e2e.api.fhir.conftest import ( PDM_SNOMED, retrieve_document_with_retry, - upload_document, ) from tests.e2e.conftest import APIM_ENDPOINT from tests.e2e.helpers.data_helper import PdmDataHelper +from tests.e2e.helpers.rest_helper import upload_document_reference pdm_data_helper = PdmDataHelper() @@ -25,7 +24,7 @@ def test_create_document_base64(test_data): record["data"] = base64.b64encode(f.read()).decode("utf-8") payload = pdm_data_helper.create_upload_payload(record) - raw_upload_response = upload_document(payload) + raw_upload_response = upload_document_reference(payload) assert raw_upload_response.status_code == 201 record["id"] = raw_upload_response.json()["id"].split("~")[1] test_data.append(record) @@ -43,8 +42,7 @@ def condition(response_json): return response_json["content"][0]["attachment"].get("data", False) raw_retrieve_response = retrieve_document_with_retry( - upload_response["id"], - condition, + upload_response["id"], condition, ) retrieve_response = raw_retrieve_response.json() @@ -64,7 +62,7 @@ def test_create_document_saves_raw(test_data): record["data"] = base64.b64encode(f.read()).decode("utf-8") payload = pdm_data_helper.create_upload_payload(record) - raw_upload_response = upload_document(payload) + raw_upload_response = upload_document_reference(payload) assert raw_upload_response.status_code == 201 record["id"] = raw_upload_response.json()["id"].split("~")[1] test_data.append(record) @@ -87,13 +85,12 @@ def test_create_document_without_author_or_type(test_data): with open(sample_pdf_path, "rb") as f: record["data"] = base64.b64encode(f.read()).decode("utf-8") payload = pdm_data_helper.create_upload_payload( - record=record, - exclude=["type", "author"], + record=record, exclude=["type", "author"], ) for field in ["type", "author"]: assert field not in payload - raw_upload_response = upload_document(payload) + raw_upload_response = upload_document_reference(payload) assert raw_upload_response.status_code == 201 record["id"] = raw_upload_response.json()["id"].split("~")[1] test_data.append(record) @@ -106,32 +103,3 @@ def test_create_document_without_author_or_type(test_data): assert doc_ref["Item"]["RawRequest"] == payload for field in ["type", "author"]: assert field not in doc_ref["Item"]["RawRequest"] - - -def test_create_document_without_title(test_data): - record = { - "ods": "H81109", - "nhs_number": "9912003071", - } - - sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "dummy.pdf") - with open(sample_pdf_path, "rb") as f: - record["data"] = base64.b64encode(f.read()).decode("utf-8") - payload = pdm_data_helper.create_upload_payload(record=record, exclude=["title"]) - assert "title" not in payload - - raw_upload_response = upload_document(payload) - assert raw_upload_response.status_code == 201 - record["id"] = raw_upload_response.json()["id"].split("~")[1] - test_data.append(record) - - doc_ref = pdm_data_helper.retrieve_document_reference(record=record) - assert "Item" in doc_ref - assert "RawRequest" in doc_ref["Item"] - assert doc_ref["Item"]["RawRequest"] == payload - raw_request = json.loads(doc_ref["Item"]["RawRequest"]) - assert "content" in raw_request - content = raw_request["content"] - assert "attachment" in content[0] - attachment = raw_request["content"][0]["attachment"] - assert "title" not in attachment diff --git a/lambdas/tests/e2e/api/fhir_lloyd_george/__init__.py b/lambdas/tests/e2e/api/fhir_lloyd_george/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lambdas/tests/e2e/api/test_delete_document_reference_fhir_api_failure.py b/lambdas/tests/e2e/api/fhir_lloyd_george/test_delete_document_reference_fhir_api_failure.py similarity index 100% rename from lambdas/tests/e2e/api/test_delete_document_reference_fhir_api_failure.py rename to lambdas/tests/e2e/api/fhir_lloyd_george/test_delete_document_reference_fhir_api_failure.py diff --git a/lambdas/tests/e2e/api/test_delete_document_reference_fhir_api_success.py b/lambdas/tests/e2e/api/fhir_lloyd_george/test_delete_document_reference_fhir_api_success.py similarity index 100% rename from lambdas/tests/e2e/api/test_delete_document_reference_fhir_api_success.py rename to lambdas/tests/e2e/api/fhir_lloyd_george/test_delete_document_reference_fhir_api_success.py diff --git a/lambdas/tests/e2e/api/fhir_lloyd_george/test_retrieve_document_api.py b/lambdas/tests/e2e/api/fhir_lloyd_george/test_retrieve_document_api.py new file mode 100644 index 0000000000..4f464c7b5b --- /dev/null +++ b/lambdas/tests/e2e/api/fhir_lloyd_george/test_retrieve_document_api.py @@ -0,0 +1,108 @@ +import io +import uuid + +import requests +from syrupy.filters import paths +from tests.e2e.conftest import ( + API_ENDPOINT, + API_KEY, + LLOYD_GEORGE_S3_BUCKET, + LLOYD_GEORGE_SNOMED, +) +from tests.e2e.helpers.data_helper import LloydGeorgeDataHelper + +data_helper = LloydGeorgeDataHelper() + + +def test_small_file(test_data, snapshot_json): + lloyd_george_record = {} + test_data.append(lloyd_george_record) + + lloyd_george_record["id"] = str(uuid.uuid4()) + lloyd_george_record["nhs_number"] = "9449305943" + lloyd_george_record["data"] = io.BytesIO(b"Sample PDF Content") + + data_helper.create_metadata(lloyd_george_record) + data_helper.create_resource(lloyd_george_record) + + url = f"https://{API_ENDPOINT}/FhirDocumentReference/{LLOYD_GEORGE_SNOMED}~{lloyd_george_record['id']}" + headers = { + "Authorization": "Bearer 123", + "X-Api-Key": API_KEY, + "X-Correlation-Id": "1234", + } + response = requests.request("GET", url, headers=headers) + json = response.json() + + assert json == snapshot_json(exclude=paths("date", "id")) + + +def test_large_file(test_data, snapshot_json): + lloyd_george_record = {} + test_data.append(lloyd_george_record) + + lloyd_george_record["id"] = str(uuid.uuid4()) + lloyd_george_record["nhs_number"] = "9449305943" + lloyd_george_record["data"] = io.BytesIO(b"A" * (10 * 1024 * 1024)) + lloyd_george_record["size"] = 10 * 1024 * 1024 * 1024 + + data_helper.create_metadata(lloyd_george_record) + data_helper.create_resource(lloyd_george_record) + + url = f"https://{API_ENDPOINT}/FhirDocumentReference/{LLOYD_GEORGE_SNOMED}~{lloyd_george_record['id']}" + headers = { + "Authorization": "Bearer 123", + "X-Api-Key": API_KEY, + "X-Correlation-Id": "1234", + } + + response = requests.request("GET", url, headers=headers) + json = response.json() + + expected_presign_uri = f"https://{LLOYD_GEORGE_S3_BUCKET}.s3.eu-west-2.amazonaws.com/{lloyd_george_record['nhs_number']}/{lloyd_george_record['id']}" + assert expected_presign_uri in json["content"][0]["attachment"]["url"] + + assert json == snapshot_json( + exclude=paths("date", "id", "content.0.attachment.url") + ) + + +def test_no_file_found(snapshot_json): + lloyd_george_record = {} + lloyd_george_record["id"] = str(uuid.uuid4()) + + url = f"https://{API_ENDPOINT}/FhirDocumentReference/{LLOYD_GEORGE_SNOMED}~{lloyd_george_record['id']}" + headers = { + "Authorization": "Bearer 123", + "X-Api-Key": API_KEY, + "X-Correlation-Id": "1234", + } + response = requests.request("GET", url, headers=headers) + json = response.json() + + assert json == snapshot_json + + +def test_preliminary_file(test_data, snapshot_json): + lloyd_george_record = {} + test_data.append(lloyd_george_record) + + lloyd_george_record["id"] = str(uuid.uuid4()) + lloyd_george_record["nhs_number"] = "9449305943" + lloyd_george_record["data"] = io.BytesIO(b"Sample PDF Content") + lloyd_george_record["doc_status"] = "preliminary" + + data_helper.create_metadata(lloyd_george_record) + data_helper.create_resource(lloyd_george_record) + + url = f"https://{API_ENDPOINT}/FhirDocumentReference/{LLOYD_GEORGE_SNOMED}~{lloyd_george_record['id']}" + headers = { + "Authorization": "Bearer 123", + "X-Api-Key": API_KEY, + "X-Correlation-Id": "1234", + } + + response = requests.request("GET", url, headers=headers) + json = response.json() + + assert json == snapshot_json(exclude=paths("date", "id")) diff --git a/lambdas/tests/e2e/api/test_search_patient_api.py b/lambdas/tests/e2e/api/fhir_lloyd_george/test_search_patient_api.py similarity index 100% rename from lambdas/tests/e2e/api/test_search_patient_api.py rename to lambdas/tests/e2e/api/fhir_lloyd_george/test_search_patient_api.py diff --git a/lambdas/tests/e2e/api/fhir_lloyd_george/test_upload_document_api.py b/lambdas/tests/e2e/api/fhir_lloyd_george/test_upload_document_api.py new file mode 100644 index 0000000000..204eea9368 --- /dev/null +++ b/lambdas/tests/e2e/api/fhir_lloyd_george/test_upload_document_api.py @@ -0,0 +1,259 @@ +import base64 +import json +import logging +import os + +import requests +from syrupy.filters import paths +from tests.e2e.conftest import ( + API_ENDPOINT, + API_KEY, + APIM_ENDPOINT, + LLOYD_GEORGE_S3_BUCKET, + LLOYD_GEORGE_SNOMED, + fetch_with_retry, +) +from tests.e2e.helpers.data_helper import LloydGeorgeDataHelper + +data_helper = LloydGeorgeDataHelper() + + +def create_upload_payload(lloyd_george_record, exclude: list[str] | None = None): + sample_payload = { + "resourceType": "DocumentReference", + "type": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": f"{LLOYD_GEORGE_SNOMED}", + "display": "Lloyd George record folder", + }, + ], + }, + "subject": { + "identifier": { + "system": "https://fhir.nhs.uk/Id/nhs-number", + "value": lloyd_george_record["nhs_number"], + }, + }, + "author": [ + { + "identifier": { + "system": "https://fhir.nhs.uk/Id/ods-organization-code", + "value": lloyd_george_record["ods"], + }, + }, + ], + "custodian": { + "identifier": { + "system": "https://fhir.nhs.uk/Id/ods-organization-code", + "value": lloyd_george_record["ods"], + }, + }, + "content": [ + { + "attachment": { + "creation": "2023-01-01", + "contentType": "application/pdf", + "language": "en-GB", + "title": "1of1_Lloyd_George_Record_[Paula Esme VESEY]_[9730153973]_[22-01-1960].pdf", + }, + }, + ], + } + + if exclude: + for field in exclude: + if field == "title": + sample_payload["content"][0]["attachment"].pop(field, None) + else: + sample_payload.pop(field, None) + + if "data" in lloyd_george_record: + sample_payload["content"][0]["attachment"]["data"] = lloyd_george_record["data"] + return json.dumps(sample_payload) + + +def test_create_document_base64(test_data, snapshot_json): + lloyd_george_record = {} + lloyd_george_record["ods"] = "H81109" + lloyd_george_record["nhs_number"] = "9449303304" + sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "dummy.pdf") + with open(sample_pdf_path, "rb") as f: + lloyd_george_record["data"] = base64.b64encode(f.read()).decode("utf-8") + + payload = create_upload_payload(lloyd_george_record) + url = f"https://{API_ENDPOINT}/FhirDocumentReference" + headers = {"Authorization": "Bearer 123", "X-Api-Key": API_KEY} + + retrieve_response = requests.post(url, headers=headers, data=payload) + upload_response = retrieve_response.json() + lloyd_george_record["id"] = upload_response["id"].split("~")[1] + test_data.append(lloyd_george_record) + + retrieve_url = ( + f"https://{API_ENDPOINT}/FhirDocumentReference/{upload_response['id']}" + ) + + def condition(response_json): + logging.info(response_json) + return response_json["content"][0]["attachment"].get("data", False) + + raw_retrieve_response = fetch_with_retry(retrieve_url, condition) + retrieve_response = raw_retrieve_response.json() + + attachment_url = upload_response["content"][0]["attachment"]["url"] + assert ( + f"https://{APIM_ENDPOINT}/national-document-repository/FHIR/R4/DocumentReference/{LLOYD_GEORGE_SNOMED}~" + in attachment_url + ) + + base64_data = retrieve_response["content"][0]["attachment"]["data"] + assert base64.b64decode(base64_data, validate=True) + + assert upload_response == snapshot_json( + exclude=paths("id", "date", "content.0.attachment.url"), + ) + assert retrieve_response == snapshot_json( + exclude=paths("id", "date", "content.0.attachment.data"), + ) + + +def test_create_document_presign(test_data, snapshot_json): + lloyd_george_record = {} + lloyd_george_record["ods"] = "H81109" + lloyd_george_record["nhs_number"] = "9449303304" + + payload = create_upload_payload(lloyd_george_record) + url = f"https://{API_ENDPOINT}/FhirDocumentReference" + headers = {"Authorization": "Bearer 123", "X-Api-Key": API_KEY} + + retrieve_response = requests.post(url, headers=headers, data=payload) + upload_response = retrieve_response.json() + lloyd_george_record["id"] = upload_response["id"].split("~")[1] + test_data.append(lloyd_george_record) + presign_uri = upload_response["content"][0]["attachment"]["url"] + del upload_response["content"][0]["attachment"]["url"] + + sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "big-dummy.pdf") + with open(sample_pdf_path, "rb") as f: + files = {"file": f} + presign_response = requests.put(presign_uri, files=files) + assert presign_response.status_code == 200 + + retrieve_url = ( + f"https://{API_ENDPOINT}/FhirDocumentReference/{upload_response['id']}" + ) + + def condition(response_json): + logging.info(response_json) + return response_json["content"][0]["attachment"].get("url", False) + + raw_retrieve_response = fetch_with_retry(retrieve_url, condition) + retrieve_response = raw_retrieve_response.json() + + expected_presign_uri = f"https://{LLOYD_GEORGE_S3_BUCKET}.s3.eu-west-2.amazonaws.com/{lloyd_george_record['nhs_number']}/{lloyd_george_record['id']}" + assert expected_presign_uri in retrieve_response["content"][0]["attachment"]["url"] + + assert isinstance(retrieve_response["content"][0]["attachment"]["size"], (int)) + + assert upload_response == snapshot_json(exclude=paths("id", "date")) + assert retrieve_response == snapshot_json( + exclude=paths( + "id", + "date", + "content.0.attachment.url", + "content.0.attachment.size", + ), + ) + + +def test_create_document_virus(test_data, snapshot_json): + lloyd_george_record = {} + lloyd_george_record["ods"] = "H81109" + + lloyd_george_record["nhs_number"] = "9730154260" + + # Attach EICAR data + eicar_string = ( + r"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" + ) + lloyd_george_record["data"] = base64.b64encode(eicar_string.encode()).decode() + + payload = create_upload_payload(lloyd_george_record) + url = f"https://{API_ENDPOINT}/FhirDocumentReference" + headers = {"Authorization": "Bearer 123", "X-Api-Key": API_KEY} + + retrieve_response = requests.post(url, headers=headers, data=payload) + upload_response = retrieve_response.json() + lloyd_george_record["id"] = upload_response["id"].split("~")[1] + test_data.append(lloyd_george_record) + + retrieve_url = ( + f"https://{API_ENDPOINT}/FhirDocumentReference/{upload_response['id']}" + ) + + # Poll until processing/scan completes + def condition(response_json): + logging.info(response_json) + return response_json.get("docStatus") in ( + "cancelled", + "final", + ) + + raw_retrieve_response = fetch_with_retry(retrieve_url, condition) + retrieve_response = raw_retrieve_response.json() + + assert upload_response == snapshot_json( + exclude=paths("id", "date", "content.0.attachment.url"), + ) + assert retrieve_response == snapshot_json(exclude=paths("id", "date")) + + +def test_create_document_does_not_save_raw(test_data): + lloyd_george_record = {} + lloyd_george_record["ods"] = "H81109" + lloyd_george_record["nhs_number"] = "9449303304" + + sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "dummy.pdf") + with open(sample_pdf_path, "rb") as f: + lloyd_george_record["data"] = base64.b64encode(f.read()).decode("utf-8") + payload = create_upload_payload(lloyd_george_record) + + url = f"https://{API_ENDPOINT}/FhirDocumentReference" + headers = {"Authorization": "Bearer 123", "X-Api-Key": API_KEY} + + retrieve_response = requests.post(url, headers=headers, data=payload) + json_response = retrieve_response.json() + + lloyd_george_record["id"] = json_response.get("id").split("~")[1] + test_data.append(lloyd_george_record) + doc_ref = data_helper.retrieve_document_reference(record=lloyd_george_record) + assert "Item" in doc_ref + assert "RawRequest" not in doc_ref["Item"] + + +def test_create_document_without_title_raises_error(test_data): + lloyd_george_record = {} + lloyd_george_record["ods"] = "H81109" + lloyd_george_record["nhs_number"] = "9449303304" + + sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "dummy.pdf") + with open(sample_pdf_path, "rb") as f: + lloyd_george_record["data"] = base64.b64encode(f.read()).decode("utf-8") + payload = create_upload_payload(lloyd_george_record, exclude=["title"]) + + url = f"https://{API_ENDPOINT}/FhirDocumentReference" + headers = {"Authorization": "Bearer 123", "X-Api-Key": API_KEY} + + retrieve_response = requests.post(url, headers=headers, data=payload) + assert retrieve_response.status_code == 400 + + json_response = retrieve_response.json() + assert ( + json_response["issue"][0]["details"]["coding"][0]["code"] == "VALIDATION_ERROR" + ) + assert ( + json_response["issue"][0]["diagnostics"] + == "Failed to parse document upload request data" + ) diff --git a/lambdas/tests/e2e/helpers/rest_helper.py b/lambdas/tests/e2e/helpers/rest_helper.py new file mode 100644 index 0000000000..4710375ba4 --- /dev/null +++ b/lambdas/tests/e2e/helpers/rest_helper.py @@ -0,0 +1,86 @@ +import os +import uuid + +import requests +from tests.e2e.helpers.data_helper import PdmDataHelper + +pdm_data_helper = PdmDataHelper() + +PDM_SNOMED = pdm_data_helper.snomed_code +PDM_METADATA_TABLE = pdm_data_helper.dynamo_table +PDM_S3_BUCKET = pdm_data_helper.s3_bucket +MTLS_ENDPOINT = pdm_data_helper.mtls_endpoint +CLIENT_CERT_PATH = os.environ.get("CLIENT_CERT_PATH") +CLIENT_KEY_PATH = os.environ.get("CLIENT_KEY_PATH") + + +def _create_mtls_session( + client_cert_path=CLIENT_CERT_PATH, + client_key_path=CLIENT_KEY_PATH, +) -> requests.Session: + session = requests.Session() + + session.cert = (client_cert_path, client_key_path) + return session + + +def _define_request_headers() -> dict[str, str]: + default_headers = { + "X-Correlation-Id": str(uuid.uuid4()), + } + return default_headers + + +def search_document_reference( + nhs_number, + resource_type="DocumentReference", + client_cert_path=CLIENT_CERT_PATH, + client_key_path=CLIENT_KEY_PATH, +): + + """Helper to perform search by NHS number with optional mTLS certs.""" + url = f"https://{MTLS_ENDPOINT}/{resource_type}?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{nhs_number}" + headers = _define_request_headers() + session = _create_mtls_session(client_cert_path, client_key_path) + return session.get(url, headers=headers) + + +def get_pdm_document_reference( + record_id="", + client_cert_path=CLIENT_CERT_PATH, + client_key_path=CLIENT_KEY_PATH, + resource_type="DocumentReference", + pdm_snomed=PDM_SNOMED, + endpoint_override=None, +): + + if not endpoint_override: + url = f"https://{MTLS_ENDPOINT}/{resource_type}/{pdm_snomed}~{record_id}" + else: + url = f"https://{MTLS_ENDPOINT}/{resource_type}/{endpoint_override}" + + headers = _define_request_headers() + session = _create_mtls_session(client_cert_path, client_key_path) + response = session.get(url, headers=headers) + return response + + +def delete_document_reference( + endpoint, + client_cert_path=CLIENT_CERT_PATH, + client_key_path=CLIENT_KEY_PATH,): + + """Helper to perform a DELETE by NHS number.""" + url = f"https://{ MTLS_ENDPOINT}/DocumentReference{endpoint}" + headers = _define_request_headers() + session = _create_mtls_session(client_cert_path, client_key_path) + return session.delete(url=url, headers=headers) + + +def upload_document_reference(payload, resource_type="DocumentReference"): + + """Helper to upload DocumentReference.""" + url = f"https://{MTLS_ENDPOINT}/{resource_type}" + headers = _define_request_headers() + session = _create_mtls_session() + return session.post(url, headers=headers, data=payload)