Skip to content

Commit 835ef48

Browse files
authored
[NDR-270] Add spec & APIM e2e tests (#859)
1 parent 29260ec commit 835ef48

File tree

15 files changed

+1723
-105
lines changed

15 files changed

+1723
-105
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ node_modules/
9999
.vscode/
100100

101101
lambdas/tests/unit/helpers/data/pdf/tmp
102+
lambdas/tests/e2e/apim/*.pdf
102103
/lambdas/package_/
103104

104105
# jupyter notebook files

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ test-fhir-api-e2e: ## Runs FHIR API E2E tests. Usage: make test-fhir-api-e2e WOR
7373
./scripts/test/run-e2e-fhir-api-tests.sh --workspace $(WORKSPACE)
7474
rm -rf ./lambdas/mtls_env_certs/$(WORKSPACE)
7575

76+
test-apim-e2e: ## Runs APIM E2E tests for National Document Repository FHIR R4 API against ndr-dev.
77+
./scripts/test/run-apim-e2e-tests.sh
78+
7679
test-api-e2e-snapshots:
7780
cd ./lambdas && ./venv/bin/python3 -m pytest tests/e2e/api --ignore=tests/e2e/api/fhir --snapshot-update
7881

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Our Lambda function readme can be found [here](lambdas/README.md)
88

99
Our React User Interface readme can be found [here](app/README.md)
1010

11+
## End-to-End Tests Intro
12+
13+
Our E2E test readme can be found [here](lambdas/tests/e2e/README.md). We have E2E tests for our FHIR endpoints and APIM setup.
14+
1115
## Installation
1216

1317
- [Git](https://git-scm.com/)

apim/specification.yaml

Lines changed: 1424 additions & 0 deletions
Large diffs are not rendered by default.

lambdas/tests/e2e/README.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ There are 2 suites which separately test
77
1. FHIR API endpoints using mTLS `lambdas/tests/e2e/api/fhir/` (routed to **PDM** usecase)
88
2. API endpoints **not** using mTLS `lambdas/tests/e2e/api/` (routed to **Lloyd George** usecase)
99

10+
as well as APIM E2E tests `lambdas/tests/e2e/apim/`.
11+
1012
### 🔐 AWS Authentication
1113

12-
You must be authenticated with AWS to run the tests. Use the following commands with a configured profile set up in ~/.aws/config to authenticate:
14+
You must be authenticated with AWS to run the api tests. Use the following commands with a configured profile set up in ~/.aws/config to authenticate:
1315

1416
```bash
1517
aws sso login --profile <your-aws-profile>
@@ -40,6 +42,8 @@ Make sure your AWS profile has access to the required resources.
4042

4143
- `make test-api-e2e` — Runs the E2E tests without mTLS
4244

45+
- `make test-apim-e2e` - Runs the APIM E2E tests
46+
4347
### Snapshots
4448

4549
Snapshots reduce the amount of individual assertions by comparing pre and post an object e.g. a JSON returned from an API.
@@ -75,3 +79,60 @@ After updating your shell config, reload it:
7579
```bash
7680
source ~/.zshrc # or source ~/.bashrc
7781
```
82+
83+
## APIM E2E Tests
84+
85+
The **APIM E2E tests** validate the full integration and behavior of the API Management layer within the system. These tests ensure that:
86+
87+
- FHIR API endpoints (/DocumentReference) are correctly exposed and accessible via APIM.
88+
- mTLS authentication and authorization mechanisms are functioning as expected.
89+
- The routing and transformation logic configured in APIM behaves correctly.
90+
- The downstream services respond appropriately when accessed through APIM.
91+
92+
### Running the Tests
93+
94+
#### Authentication Requirement
95+
96+
Before running the APIM E2E tests, you must generate a **Proxygen access token**. This token is required to authenticate requests against the APIM proxy.
97+
98+
You can generate a temporary token using the `pytest-nhsd-apim` plugin
99+
100+
```
101+
proxygen pytest-nhsd-apim get-token
102+
```
103+
104+
Once generated, export the token to your shell environment so it's available during test execution. For example, in your `.zshrc` or `.bashrc` file:
105+
106+
```
107+
export APIGEE_ACCESS_TOKEN=your_token_here
108+
```
109+
110+
#### Executing
111+
112+
To execute the APIM E2E tests, use the following Make command:
113+
114+
```bash
115+
make test-apim-e2e
116+
```
117+
118+
This command validates APIM functionality for the National Document Repository FHIR R4 API using the following configuration:
119+
120+
```
121+
cd ./lambdas && ./venv/bin/python3 -m pytest tests/e2e/apim -vv \
122+
--api-name=national-document-repository_FHIR_R4 \
123+
--proxy-name=national-document-repository--internal-dev--national-document-repository_FHIR_R4
124+
```
125+
126+
### Arguments
127+
128+
- `api-name`: Specifies the name of the API being tested. In this case, it's the National Document Repository FHIR R4 API.
129+
- `proxy-name`: Indicates the APIM proxy configuration to use during testing. This ensures the tests target the correct APIM instance and environment.
130+
131+
### Important Notes
132+
133+
- Environment Configuration: Make sure your test environment is properly configured with access to APIM and any required secrets or credentials.
134+
135+
- You can test your proxygen set up via `proxygen instance list` and expect to see `national-document-repository_FHIR_R4` listed.
136+
For more information on proxygen credentials see: https://nhsd-confluence.digital.nhs.uk/pages/viewpage.action?spaceKey=APM&title=Proxygen+CLI+user+guide
137+
138+
- Dependencies: These tests rely on other services being up and running (e.g. backend API, databases). Ensure all dependencies are available before running the tests.

lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import base64
22
import io
3-
import uuid
43

54
import pytest
65
import requests
@@ -9,27 +8,13 @@
98
from lambdas.tests.e2e.api.fhir.conftest import (
109
MTLS_ENDPOINT,
1110
PDM_S3_BUCKET,
12-
PDM_SNOMED,
1311
create_mtls_session,
1412
)
13+
from lambdas.tests.e2e.conftest import PDM_SNOMED
1514

1615
pdm_data_helper = PdmDataHelper()
1716

1817

19-
def build_pdm_record(nhs_number="9912003071", data=None, doc_status=None, size=None):
20-
"""Helper to create a PDM record dictionary."""
21-
record = {
22-
"id": str(uuid.uuid4()),
23-
"nhs_number": nhs_number,
24-
"data": data or io.BytesIO(b"Sample PDF Content"),
25-
}
26-
if doc_status:
27-
record["doc_status"] = doc_status
28-
if size:
29-
record["size"] = size
30-
return record
31-
32-
3318
def get_document_reference(record_id):
3419
"""Helper to perform GET request for DocumentReference."""
3520
url = f"https://{MTLS_ENDPOINT}/DocumentReference/{PDM_SNOMED}~{record_id}"
@@ -43,7 +28,7 @@ def get_document_reference(record_id):
4328
@pytest.mark.parametrize("file_size", [None, 10 * 1024 * 1024])
4429
def test_file_retrieval(test_data, file_size):
4530
"""Test retrieval for small and large files."""
46-
pdm_record = build_pdm_record(
31+
pdm_record = pdm_data_helper.build_record(
4732
data=io.BytesIO(b"A" * file_size) if file_size else None,
4833
size=file_size * 1024 if file_size else None,
4934
)
@@ -93,7 +78,7 @@ def test_retrieve_edge_cases(
9378

9479

9580
def test_preliminary_file(test_data):
96-
pdm_record = build_pdm_record(doc_status="preliminary")
81+
pdm_record = pdm_data_helper.build_record(doc_status="preliminary")
9782
test_data.append(pdm_record)
9883
pdm_data_helper.create_metadata(pdm_record)
9984
pdm_data_helper.create_resource(pdm_record)
@@ -106,7 +91,7 @@ def test_preliminary_file(test_data):
10691

10792

10893
def test_forbidden_with_invalid_cert(test_data, temp_cert_and_key):
109-
pdm_record = build_pdm_record()
94+
pdm_record = pdm_data_helper.build_record()
11095
test_data.append(pdm_record)
11196
pdm_data_helper.create_metadata(pdm_record)
11297
pdm_data_helper.create_resource(pdm_record)

lambdas/tests/e2e/api/fhir/test_search_patient_fhir_api.py

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,13 @@
1-
import io
2-
import uuid
3-
41
import pytest
52
import requests
63

7-
from lambdas.tests.e2e.api.fhir.conftest import (
8-
MTLS_ENDPOINT,
9-
PDM_SNOMED,
10-
create_mtls_session,
11-
)
12-
from lambdas.tests.e2e.conftest import APIM_ENDPOINT
4+
from lambdas.tests.e2e.api.fhir.conftest import MTLS_ENDPOINT, create_mtls_session
5+
from lambdas.tests.e2e.conftest import APIM_ENDPOINT, PDM_SNOMED
136
from lambdas.tests.e2e.helpers.data_helper import PdmDataHelper
147

158
pdm_data_helper = PdmDataHelper()
169

1710

18-
def build_pdm_record(nhs_number="9912003071", data=None, doc_status=None):
19-
"""Helper to create a PDM record dictionary."""
20-
record = {
21-
"id": str(uuid.uuid4()),
22-
"nhs_number": nhs_number,
23-
"data": data or io.BytesIO(b"Sample PDF Content"),
24-
}
25-
if doc_status:
26-
record["doc_status"] = doc_status
27-
return record
28-
29-
3011
def search_document_reference(nhs_number):
3112
"""Helper to perform search by NHS number."""
3213
url = (
@@ -45,7 +26,7 @@ def create_and_store_record(
4526
test_data, nhs_number="9912003071", doc_status: str | None = None
4627
):
4728
"""Helper to create metadata and resource for a record."""
48-
record = build_pdm_record(nhs_number=nhs_number, doc_status=doc_status)
29+
record = pdm_data_helper.build_record(nhs_number=nhs_number, doc_status=doc_status)
4930
test_data.append(record)
5031
pdm_data_helper.create_metadata(record)
5132
pdm_data_helper.create_resource(record)

lambdas/tests/e2e/api/fhir/test_upload_document_fhir_api.py

Lines changed: 15 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import base64
2-
import json
32
import logging
43
import os
54

@@ -8,60 +7,13 @@
87

98
from lambdas.tests.e2e.api.fhir.conftest import (
109
MTLS_ENDPOINT,
11-
PDM_SNOMED,
1210
create_mtls_session,
1311
fetch_with_retry_mtls,
1412
)
13+
from lambdas.tests.e2e.conftest import APIM_ENDPOINT, PDM_SNOMED
1514
from lambdas.tests.e2e.helpers.data_helper import PdmDataHelper
1615

17-
18-
def create_upload_payload(record):
19-
"""Helper to build DocumentReference payload."""
20-
payload = {
21-
"resourceType": "DocumentReference",
22-
"type": {
23-
"coding": [
24-
{
25-
"system": "https://snomed.info/sct",
26-
"code": f"{PDM_SNOMED}",
27-
"display": "Confidential patient data",
28-
}
29-
]
30-
},
31-
"subject": {
32-
"identifier": {
33-
"system": "https://fhir.nhs.uk/Id/nhs-number",
34-
"value": record["nhs_number"],
35-
}
36-
},
37-
"author": [
38-
{
39-
"identifier": {
40-
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
41-
"value": record["ods"],
42-
}
43-
}
44-
],
45-
"custodian": {
46-
"identifier": {
47-
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
48-
"value": record["ods"],
49-
}
50-
},
51-
"content": [
52-
{
53-
"attachment": {
54-
"creation": "2023-01-01",
55-
"contentType": "application/pdf",
56-
"language": "en-GB",
57-
"title": "1of1_record_[Paula Esme VESEY]_[9730153973]_[22-01-1960].pdf",
58-
}
59-
}
60-
],
61-
}
62-
if "data" in record:
63-
payload["content"][0]["attachment"]["data"] = record["data"]
64-
return json.dumps(payload)
16+
pdm_data_helper = PdmDataHelper()
6517

6618

6719
def upload_document(session, payload):
@@ -91,19 +43,21 @@ def test_create_document_base64(test_data):
9143
sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "dummy.pdf")
9244
with open(sample_pdf_path, "rb") as f:
9345
record["data"] = base64.b64encode(f.read()).decode("utf-8")
94-
payload = create_upload_payload(record)
46+
payload = pdm_data_helper.create_upload_payload(record)
9547

9648
session = create_mtls_session()
9749
raw_upload_response = upload_document(session, payload)
9850
assert raw_upload_response.status_code == 200
51+
record["id"] = raw_upload_response.json()["id"].split("~")[1]
52+
test_data.append(record)
9953

10054
# Validate attachment URL
10155
upload_response = raw_upload_response.json()
10256
attachment_url = upload_response["content"][0]["attachment"]["url"]
103-
assert f"/DocumentReference/{PDM_SNOMED}~" in attachment_url
104-
105-
record["id"] = raw_upload_response.json()["id"].split("~")[1]
106-
test_data.append(record)
57+
assert (
58+
f"https://{APIM_ENDPOINT}/national-document-repository/FHIR/R4/DocumentReference/{PDM_SNOMED}~"
59+
in attachment_url
60+
)
10761

10862
def condition(response_json):
10963
logging.info(response_json)
@@ -128,7 +82,7 @@ def test_create_document_presign_fails():
12882
sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "big-dummy.pdf")
12983
with open(sample_pdf_path, "rb") as f:
13084
record["data"] = base64.b64encode(f.read()).decode("utf-8")
131-
payload = create_upload_payload(record)
85+
payload = pdm_data_helper.create_upload_payload(record)
13286

13387
session = create_mtls_session()
13488
upload_response = upload_document(session, payload)
@@ -141,7 +95,7 @@ def test_create_document_virus(test_data):
14195
"ods": "H81109",
14296
"nhs_number": "9730154260",
14397
}
144-
payload = create_upload_payload(record)
98+
payload = pdm_data_helper.create_upload_payload(record)
14599
session = create_mtls_session()
146100

147101
retrieve_response = upload_document(session, payload)
@@ -198,7 +152,7 @@ def test_search_edge_cases(
198152
sample_pdf_bytes = b"Sample PDF Content"
199153
record["data"] = base64.b64encode(sample_pdf_bytes).decode("utf-8")
200154

201-
payload = create_upload_payload(record)
155+
payload = pdm_data_helper.create_upload_payload(record)
202156
session = create_mtls_session()
203157
response = upload_document(session, payload)
204158
assert response.status_code == expected_status
@@ -220,7 +174,7 @@ def test_forbidden_with_invalid_cert(temp_cert_and_key):
220174
sample_pdf_bytes = b"Sample PDF Content"
221175
record["data"] = base64.b64encode(sample_pdf_bytes).decode("utf-8")
222176

223-
payload = create_upload_payload(record)
177+
payload = pdm_data_helper.create_upload_payload(record)
224178

225179
# Use an invalid cert that is trusted by TLS but fails truststore validation
226180
cert_path, key_path = temp_cert_and_key
@@ -244,15 +198,15 @@ def test_create_document_saves_raw(test_data):
244198
sample_pdf_path = os.path.join(os.path.dirname(__file__), "files", "dummy.pdf")
245199
with open(sample_pdf_path, "rb") as f:
246200
record["data"] = base64.b64encode(f.read()).decode("utf-8")
247-
payload = create_upload_payload(record)
201+
payload = pdm_data_helper.create_upload_payload(record)
248202

249203
session = create_mtls_session()
250204
raw_upload_response = upload_document(session, payload)
251205
assert raw_upload_response.status_code == 200
252206

253207
json_response = raw_upload_response.json()
254208
record["id"] = json_response.get("id")
255-
pdm_data_helper = PdmDataHelper()
209+
256210
doc_ref = pdm_data_helper.retrieve_document_reference(record=record)
257211
assert "Item" in doc_ref
258212
assert "RawRequest" in doc_ref["Item"]

lambdas/tests/e2e/apim/conftest.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
3+
import pytest
4+
5+
from lambdas.tests.e2e.helpers.data_helper import PdmDataHelper
6+
7+
pdm_data_helper = PdmDataHelper()
8+
9+
PDM_METADATA_TABLE = (
10+
os.environ.get("PDM_METADATA_TABLE") or "ndr-dev_PDMDocumentMetadata"
11+
)
12+
PDM_S3_BUCKET = os.environ.get("PDM_S3_BUCKET") or "ndr-dev-pdm-document-store"
13+
14+
15+
@pytest.fixture
16+
def test_data():
17+
test_records = []
18+
yield test_records
19+
for record in test_records:
20+
pdm_data_helper.tidyup(record)
10 MB
Binary file not shown.

0 commit comments

Comments
 (0)