Skip to content

Commit 43b20d2

Browse files
committed
Make requests work with identity section
1 parent 75eecc8 commit 43b20d2

7 files changed

Lines changed: 247 additions & 114 deletions

File tree

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,87 @@
11
info:
2-
name: "Access Record Structured"
2+
name: Access Record Structured
33
type: http
44
seq: 1
55

66
http:
77
method: POST
88
url: "{{base_url}}/patient/$gpc.getstructuredrecord"
9-
auth: inherit
109
headers:
1110
- name: ODS-from
1211
value: A12345
12+
- name: Ssp-TraceId
13+
value: 702bb247-5f51-46e0-a06e-975d32d045b5
1314
body:
1415
type: json
1516
data: |-
1617
{
1718
"resourceType": "Parameters",
1819
"parameter": [
20+
{
21+
"name": "patientNHSNumber",
22+
"valueIdentifier": {
23+
"system": "https://fhir.nhs.uk/Id/nhs-number",
24+
"value": "9999999999"
25+
}
26+
},
27+
{
28+
"name": "identity",
29+
"part": [
30+
{
31+
"name": "issuer",
32+
"valueString": "https://clinical-data-gateway-api.sandbox.nhs.uk"
33+
},
34+
{
35+
"name": "requestingOrgName",
36+
"valueString": "Example Consumer Organization"
37+
},
1938
{
20-
"name": "patientNHSNumber",
21-
"valueIdentifier": {
22-
"system": "https://fhir.nhs.uk/Id/nhs-number",
23-
"value": "9999999999"
24-
}
39+
"name": "requestingDevice",
40+
"resource": {
41+
"resourceType": "Device",
42+
"identifier": [
43+
{
44+
"system": "https://orange.testlab.nhs.uk/gpconnect-demonstrator/Id/local-system-instance-id",
45+
"value": "gpcdemonstrator-1-orange"
46+
}
47+
],
48+
"model": "GP Connect Demonstrator",
49+
"version": "1.5.0"
50+
}
51+
},
52+
{
53+
"name": "requestingPractitioner",
54+
"resource": {
55+
"resourceType": "Practitioner",
56+
"id": "10019",
57+
"name": [
58+
{
59+
"family": "Doe",
60+
"given": ["John"],
61+
"prefix": ["Mr"]
62+
}
63+
],
64+
"identifier": [
65+
{
66+
"system": "https://fhir.nhs.uk/Id/sds-user-id",
67+
"value": "111222333444"
68+
},
69+
{
70+
"system": "https://fhir.nhs.uk/Id/sds-role-profile-id",
71+
"value": "444555666777"
72+
},
73+
{
74+
"system": "https://orange.testlab.nhs.uk/gpconnect-demonstrator/Id/local-user-id",
75+
"value": "98ed4f78-814d-4266-8d5b-cde742f3093c"
76+
}
77+
]
78+
}
2579
}
26-
]
80+
]
81+
}
82+
]
2783
}
84+
auth: inherit
2885

2986
runtime:
3087
scripts:
@@ -37,3 +94,5 @@ runtime:
3794
settings:
3895
encodeUrl: true
3996
timeout: 0
97+
followRedirects: true
98+
maxRedirects: 5

bruno/gateway-api/collections/Steel_Thread/Access_Structured_Record_with_JWT_fields.yml

Lines changed: 0 additions & 94 deletions
This file was deleted.

gateway-api/src/fhir/stu3/elements/parameters.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from abc import ABC
22
from dataclasses import dataclass
3-
from typing import Annotated
3+
from typing import Annotated, Any
44

5-
from pydantic import Field
5+
from pydantic import BaseModel, ConfigDict, Field
66

77
from fhir import Resource
88
from fhir.stu3 import PatientIdentifier
@@ -17,4 +17,16 @@ class Parameter(ABC):
1717

1818
valueIdentifier: Annotated[PatientIdentifier, Field(frozen=True)]
1919

20-
parameter: Annotated[list[Parameter], Field(frozen=True, min_length=1)]
20+
class IdentityParameter(BaseModel):
21+
"""
22+
A CDG-specific identity parameter carrying JWT claim fields in part elements.
23+
"""
24+
25+
model_config = ConfigDict(frozen=True)
26+
27+
name: str
28+
part: list[dict[str, Any]]
29+
30+
parameter: Annotated[
31+
list[Parameter | IdentityParameter], Field(frozen=True, min_length=1)
32+
]

gateway-api/src/fhir/stu3/elements/test_elements.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ def test_create_with_multiple_parameters(self) -> None:
3939
params = Parameters.create(parameter=[param_a, param_b])
4040

4141
assert len(params.parameter) == 2, "parameter list should contain two entries"
42+
assert isinstance(params.parameter[0], Parameters.Parameter)
4243
assert params.parameter[0].valueIdentifier.value == "9000000009", (
4344
"first parameter NHS number should be '9000000009'"
4445
)
46+
assert isinstance(params.parameter[1], Parameters.Parameter)
4547
assert params.parameter[1].valueIdentifier.value == "9000000017", (
4648
"second parameter NHS number should be '9000000017'"
4749
)
@@ -66,6 +68,7 @@ def test_model_validate_valid(self) -> None:
6668
"resourceType should be 'Parameters'"
6769
)
6870
assert len(params.parameter) == 1, "parameter list should contain one entry"
71+
assert isinstance(params.parameter[0], Parameters.Parameter)
6972
assert params.parameter[0].valueIdentifier.value == "9000000009", (
7073
"valueIdentifier value should be '9000000009'"
7174
)
@@ -207,6 +210,100 @@ def test_is_frozen(self) -> None:
207210
)
208211

209212

213+
class TestIdentityParameter:
214+
def test_create(self) -> None:
215+
"""Test creating an IdentityParameter with part elements."""
216+
part = [
217+
{"name": "issuer", "valueString": "https://example.nhs.uk"},
218+
{"name": "requestingOrgName", "valueString": "Example Org"},
219+
]
220+
param = Parameters.IdentityParameter(name="identity", part=part)
221+
222+
assert param.name == "identity", "name should be 'identity'"
223+
assert len(param.part) == 2, "part list should contain two entries"
224+
assert param.part[0]["name"] == "issuer", "first part name should be 'issuer'"
225+
226+
def test_model_validate_identity_parameter_in_parameters(self) -> None:
227+
"""Test that a Parameters body with an identity part parameter validates."""
228+
params = Parameters.model_validate(
229+
{
230+
"resourceType": "Parameters",
231+
"parameter": [
232+
{
233+
"valueIdentifier": {
234+
"system": "https://fhir.nhs.uk/Id/nhs-number",
235+
"value": "9000000009",
236+
},
237+
},
238+
{
239+
"name": "identity",
240+
"part": [
241+
{
242+
"name": "issuer",
243+
"valueString": "https://example.nhs.uk",
244+
},
245+
{
246+
"name": "requestingOrgName",
247+
"valueString": "Example Org",
248+
},
249+
],
250+
},
251+
],
252+
}
253+
)
254+
255+
assert len(params.parameter) == 2, "parameter list should contain two entries"
256+
assert isinstance(params.parameter[1], Parameters.IdentityParameter), (
257+
"second parameter should be an IdentityParameter"
258+
)
259+
assert params.parameter[1].name == "identity", (
260+
"second parameter name should be 'identity'"
261+
)
262+
263+
def test_identity_parameter_serialises_name_and_part(self) -> None:
264+
"""Test that IdentityParameter serialises name and part into the JSON output."""
265+
params = Parameters.model_validate(
266+
{
267+
"resourceType": "Parameters",
268+
"parameter": [
269+
{
270+
"valueIdentifier": {
271+
"system": "https://fhir.nhs.uk/Id/nhs-number",
272+
"value": "9000000009",
273+
},
274+
},
275+
{
276+
"name": "identity",
277+
"part": [
278+
{"name": "issuer", "valueString": "https://example.nhs.uk"},
279+
],
280+
},
281+
],
282+
}
283+
)
284+
285+
json_str = params.model_dump_json()
286+
assert '"name":"identity"' in json_str.replace(" ", ""), (
287+
"JSON output should contain the identity parameter name"
288+
)
289+
assert '"part"' in json_str, "JSON output should contain the part array"
290+
assert "https://example.nhs.uk" in json_str, (
291+
"JSON output should contain the issuer value"
292+
)
293+
294+
def test_is_frozen(self) -> None:
295+
"""Test that IdentityParameter fields are frozen (immutable)."""
296+
from pydantic import ValidationError
297+
298+
param = Parameters.IdentityParameter(
299+
name="identity",
300+
part=[{"name": "issuer", "valueString": "https://example.nhs.uk"}],
301+
)
302+
303+
with pytest.raises(ValidationError):
304+
param.name = "other"
305+
306+
210307
class TestIssue:
211308
def test_diagnostics_defaults_to_none(self) -> None:
212309
class _ConcreteIssue(Issue):

0 commit comments

Comments
 (0)