Skip to content

Commit e54a8b2

Browse files
authored
VED-740: Add meta.VersionId to search response (#783)
* VED-740: Add meta version to all search responses and unify all search responses
1 parent 8de7dc2 commit e54a8b2

File tree

9 files changed

+76
-45
lines changed

9 files changed

+76
-45
lines changed

backend/src/fhir_repository.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,12 @@ def find_immunizations(self, patient_identifier: str, vaccine_types: set):
372372
items = [x for x in raw_items if x["PatientSK"].split("#")[0] in vaccine_types]
373373

374374
# Return a list of the FHIR immunization resource JSON items
375-
final_resources = [json.loads(item["Resource"]) for item in items]
375+
final_resources = [{
376+
**json.loads(item["Resource"]),
377+
"meta": {"versionId": int(item.get("Version", 1))}
378+
}
379+
for item in items
380+
]
376381

377382
return final_resources
378383
else:

backend/src/models/utils/generic_utils.py

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
"""Generic utilities"""
22

33
import datetime
4-
5-
from typing import Literal, Union, Optional
4+
import json
5+
from typing import Literal, Union, Optional, Dict, Any
6+
from fhir.resources.R4B.bundle import (
7+
Bundle as FhirBundle,
8+
BundleEntry,
9+
BundleLink,
10+
BundleEntrySearch,
11+
)
12+
from fhir.resources.R4B.immunization import Immunization
613
from models.constants import Constants
714
import urllib.parse
815
import base64
@@ -123,7 +130,6 @@ def create_diagnostics():
123130
exp_error = {"diagnostics": diagnostics}
124131
return exp_error
125132

126-
127133
def create_diagnostics_error(value):
128134
if value == "Both":
129135
diagnostics = (
@@ -134,46 +140,49 @@ def create_diagnostics_error(value):
134140
exp_error = {"diagnostics": diagnostics}
135141
return exp_error
136142

137-
138-
def form_json(response, _element, identifier, baseurl):
139-
self_url = f"{baseurl}?identifier={identifier}" + (f"&_elements={_element}" if _element else "")
140-
json = {
141-
"resourceType": "Bundle",
142-
"type": "searchset",
143-
"link": [
144-
{"relation": "self", "url": self_url}
145-
]
143+
def make_empty_bundle(self_url: str) -> Dict[str, Any]:
144+
return {
145+
"resourceType": "Bundle",
146+
"type": "searchset",
147+
"link": [{"relation": "self", "url": self_url}],
148+
"entry": [],
149+
"total": 0,
146150
}
151+
152+
def form_json(response, _elements, identifier, baseurl):
153+
self_url = f"{baseurl}?identifier={identifier}" + (f"&_elements={_elements}" if _elements else "")
154+
147155
if not response:
148-
json["entry"] = []
149-
json["total"] = 0
150-
return json
156+
return make_empty_bundle(self_url)
151157

152-
# Full Immunization payload to be returned if only the identifier parameter was provided
153-
if identifier and not _element:
154-
resource = response["resource"]
158+
meta = {"versionId": response["version"]} if "version" in response else {}
155159

156-
elif identifier and _element:
157-
element = {e.strip().lower() for e in _element.split(",") if e.strip()}
160+
# Full Immunization payload to be returned if only the identifier parameter was provided and truncated when _elements is used
161+
if _elements:
162+
elements = {e.strip().lower() for e in _elements.split(",") if e.strip()}
158163
resource = {"resourceType": "Immunization"}
164+
if "id" in elements: resource["id"] = response["id"]
165+
if "meta" in elements and meta: resource["meta"] = meta
159166

160-
# Add 'id' if specified
161-
if "id" in element:
162-
resource["id"] = response["id"]
167+
else:
168+
resource = response["resource"]
169+
resource["meta"] = meta
163170

164-
# Add 'meta' if specified
165-
if "meta" in element:
166-
resource["id"] = response["id"]
167-
resource["meta"] = {"versionId": response["version"]}
171+
entry = BundleEntry(fullUrl=f"{baseurl}/{response['id']}",
172+
resource=Immunization.construct(**resource) if _elements else Immunization.parse_obj(resource),
173+
search=BundleEntrySearch.construct(mode="match") if not _elements else None,
174+
)
168175

169-
json["entry"] = [{
170-
"fullUrl": f"https://api.service.nhs.uk/immunisation-fhir-api/Immunization/{response['id']}",
171-
"resource": resource,
172-
}
173-
]
174-
json["total"] = 1
175-
return json
176+
fhir_bundle = FhirBundle(
177+
resourceType="Bundle", type="searchset",
178+
link = [BundleLink(relation="self", url=self_url)],
179+
entry=[entry],
180+
total=1)
176181

182+
# Reassigned total to ensure it appears last in the response to match expected output
183+
data = json.loads(fhir_bundle.json(by_alias=True))
184+
data["total"] = data.pop("total")
185+
return data
177186

178187
def check_keys_in_sources(event, not_required_keys):
179188
# Decode and parse the body, assuming it is JSON and base64-encoded

backend/tests/sample_data/completed_covid19_immunization_event.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
],
5252
"identifier": [
5353
{
54+
"use": "official",
5455
"system": "https://supplierABC/identifiers/vacc",
5556
"value": "ACME-vacc123456"
5657
}

backend/tests/sample_data/completed_covid19_immunization_event_for_read.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
}
5151
],"identifier": [
5252
{
53+
"use": "official",
5354
"system": "https://supplierABC/identifiers/vacc",
5455
"value": "ACME-vacc123456"
5556
}

backend/tests/test_fhir_repository.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,16 +581,18 @@ def test_exclude_deleted(self):
581581

582582
def test_map_results_to_immunizations(self):
583583
"""it should map Resource list into a list of Immunizations"""
584-
imms1 = {"id": 1}
585-
imms2 = {"id": 2}
584+
imms1 = {"id": 1, "meta": {"versionId": 1}}
585+
imms2 = {"id": 2, "meta": {"versionId": 1}}
586586
items = [
587587
{
588588
"Resource": json.dumps(imms1),
589589
"PatientSK": "COVID19#some_other_text",
590+
"Version": "1"
590591
},
591592
{
592593
"Resource": json.dumps(imms2),
593594
"PatientSK": "COVID19#some_other_text",
595+
"Version": "1"
594596
},
595597
]
596598

backend/tests/test_fhir_service.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,25 +346,34 @@ def tearDown(self):
346346

347347
def test_get_immunization_by_identifier(self):
348348
"""it should find an Immunization by id"""
349-
imms = "an-id#an-id"
349+
imms_id = "an-id#an-id"
350350
identifier = "test"
351351
element = "id,mEta,DDD"
352+
352353
mock_resource = create_covid_19_immunization_dict(identifier)
353354
self.authoriser.authorise.return_value = True
354355
self.imms_repo.get_immunization_by_identifier.return_value = {
355356
"resource": mock_resource,
356-
"id": identifier,
357+
"id": imms_id,
357358
"version": 1
358359
}, "covid19"
359360

360361
# When
361-
service_resp = self.fhir_service.get_immunization_by_identifier(imms, self.MOCK_SUPPLIER_NAME, identifier,
362+
service_resp = self.fhir_service.get_immunization_by_identifier(imms_id, self.MOCK_SUPPLIER_NAME, identifier,
362363
element)
363364

364365
# Then
365-
self.imms_repo.get_immunization_by_identifier.assert_called_once_with(imms)
366+
self.imms_repo.get_immunization_by_identifier.assert_called_once_with(imms_id)
366367
self.authoriser.authorise.assert_called_once_with(self.MOCK_SUPPLIER_NAME, ApiOperationCode.SEARCH, {"covid19"})
367368
self.assertEqual(service_resp["resourceType"], "Bundle")
369+
self.assertEqual(service_resp.get("type"), "searchset")
370+
self.assertIn("entry", service_resp)
371+
self.assertEqual(len(service_resp["entry"]), 1)
372+
self.assertIn("total", service_resp)
373+
self.assertEqual(service_resp["total"], 1)
374+
res = service_resp["entry"][0]["resource"]
375+
self.assertEqual(res["resourceType"], "Immunization")
376+
self.assertEqual(res["id"], imms_id)
368377

369378
def test_get_immunization_by_identifier_raises_error_when_not_authorised(self):
370379
"""it should find an Immunization by id"""
@@ -382,6 +391,7 @@ def test_get_immunization_by_identifier_raises_error_when_not_authorised(self):
382391
self.imms_repo.get_immunization_by_identifier.assert_called_once_with(imms)
383392
self.authoriser.authorise.assert_called_once_with(self.MOCK_SUPPLIER_NAME, ApiOperationCode.SEARCH, {"covid19"})
384393

394+
385395
def test_immunization_not_found(self):
386396
"""it should return None if Immunization doesn't exist"""
387397
imms_id = "none"

backend/tests/utils/immunization_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def create_covid_19_immunization_dict(
2727

2828
return immunization_json
2929

30+
3031
def create_covid_19_immunization_dict_no_id(
3132
nhs_number=VALID_NHS_NUMBER, occurrence_date_time="2021-02-07T13:28:17.271+00:00"
3233
):

backend/tests/utils/test_generic_utils.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
"""Generic utils for tests"""
22

3-
43
import unittest
54
from src.models.utils.generic_utils import form_json
5+
from tests.utils.generic_utils import load_json_data
66

77

88
class TestFormJson(unittest.TestCase):
99
def setUp(self):
1010
self.baseurl = "https://api.service.nhs.uk/immunisation-fhir-api/Immunization"
1111
self.identifier = "https://supplierABC/identifiers/vacc|f10b59b3-fc73-4616-99c9-9e882ab31184"
1212
self.response = {
13-
"resource": {"resourceType": "Immunization", "id": "f10b59b3-fc73-4616-99c9-9e882ab31184", "status": "completed"},
13+
"resource": load_json_data("completed_covid19_immunization_event.json"),
1414
"id": "f10b59b3-fc73-4616-99c9-9e882ab31184",
15-
"version": 2,
15+
"version": "2",
1616
}
1717

18+
self.maxDiff = None
19+
1820
def test_no_response(self):
1921
out = form_json(None, None, self.identifier, self.baseurl)
2022
self.assertEqual(out["resourceType"], "Bundle")
@@ -45,7 +47,6 @@ def test_identifier_with_meta_element_truncates_to_meta(self):
4547
self.assertEqual(out["total"], 1)
4648
self.assertEqual(out["link"][0]["url"], f"{self.baseurl}?identifier={self.identifier}&_elements=meta")
4749
self.assertEqual(res["resourceType"], "Immunization")
48-
self.assertEqual(res["id"], self.response["id"])
4950
self.assertIn("meta", res)
5051
self.assertEqual(res["meta"]["versionId"], self.response["version"])
5152

e2e/test_search_immunization.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def test_search_backwards_compatible(self):
8888
vaccine_type=VaccineTypes.covid_19,
8989
)
9090
expected_imms_resource["id"] = imms_id
91+
expected_imms_resource["meta"] = {"versionId": "1"}
9192

9293
# When
9394
response = imms_api.search_immunizations(valid_nhs_number1, VaccineTypes.covid_19)

0 commit comments

Comments
 (0)