Skip to content

Commit f527d75

Browse files
Use Pydantic FHIR types in tests.
1 parent aa16592 commit f527d75

11 files changed

Lines changed: 51 additions & 50 deletions

File tree

.vscode/cspell-dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ asid
22
fhir
33
getstructuredrecord
44
gpconnect
5+
searchset
56
usefixtures

gateway-api/src/fhir/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""FHIR data types and resources."""
22

3-
from fhir.bundle import Bundle, BundleEntry
3+
from fhir.bundle import BundleEntry, BundleTypedDict
44
from fhir.general_practitioner import GeneralPractitioner
55
from fhir.human_name import HumanName
66
from fhir.identifier import Identifier
@@ -9,7 +9,7 @@
99
from fhir.patient import PatientTypedDict
1010

1111
__all__ = [
12-
"Bundle",
12+
"BundleTypedDict",
1313
"BundleEntry",
1414
"HumanName",
1515
"Identifier",

gateway-api/src/fhir/bundle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class BundleEntry(TypedDict):
1010
resource: PatientTypedDict
1111

1212

13-
class Bundle(TypedDict):
13+
class BundleTypedDict(TypedDict):
1414
resourceType: str
1515
id: str
1616
type: str

gateway-api/src/fhir/resources.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ def model_dump_json(self, *args: Any, **kwargs: Any) -> str:
3636
kwargs.setdefault("exclude_none", True)
3737
return super().model_dump_json(*args, **kwargs)
3838

39+
def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
40+
# FHIR resources should not return empty fields
41+
kwargs.setdefault("exclude_none", True)
42+
return super().model_dump(*args, **kwargs)
43+
3944
@model_validator(mode="wrap")
4045
@classmethod
4146
def validate_with_subtype(
@@ -82,7 +87,7 @@ def _validate_resource_type(cls, value: str) -> str:
8287
return value
8388

8489

85-
type BundleType = Literal["document", "transaction", "searchset"]
90+
type BundleType = Literal["document", "transaction", "searchset", "collection"]
8691

8792

8893
class Bundle(Resource, resource_type="Bundle"):

gateway-api/src/gateway_api/conftest.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
import pytest
88
import requests
9-
from fhir import Bundle, OperationOutcome, PatientTypedDict
10-
from fhir.parameters import Parameters
119
from flask import Request
1210
from requests.structures import CaseInsensitiveDict
1311
from werkzeug.test import EnvironBuilder
@@ -21,10 +19,12 @@ class FakeResponse:
2119

2220
status_code: int
2321
headers: dict[str, str] | CaseInsensitiveDict[str]
24-
_json: dict[str, Any] | PatientTypedDict | OperationOutcome | Bundle
22+
_json: dict[str, Any]
2523
reason: str = ""
2624

27-
def json(self) -> dict[str, Any] | PatientTypedDict | OperationOutcome | Bundle:
25+
def json(
26+
self,
27+
) -> dict[str, Any]:
2828
return self._json
2929

3030
def raise_for_status(self) -> None:
@@ -39,7 +39,7 @@ def text(self) -> str:
3939
return json.dumps(self._json)
4040

4141

42-
def create_mock_request(headers: dict[str, str], body: Parameters) -> Request:
42+
def create_mock_request(headers: dict[str, str], body: dict[str, Any]) -> Request:
4343
"""Create a proper Flask Request object with headers and JSON body."""
4444
builder = EnvironBuilder(
4545
method="POST",
@@ -53,7 +53,7 @@ def create_mock_request(headers: dict[str, str], body: Parameters) -> Request:
5353

5454

5555
@pytest.fixture
56-
def valid_simple_request_payload() -> Parameters:
56+
def valid_simple_request_payload() -> dict[str, Any]:
5757
return {
5858
"resourceType": "Parameters",
5959
"parameter": [
@@ -69,7 +69,7 @@ def valid_simple_request_payload() -> Parameters:
6969

7070

7171
@pytest.fixture
72-
def valid_simple_response_payload() -> Bundle:
72+
def valid_simple_response_payload() -> dict[str, Any]:
7373
return {
7474
"resourceType": "Bundle",
7575
"id": "example-patient-bundle",
@@ -124,7 +124,7 @@ def valid_headers() -> dict[str, str]:
124124

125125

126126
@pytest.fixture
127-
def happy_path_pds_response_body() -> PatientTypedDict:
127+
def happy_path_pds_response_body() -> dict[str, Any]:
128128
return {
129129
"resourceType": "Patient",
130130
"id": "9999999999",

gateway-api/src/gateway_api/get_structured_record/request.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
)
1919

2020
if TYPE_CHECKING:
21-
from fhir.bundle import Bundle
21+
from fhir.bundle import BundleTypedDict
2222

2323

2424
class GetStructuredRecordRequest:
@@ -34,7 +34,7 @@ def __init__(self, request: Request) -> None:
3434
except BadRequest as error:
3535
raise InvalidRequestJSONError() from error
3636

37-
self._response_body: Bundle | OperationOutcome | None = None
37+
self._response_body: BundleTypedDict | OperationOutcome | None = None
3838
self._status_code: int | None = None
3939

4040
self._validate_headers()

gateway-api/src/gateway_api/get_structured_record/test_request.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import json
2-
from typing import TYPE_CHECKING, cast
2+
from typing import TYPE_CHECKING, Any, cast
33

44
import pytest
5-
from fhir.parameters import Parameters
65
from flask import Request
76

87
from gateway_api.common.common import FlaskResponse
@@ -11,11 +10,11 @@
1110
from gateway_api.get_structured_record.request import GetStructuredRecordRequest
1211

1312
if TYPE_CHECKING:
14-
from fhir.bundle import Bundle
13+
from fhir.bundle import BundleTypedDict
1514

1615

1716
@pytest.fixture
18-
def mock_request_with_headers(valid_simple_request_payload: Parameters) -> Request:
17+
def mock_request_with_headers(valid_simple_request_payload: dict[str, Any]) -> Request:
1918
headers = {
2019
"Ssp-TraceID": "test-trace-id",
2120
"ODS-from": "test-ods",
@@ -58,7 +57,7 @@ def test_nhs_number_is_pulled_from_request_body(
5857
assert actual == expected
5958

6059
def test_raises_value_error_when_ods_from_header_is_missing(
61-
self, valid_simple_request_payload: Parameters
60+
self, valid_simple_request_payload: dict[str, Any]
6261
) -> None:
6362
"""Test that ValueError is raised when ODS-from header is missing."""
6463
headers = {
@@ -73,7 +72,7 @@ def test_raises_value_error_when_ods_from_header_is_missing(
7372
GetStructuredRecordRequest(request=mock_request)
7473

7574
def test_raises_value_error_when_ods_from_header_is_whitespace(
76-
self, valid_simple_request_payload: Parameters
75+
self, valid_simple_request_payload: dict[str, Any]
7776
) -> None:
7877
"""
7978
Test that ValueError is raised when ODS-from header contains only whitespace.
@@ -91,7 +90,7 @@ def test_raises_value_error_when_ods_from_header_is_whitespace(
9190
GetStructuredRecordRequest(request=mock_request)
9291

9392
def test_raises_value_error_when_trace_id_header_is_missing(
94-
self, valid_simple_request_payload: Parameters
93+
self, valid_simple_request_payload: dict[str, Any]
9594
) -> None:
9695
"""Test that ValueError is raised when Ssp-TraceID header is missing."""
9796
headers = {
@@ -106,7 +105,7 @@ def test_raises_value_error_when_trace_id_header_is_missing(
106105
GetStructuredRecordRequest(request=mock_request)
107106

108107
def test_raises_value_error_when_trace_id_header_is_whitespace(
109-
self, valid_simple_request_payload: Parameters
108+
self, valid_simple_request_payload: dict[str, Any]
110109
) -> None:
111110
"""
112111
Test that ValueError is raised when Ssp-TraceID header contains only whitespace.
@@ -132,7 +131,7 @@ def test_sets_response_body_from_valid_json_data(
132131

133132
request_obj = GetStructuredRecordRequest(request=mock_request_with_headers)
134133

135-
bundle_data: Bundle = {
134+
bundle_data: BundleTypedDict = {
136135
"resourceType": "Bundle",
137136
"id": "test-bundle",
138137
"type": "collection",

gateway-api/src/gateway_api/pds/test_client.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
Unit tests for :mod:`gateway_api.pds_search`.
33
"""
44

5+
from typing import Any
56
from uuid import UUID, uuid4
67

78
import pytest
8-
from fhir import PatientTypedDict
99
from fhir.resources import Patient
1010
from pytest_mock import MockerFixture
1111

@@ -17,7 +17,7 @@
1717
def test_search_patient_by_nhs_number_happy_path(
1818
auth_token: str,
1919
mocker: MockerFixture,
20-
happy_path_pds_response_body: PatientTypedDict,
20+
happy_path_pds_response_body: dict[str, Any],
2121
) -> None:
2222
happy_path_response = FakeResponse(
2323
status_code=200, headers={}, _json=happy_path_pds_response_body
@@ -35,7 +35,7 @@ def test_search_patient_by_nhs_number_happy_path(
3535
def test_search_patient_by_nhs_number_has_no_gp_returns_gp_ods_code_none(
3636
auth_token: str,
3737
mocker: MockerFixture,
38-
happy_path_pds_response_body: PatientTypedDict,
38+
happy_path_pds_response_body: dict[str, Any],
3939
) -> None:
4040
gp_less_response_body = happy_path_pds_response_body.copy()
4141
del gp_less_response_body["generalPractitioner"]
@@ -55,7 +55,7 @@ def test_search_patient_by_nhs_number_has_no_gp_returns_gp_ods_code_none(
5555
def test_search_patient_by_nhs_number_sends_expected_headers(
5656
auth_token: str,
5757
mocker: MockerFixture,
58-
happy_path_pds_response_body: PatientTypedDict,
58+
happy_path_pds_response_body: dict[str, Any],
5959
) -> None:
6060
happy_path_response = FakeResponse(
6161
status_code=200, headers={}, _json=happy_path_pds_response_body
@@ -87,7 +87,7 @@ def test_search_patient_by_nhs_number_sends_expected_headers(
8787
def test_search_patient_by_nhs_number_generates_request_id(
8888
auth_token: str,
8989
mocker: MockerFixture,
90-
happy_path_pds_response_body: PatientTypedDict,
90+
happy_path_pds_response_body: dict[str, Any],
9191
) -> None:
9292
happy_path_response = FakeResponse(
9393
status_code=200, headers={}, _json=happy_path_pds_response_body
@@ -128,7 +128,7 @@ def test_search_patient_by_nhs_number_not_found_raises_error(
128128
def test_search_patient_by_nhs_number_missing_nhs_number_raises_error(
129129
auth_token: str,
130130
mocker: MockerFixture,
131-
happy_path_pds_response_body: PatientTypedDict,
131+
happy_path_pds_response_body: dict[str, Any],
132132
) -> None:
133133
response_body_missing_nhs_number = happy_path_pds_response_body.copy()
134134
response_body_missing_nhs_number["identifier"] = []

gateway-api/src/gateway_api/provider/test_client.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from typing import Any
1111

1212
import pytest
13-
from fhir import Parameters
1413
from requests import Response
1514
from requests.structures import CaseInsensitiveDict
1615
from stubs.provider.stub import GpProviderStub
@@ -73,7 +72,7 @@ def dummy_jwt() -> JWT:
7372

7473
def test_valid_gpprovider_access_structured_record_makes_request_correct_url_post_200(
7574
mock_request_post: dict[str, Any],
76-
valid_simple_request_payload: Parameters,
75+
valid_simple_request_payload: dict[str, Any],
7776
dummy_jwt: JWT,
7877
) -> None:
7978
"""
@@ -110,7 +109,7 @@ def test_valid_gpprovider_access_structured_record_makes_request_correct_url_pos
110109

111110
def test_valid_gpprovider_access_structured_record_with_correct_headers_post_200(
112111
mock_request_post: dict[str, Any],
113-
valid_simple_request_payload: Parameters,
112+
valid_simple_request_payload: dict[str, Any],
114113
dummy_jwt: JWT,
115114
) -> None:
116115
"""
@@ -156,7 +155,7 @@ def test_valid_gpprovider_access_structured_record_with_correct_headers_post_200
156155

157156
def test_valid_gpprovider_access_structured_record_with_correct_body_200(
158157
mock_request_post: dict[str, Any],
159-
valid_simple_request_payload: Parameters,
158+
valid_simple_request_payload: dict[str, Any],
160159
dummy_jwt: JWT,
161160
) -> None:
162161
"""
@@ -191,7 +190,7 @@ def test_valid_gpprovider_access_structured_record_with_correct_body_200(
191190
def test_valid_gpprovider_access_structured_record_returns_stub_response_200(
192191
mock_request_post: dict[str, Any], # NOQA ARG001 (Mock not called directly)
193192
stub: GpProviderStub,
194-
valid_simple_request_payload: Parameters,
193+
valid_simple_request_payload: dict[str, Any],
195194
dummy_jwt: JWT,
196195
) -> None:
197196
"""
@@ -227,7 +226,7 @@ def test_valid_gpprovider_access_structured_record_returns_stub_response_200(
227226

228227
def test_access_structured_record_raises_external_service_error(
229228
mock_request_post: dict[str, Any], # NOQA ARG001 (Mock not called directly)
230-
valid_simple_request_payload: Parameters,
229+
valid_simple_request_payload: dict[str, Any],
231230
dummy_jwt: JWT,
232231
) -> None:
233232
"""
@@ -257,7 +256,7 @@ def test_access_structured_record_raises_external_service_error(
257256

258257
def test_gpprovider_client_includes_authorization_header_with_bearer_token(
259258
mock_request_post: dict[str, Any],
260-
valid_simple_request_payload: Parameters,
259+
valid_simple_request_payload: dict[str, Any],
261260
dummy_jwt: JWT,
262261
) -> None:
263262
"""

gateway-api/src/gateway_api/test_app.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
import os
55
from collections.abc import Generator
66
from copy import copy
7-
from typing import TYPE_CHECKING
7+
from typing import TYPE_CHECKING, Any
88

99
import pytest
10-
from fhir.bundle import Bundle
11-
from fhir.parameters import Parameters
1210
from flask import Flask
1311
from flask.testing import FlaskClient
1412
from pytest_mock import MockerFixture
@@ -58,7 +56,7 @@ class TestGetStructuredRecord:
5856
def test_valid_get_structured_record_request_returns_expected_bundle(
5957
self,
6058
get_structured_record_response: Flask,
61-
valid_simple_response_payload: Bundle,
59+
valid_simple_response_payload: dict[str, Any],
6260
) -> None:
6361
actual_bundle = get_structured_record_response.get_json()
6462
assert actual_bundle == valid_simple_response_payload
@@ -182,7 +180,7 @@ def test_get_structured_record_returns_internal_server_error_when_invalid_json_s
182180
def get_structured_record_response(
183181
client: FlaskClient[Flask],
184182
valid_headers: dict[str, str],
185-
valid_simple_request_payload: Parameters,
183+
valid_simple_request_payload: dict[str, Any],
186184
) -> Flask:
187185
response = client.post(
188186
"/patient/$gpc.getstructuredrecord",
@@ -196,7 +194,7 @@ def get_structured_record_response(
196194
def get_structured_record_response_from_missing_header(
197195
client: FlaskClient[Flask],
198196
missing_headers: dict[str, str],
199-
valid_simple_request_payload: Parameters,
197+
valid_simple_request_payload: dict[str, Any],
200198
) -> Flask:
201199
response = client.post(
202200
"/patient/$gpc.getstructuredrecord",
@@ -225,7 +223,7 @@ def get_structured_record_response_using_invalid_json_body(
225223
def mock_positive_return_value_from_controller_run(
226224
mocker: MockerFixture,
227225
valid_headers: dict[str, str],
228-
valid_simple_response_payload: Bundle,
226+
valid_simple_response_payload: dict[str, Any],
229227
) -> None:
230228
postive_response = FlaskResponse(
231229
status_code=200,

0 commit comments

Comments
 (0)