Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bab2916
[PRMP-631] Bulk Upload E2e Test
robg-test Nov 28, 2025
44eb3cc
[PRMP-631] Fix Tests
robg-test Dec 1, 2025
8f39095
[PRMP-631] Make command
robg-test Dec 1, 2025
5532645
[PRMP-631] Split Check and Upload
robg-test Dec 1, 2025
ad021a0
[PRMP-631] CI
robg-test Dec 1, 2025
680ef41
[PRMP-631] CI & Test
robg-test Dec 1, 2025
51b021f
[PRMP-631] Fix dependency and closing quotes
robg-test Dec 1, 2025
2f4e774
[PRMP-631] Update Makefile and comments for testing purposes
robg-test Dec 1, 2025
d6a4779
[PRMP-631] Var fix
robg-test Dec 1, 2025
4e6f5ce
[PRMP-631] Test validate and add missing vars
robg-test Dec 1, 2025
c5026a9
[PRMP-631] define branch
robg-test Dec 1, 2025
1ff2096
[PRMP-631] Missing quote
robg-test Dec 1, 2025
b50e14b
[PRMP-631] Fix table names
robg-test Dec 1, 2025
7c03051
[PRMP-631] Fix snapshot
robg-test Dec 1, 2025
49c631d
[PRMP-631] Make Initiate on Workflow Call
robg-test Dec 1, 2025
19d8be1
[PRMP-631] Add seperator
robg-test Dec 1, 2025
616c8df
[PRMP-631] Workflow Rename
robg-test Dec 1, 2025
966b254
[PRMP-631] Prepare for test
robg-test Dec 1, 2025
7e6d1e9
[PRMP-631] Remove weird text
robg-test Dec 1, 2025
60ad4d1
[PRMP-631] Back to previous
robg-test Dec 1, 2025
130d02c
Merge branch 'main' into PRMP-631
robg-test Dec 5, 2025
2e7e852
[PRMP-631] WIP
robg-test Dec 8, 2025
70a233f
[PRMP-631] Bulk Upload Full Tests
robg-test Dec 9, 2025
e4d2b60
Merge branch 'main' into PRMP-631
robg-test Dec 9, 2025
06e94e4
[PRMP-631] Break from this branch
robg-test Dec 11, 2025
580d3fa
[PRMP-631] Nested Directories
robg-test Dec 15, 2025
cd3d31f
[PRMP-631] Support for prefixes
robg-test Dec 17, 2025
cdc0d97
[PRMP-631] Corruption Test
robg-test Dec 18, 2025
8f8916e
[PRMP-631] Filename tests
robg-test Dec 22, 2025
a1d093f
[PRMP-613] Working test
robg-test Dec 22, 2025
9228083
[PRMP-631] Pause
robg-test Dec 24, 2025
0a051f5
[PRMP-631] Make tests repeatedly runnable
robg-test Jan 2, 2026
b0dd351
[PRMP-631] Revert workflow changes
robg-test Jan 2, 2026
aef3a2c
[PRMP-631] Merge pretesting
robg-test Jan 2, 2026
27fdbef
[PRMP-631] Do not change these files
robg-test Jan 2, 2026
fded237
[PRMP-631] use new name in PDM Tests
robg-test Jan 2, 2026
9ae81a2
[PRMP-631] Missed comma
robg-test Jan 2, 2026
673ec84
[PRMP-631] Update Snapshot
robg-test Jan 2, 2026
8567552
Merge branch 'main' into PRMP-631
robg-test Jan 2, 2026
9a7272a
[PRMP-631] Fixes for e2e tests
robg-test Jan 2, 2026
f52e9f3
[PRMP-631] Weird method usage fix
robg-test Jan 2, 2026
0b88e15
[PRMP-631] Remove extra variables
robg-test Jan 2, 2026
7bc0c9d
[PRMP-631] Formatting
robg-test Jan 5, 2026
ffec4af
[PRMP-631] PR Comments
robg-test Jan 5, 2026
44dce85
[PRMP-631] Remove Bulk Upload Helper
robg-test Jan 5, 2026
a423caf
[PRMP-631] Format Data Helper
robg-test Jan 5, 2026
02cd938
[PRMP-631] Use LloydGeorgeHelper Test fixes after changes
robg-test Jan 5, 2026
ca4a62e
Merge branch 'main' into PRMP-631
robg-test Jan 5, 2026
4840212
[PRMP-631] Reorganize methods
robg-test Jan 5, 2026
241d67e
[PRMP-631] Remove TODO
robg-test Jan 6, 2026
333bb1e
Merge branch 'main' into PRMP-631
robg-test Jan 6, 2026
43ad737
Merge branch 'main' into PRMP-631
robg-test Jan 6, 2026
dfe9d0e
[PRMP-631] Typo in 'unstitched'
robg-test Jan 8, 2026
cf84b47
Merge branch 'main' into PRMP-631
robg-test Jan 8, 2026
3c42104
Merge branch 'main' into PRMP-631
robg-test Jan 8, 2026
8efb265
Merge branch 'main' into PRMP-631
robg-test Jan 8, 2026
5517abd
Update .gitignore
robg-test Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,4 @@ lambdas/tests/e2e/apim/*.pdf

tests/bulk-upload/output/**
tests/bulk-upload/test_patients_data/**
tests/bulk-upload/scripts/scenario_report.txt
tests/bulk-upload/scripts/scenario_report.txt
18 changes: 16 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,25 @@ else
cd ./lambdas && ./venv/bin/python3 -m pytest tests/e2e/api --ignore=tests/e2e/api/fhir --snapshot-update
endif

initiate-bulk-upload:
ifeq ($(CONTAINER), true)
cd ./lambdas && PYTHONPATH=. poetry run pytest tests/e2e/bulk_upload/upload -vv
else
cd ./lambdas && ./venv/bin/python3 -m pytest tests/e2e/bulk_upload/upload -vv
endif

test-bulk-upload-e2e:
ifeq ($(CONTAINER), true)
cd ./lambdas && PYTHONPATH=. poetry run pytest tests/e2e/bulk_upload -vv
cd ./lambdas && PYTHONPATH=. poetry run pytest tests/e2e/bulk_upload/check -vv
else
cd ./lambdas && ./venv/bin/python3 -m pytest tests/e2e/bulk_upload/check -vv
endif

test-bulk-upload-e2e-snapshots:
ifeq ($(CONTAINER), true)
cd ./lambdas && PYTHONPATH=. poetry run pytest tests/e2e/bulk_upload/check --snapshot-update
else
cd ./lambdas && ./venv/bin/python3 -m pytest tests/e2e/bulk_upload -vv
cd ./lambdas && ./venv/bin/python3 -m pytest tests/e2e/bulk_upload/check --snapshot-update
endif

test-unit:
Expand Down
14 changes: 4 additions & 10 deletions lambdas/tests/e2e/api/fhir/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
import requests

from lambdas.tests.e2e.helpers.data_helper import PdmDataHelper
from tests.e2e.helpers.data_helper import PdmDataHelper

pdm_data_helper = PdmDataHelper()

Expand All @@ -27,9 +27,7 @@ def test_data():
pdm_data_helper.tidyup(record)


def fetch_with_retry_mtls(
session, url, headers, condition_func=None, max_retries=5, delay=10
):
def fetch_with_retry_mtls(session, url, headers, condition_func=None, max_retries=5, delay=10):
retries = 0
while retries < max_retries:
response = session.get(url, headers=headers)
Expand All @@ -47,9 +45,7 @@ def fetch_with_retry_mtls(
raise Exception("Condition not met within retry limit")


def create_mtls_session(
client_cert_path=CLIENT_CERT_PATH, client_key_path=CLIENT_KEY_PATH
):
def create_mtls_session(client_cert_path=CLIENT_CERT_PATH, client_key_path=CLIENT_KEY_PATH):
session = requests.Session()
session.cert = (client_cert_path, client_key_path)
return session
Expand Down Expand Up @@ -111,9 +107,7 @@ def create_and_store_pdm_record(
size: int | None = None,
):
"""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
)
record = pdm_data_helper.build_record(nhs_number=nhs_number, doc_status=doc_status, size=size)
test_data.append(record)
pdm_data_helper.create_metadata(record)
pdm_data_helper.create_resource(record)
Expand Down
6 changes: 2 additions & 4 deletions lambdas/tests/e2e/api/fhir/test_mtls_routing.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import os
import uuid

from lambdas.tests.e2e.api.fhir.conftest import get_pdm_document_reference
from tests.e2e.api.fhir.conftest 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")


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
)
response = get_pdm_document_reference(record_id, UNAUTHORISED_CLIENT_CERT_PATH, UNAUTHORISED_CLIENT_KEY_PATH)
assert response.status_code == 400

data = response.json()
Expand Down
21 changes: 6 additions & 15 deletions lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@
import uuid

import pytest
from tests.e2e.helpers.data_helper import PdmDataHelper

from lambdas.tests.e2e.api.fhir.conftest import (
from tests.e2e.api.fhir.conftest import (
PDM_S3_BUCKET,
create_and_store_pdm_record,
get_pdm_document_reference,
)
from tests.e2e.helpers.data_helper import PdmDataHelper

pdm_data_helper = PdmDataHelper()


@pytest.mark.parametrize("file_size", [None, 10 * 1024 * 1024])
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
)
pdm_record = create_and_store_pdm_record(test_data, size=file_size if file_size else None)
response = get_pdm_document_reference(pdm_record["id"])
assert response.status_code == 200

Expand All @@ -30,10 +28,7 @@ def test_file_retrieval(test_data, file_size):
expected_bytes = b"Sample PDF Content"
assert decoded_data == expected_bytes
else:
expected_presign_uri = (
f"https://{PDM_S3_BUCKET}.s3.eu-west-2.amazonaws.com/"
f"{pdm_record['nhs_number']}/{pdm_record['id']}"
)
expected_presign_uri = f"https://{PDM_S3_BUCKET}.s3.eu-west-2.amazonaws.com/{pdm_record['nhs_number']}/{pdm_record['id']}"
assert expected_presign_uri in json["content"][0]["attachment"]["url"]


Expand All @@ -43,9 +38,7 @@ def test_file_retrieval(test_data, file_size):
(str(uuid.uuid4()), 404, "RESOURCE_NOT_FOUND", "Document reference not found"),
],
)
def test_retrieve_edge_cases(
record_id, expected_status, expected_code, expected_diagnostics
):
def test_retrieve_edge_cases(record_id, expected_status, expected_code, expected_diagnostics):
response = get_pdm_document_reference(record_id)
assert response.status_code == expected_status

Expand Down Expand Up @@ -75,9 +68,7 @@ def test_forbidden_with_invalid_cert(test_data, temp_cert_and_key):
# Use an invalid cert that is trusted by TLS but fails truststore validation
cert_path, key_path = temp_cert_and_key

response = get_pdm_document_reference(
pdm_record["id"], client_cert_path=cert_path, client_key_path=key_path
)
response = get_pdm_document_reference(pdm_record["id"], client_cert_path=cert_path, client_key_path=key_path)

body = response.json()
assert response.status_code == 403
Expand Down
36 changes: 9 additions & 27 deletions lambdas/tests/e2e/api/fhir/test_search_patient_fhir_api.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import pytest

from lambdas.tests.e2e.api.fhir.conftest import (
from tests.e2e.api.fhir.conftest import (
MTLS_ENDPOINT,
PDM_SNOMED,
create_and_store_pdm_record,
create_mtls_session,
)
from lambdas.tests.e2e.conftest import APIM_ENDPOINT
from lambdas.tests.e2e.helpers.data_helper import PdmDataHelper
from tests.e2e.conftest import APIM_ENDPOINT
from tests.e2e.helpers.data_helper import PdmDataHelper

pdm_data_helper = PdmDataHelper()


def search_document_reference(nhs_number, client_cert_path=None, client_key_path=None):
"""Helper to perform search by NHS number with optional mTLS certs."""
url = (
f"https://{MTLS_ENDPOINT}/DocumentReference?"
f"subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{nhs_number}"
)
url = f"https://{MTLS_ENDPOINT}/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{nhs_number}"
headers = {
"X-Correlation-Id": "1234",
}
Expand Down Expand Up @@ -56,11 +53,7 @@ def test_search_patient_details(test_data):

# Find the entry with the matching record_id
matching_entry = next(
(
e
for e in entries
if e["resource"].get("id") == f"{PDM_SNOMED}~{expected_record_id}"
),
(e for e in entries if e["resource"].get("id") == f"{PDM_SNOMED}~{expected_record_id}"),
None,
)
assert matching_entry
Expand All @@ -73,10 +66,7 @@ def test_search_patient_details(test_data):


def test_multiple_cancelled_search_patient_details(test_data):
record_ids = [
create_and_store_pdm_record(test_data, doc_status="cancelled")["id"]
for _ in range(2)
]
record_ids = [create_and_store_pdm_record(test_data, doc_status="cancelled")["id"] for _ in range(2)]

response = search_document_reference("9912003071")
assert response.status_code == 200
Expand All @@ -88,11 +78,7 @@ def test_multiple_cancelled_search_patient_details(test_data):
# Validate all created records exist and have status cancelled
for record_id in record_ids:
entry = next(
(
e
for e in entries
if e["resource"].get("id") == f"{PDM_SNOMED}~{record_id}"
),
(e for e in entries if e["resource"].get("id") == f"{PDM_SNOMED}~{record_id}"),
None,
)
assert entry
Expand All @@ -106,9 +92,7 @@ def test_multiple_cancelled_search_patient_details(test_data):
("123", 400, "INVALID_SEARCH_DATA", "Invalid patient number 123"),
],
)
def test_search_edge_cases(
nhs_number, expected_status, expected_code, expected_diagnostics
):
def test_search_edge_cases(nhs_number, expected_status, expected_code, expected_diagnostics):
response = search_document_reference(nhs_number)
assert response.status_code == expected_status

Expand All @@ -127,9 +111,7 @@ def test_search_patient_unauthorized_mtls(test_data, temp_cert_and_key):
# Use an invalid cert that is trusted by TLS but fails truststore validation
cert_path, key_path = temp_cert_and_key

response = search_document_reference(
"9912003071", client_cert_path=cert_path, client_key_path=key_path
)
response = search_document_reference("9912003071", client_cert_path=cert_path, client_key_path=key_path)

body = response.json()
assert response.status_code == 403
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import pytest
import requests

from lambdas.tests.e2e.api.fhir.conftest import (
from tests.e2e.api.fhir.conftest import (
MTLS_ENDPOINT,
create_mtls_session,
fetch_with_retry_mtls,
)
from lambdas.tests.e2e.helpers.data_helper import PdmDataHelper
from tests.e2e.helpers.data_helper import PdmDataHelper

pdm_data_helper = PdmDataHelper()

Expand Down Expand Up @@ -59,9 +59,7 @@ def test_create_document_virus(test_data):
}

# Attach EICAR data
eicar_string = (
r"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
)
eicar_string = r"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
record["data"] = base64.b64encode(eicar_string.encode()).decode()
payload = pdm_data_helper.create_upload_payload(record)

Expand All @@ -79,9 +77,7 @@ def condition(response_json):
"final",
)

raw_retrieve_response = retrieve_document_with_retry(
upload_response["id"], condition
)
raw_retrieve_response = retrieve_document_with_retry(upload_response["id"], condition)
retrieve_response = raw_retrieve_response.json()

assert retrieve_response["docStatus"] == "cancelled"
Expand All @@ -104,9 +100,7 @@ def condition(response_json):
),
],
)
def test_search_edge_cases(
nhs_number, expected_status, expected_code, expected_diagnostics
):
def test_search_edge_cases(nhs_number, expected_status, expected_code, expected_diagnostics):
record = {
"ods": "H81109",
"nhs_number": f"{nhs_number}",
Expand Down Expand Up @@ -143,9 +137,7 @@ def test_forbidden_with_invalid_cert(temp_cert_and_key):
url = f"https://{MTLS_ENDPOINT}/DocumentReference"
headers = {"Authorization": "Bearer 123", "X-Correlation-Id": "1234"}

response = requests.post(
url, headers=headers, cert=(cert_path, key_path), data=payload
)
response = requests.post(url, headers=headers, cert=(cert_path, key_path), data=payload)
body = response.json()
assert response.status_code == 403
assert body["message"] == "Forbidden"
Expand All @@ -172,6 +164,4 @@ def test_create_document_with_invalid_author_returns_error(test_data, author_pay
response_json = raw_upload_response.json()
assert raw_upload_response.status_code == 400
assert response_json["resourceType"] == "OperationOutcome"
assert (
response_json["issue"][0]["details"]["coding"][0]["code"] == "VALIDATION_ERROR"
)
assert response_json["issue"][0]["details"]["coding"][0]["code"] == "VALIDATION_ERROR"
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import logging
import os

from lambdas.tests.e2e.api.fhir.conftest import (
from tests.e2e.api.fhir.conftest import (
MTLS_ENDPOINT,
PDM_SNOMED,
create_mtls_session,
fetch_with_retry_mtls,
)
from lambdas.tests.e2e.conftest import APIM_ENDPOINT
from lambdas.tests.e2e.helpers.data_helper import PdmDataHelper
from tests.e2e.conftest import APIM_ENDPOINT
from tests.e2e.helpers.data_helper import PdmDataHelper

pdm_data_helper = PdmDataHelper()

Expand Down Expand Up @@ -53,18 +53,13 @@ def test_create_document_base64(test_data):
# Validate attachment URL
upload_response = raw_upload_response.json()
attachment_url = upload_response["content"][0]["attachment"]["url"]
assert (
f"https://{APIM_ENDPOINT}/national-document-repository/FHIR/R4/DocumentReference/{PDM_SNOMED}~"
in attachment_url
)
assert f"https://{APIM_ENDPOINT}/national-document-repository/FHIR/R4/DocumentReference/{PDM_SNOMED}~" in attachment_url

def condition(response_json):
logging.info(response_json)
return response_json["content"][0]["attachment"].get("data", False)

raw_retrieve_response = retrieve_document_with_retry(
upload_response["id"], condition
)
raw_retrieve_response = retrieve_document_with_retry(upload_response["id"], condition)
retrieve_response = raw_retrieve_response.json()

# Validate base64 data
Expand Down Expand Up @@ -105,9 +100,7 @@ def test_create_document_without_author_or_type(test_data):
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=["type", "author"]
)
payload = pdm_data_helper.create_upload_payload(record=record, exclude=["type", "author"])

for field in ["type", "author"]:
assert field not in payload
Expand Down
11 changes: 4 additions & 7 deletions lambdas/tests/e2e/api/test_login_api.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import pytest

from tests.e2e.helpers.data_helper import LloydGeorgeDataHelper
from tests.e2e.helpers.lloyd_george_mockcis2_helper import LloydGeorgeMockcis2Helper
from tests.e2e.helpers.mockcis2_helper import MockCis2Helper

# Note this is testing a mock, but this test is valuable to ensure the login code is working for other tests

data_helper = LloydGeorgeDataHelper()


@pytest.mark.skipif(
data_helper.workspace == "pre-prod", reason="CIS2 login is not mocked in pre-prod"
)
@pytest.mark.skipif(data_helper.workspace == "pre-prod", reason="CIS2 login is not mocked in pre-prod")
@pytest.mark.parametrize(
"ods_code, role_to_assume, expected_granted_role",
[
Expand All @@ -19,9 +18,7 @@
],
)
def test_login(ods_code, role_to_assume, expected_granted_role):
login_helper = LloydGeorgeMockcis2Helper(
ods=ods_code, repository_role=role_to_assume
)
login_helper = MockCis2Helper(ods=ods_code, repository_role=role_to_assume)
login_helper.generate_mockcis2_token()
assert login_helper.user_role == expected_granted_role
assert login_helper.user_token is not None
Loading
Loading