Skip to content

Commit 7721d42

Browse files
Warn instead of error for Bundle.link mismatch
Fixes an issue in production. The `Bundle.link` is mismatching between the request we send and the response which they return, which means that we're requesting vaccination records for more programmes than we have permission to view. This was by design; we instead filter the records in Mavis before saving them. The error raised by Mavis causes the job to fail, meaning that no new vaccination records are being consumed into Mavis. Jira-Issue: MAV-2652
1 parent c2f0ea6 commit 7721d42

5 files changed

Lines changed: 314 additions & 5 deletions

File tree

app/lib/nhs/immunisations_api.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,8 +430,10 @@ def check_bundle_link_params(bundle, request_params)
430430

431431
unless tweaked_bundle_params == request_params ||
432432
bundle_params == request_params
433-
raise NHS::ImmunisationsAPI::BundleLinkParamsMismatch,
434-
"Bundle link parameters do not match request parameters: #{tweaked_bundle_params} != #{request_params}"
433+
message =
434+
"Bundle link parameters do not match request parameters: #{tweaked_bundle_params} != #{request_params}"
435+
Rails.logger.warn(message)
436+
Sentry.capture_exception(BundleLinkParamsMismatch.new(message))
435437
end
436438
end
437439

spec/components/app_vaccination_record_summary_component_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@
411411
context "when the imms_api_sync_job feature flag is enabled for any programme" do
412412
before do
413413
Flipper.disable(:imms_api_sync_job)
414-
Flipper.enable(:imms_api_sync_job, CachedProgramme.flu)
414+
Flipper.enable(:imms_api_sync_job, Programme.flu)
415415
end
416416

417417
it_behaves_like "should have a `Synced with NHS England?` row"
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
{
2+
"resourceType": "Bundle",
3+
"type": "searchset",
4+
"link": [
5+
{
6+
"relation": "self",
7+
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?-date.from=2025-08-01\u0026-date.to=2025-10-01\u0026immunization.target=FLU,HPV,MENACWY,3IN1,MMR\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
8+
}
9+
],
10+
"entry": [
11+
{
12+
"fullUrl": "https://api.service.nhs.uk/immunisation-fhir-api/Immunization/4e7f3c91-a14d-4139-bbcf-859e998d2028",
13+
"resource": {
14+
"resourceType": "Immunization",
15+
"id": "4e7f3c91-a14d-4139-bbcf-859e998d2028",
16+
"extension": [
17+
{
18+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
19+
"valueCodeableConcept": {
20+
"coding": [
21+
{
22+
"system": "http://snomed.info/sct",
23+
"code": "884861000000100",
24+
"display": "Seasonal influenza vaccination (procedure)"
25+
}
26+
]
27+
}
28+
}
29+
],
30+
"identifier": [
31+
{
32+
"use": "official",
33+
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
34+
"value": "71f538d8-1171-4204-aee4-17ff0b0ba0b0"
35+
}
36+
],
37+
"status": "completed",
38+
"vaccineCode": {
39+
"coding": [
40+
{
41+
"system": "http://snomed.info/sct",
42+
"code": "43208811000001106",
43+
"display": "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd) (product)"
44+
}
45+
]
46+
},
47+
"patient": {
48+
"reference": "urn:uuid:e06dbb8d-ef9b-454f-9c5f-fde8460a0b6a",
49+
"type": "Patient",
50+
"identifier": {
51+
"system": "https://fhir.nhs.uk/Id/nhs-number",
52+
"value": "9449308357"
53+
}
54+
},
55+
"occurrenceDateTime": "2025-08-22T14:16:03+01:00",
56+
"recorded": "2025-08-22T14:16:05.246000+01:00",
57+
"primarySource": true,
58+
"location": {
59+
"identifier": {
60+
"system": "https://fhir.hl7.org.uk/Id/urn-school-number",
61+
"value": "100001"
62+
}
63+
},
64+
"manufacturer": {
65+
"display": "AstraZeneca"
66+
},
67+
"lotNumber": "BU5086",
68+
"expirationDate": "2025-09-30",
69+
"site": {
70+
"coding": [
71+
{
72+
"system": "http://snomed.info/sct",
73+
"code": "279549004",
74+
"display": "Nasal cavity structure"
75+
}
76+
]
77+
},
78+
"route": {
79+
"coding": [
80+
{
81+
"system": "http://snomed.info/sct",
82+
"code": "46713006",
83+
"display": "Nasal"
84+
}
85+
]
86+
},
87+
"doseQuantity": {
88+
"value": 0.2,
89+
"unit": "ml",
90+
"system": "http://snomed.info/sct",
91+
"code": "258773002"
92+
},
93+
"performer": [
94+
{
95+
"actor": {
96+
"type": "Organization",
97+
"identifier": {
98+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
99+
"value": "R1L"
100+
}
101+
}
102+
}
103+
],
104+
"reasonCode": [
105+
{
106+
"coding": [
107+
{
108+
"system": "http://snomed.info/sct",
109+
"code": "723620004"
110+
}
111+
]
112+
}
113+
],
114+
"protocolApplied": [
115+
{
116+
"targetDisease": [
117+
{
118+
"coding": [
119+
{
120+
"system": "http://snomed.info/sct",
121+
"code": "6142004",
122+
"display": "Influenza"
123+
}
124+
]
125+
}
126+
],
127+
"doseNumberPositiveInt": 1
128+
}
129+
]
130+
},
131+
"search": {
132+
"mode": "match"
133+
}
134+
},
135+
{
136+
"fullUrl": "https://api.service.nhs.uk/immunisation-fhir-api/Immunization/871f91fa-385e-4f42-8e0b-98e6c9a592dd",
137+
"resource": {
138+
"resourceType": "Immunization",
139+
"id": "871f91fa-385e-4f42-8e0b-98e6c9a592dd",
140+
"extension": [
141+
{
142+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
143+
"valueCodeableConcept": {
144+
"coding": [
145+
{
146+
"system": "http://snomed.info/sct",
147+
"code": "884861000000100",
148+
"display": "Seasonal influenza vaccination (procedure)"
149+
}
150+
]
151+
}
152+
}
153+
],
154+
"identifier": [
155+
{
156+
"use": "official",
157+
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
158+
"value": "18441e7b-b652-4d8c-980c-b60009f95942"
159+
}
160+
],
161+
"status": "completed",
162+
"vaccineCode": {
163+
"coding": [
164+
{
165+
"system": "http://snomed.info/sct",
166+
"code": "43208811000001106",
167+
"display": "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd) (product)"
168+
}
169+
]
170+
},
171+
"patient": {
172+
"reference": "urn:uuid:e06dbb8d-ef9b-454f-9c5f-fde8460a0b6a",
173+
"type": "Patient",
174+
"identifier": {
175+
"system": "https://fhir.nhs.uk/Id/nhs-number",
176+
"value": "9449308357"
177+
}
178+
},
179+
"occurrenceDateTime": "2025-08-26T12:48:01+01:00",
180+
"recorded": "2025-08-26T12:48:58.741000+01:00",
181+
"primarySource": true,
182+
"location": {
183+
"identifier": {
184+
"system": "https://fhir.hl7.org.uk/Id/urn-school-number",
185+
"value": "100005"
186+
}
187+
},
188+
"manufacturer": {
189+
"display": "AstraZeneca"
190+
},
191+
"lotNumber": "IK1741",
192+
"expirationDate": "2025-09-25",
193+
"site": {
194+
"coding": [
195+
{
196+
"system": "http://snomed.info/sct",
197+
"code": "279549004",
198+
"display": "Nasal cavity structure"
199+
}
200+
]
201+
},
202+
"route": {
203+
"coding": [
204+
{
205+
"system": "http://snomed.info/sct",
206+
"code": "46713006",
207+
"display": "Nasal"
208+
}
209+
]
210+
},
211+
"doseQuantity": {
212+
"value": 0.2,
213+
"unit": "ml",
214+
"system": "http://snomed.info/sct",
215+
"code": "258773002"
216+
},
217+
"performer": [
218+
{
219+
"actor": {
220+
"type": "Organization",
221+
"identifier": {
222+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
223+
"value": "R1L"
224+
}
225+
}
226+
}
227+
],
228+
"reasonCode": [
229+
{
230+
"coding": [
231+
{
232+
"system": "http://snomed.info/sct",
233+
"code": "723620004"
234+
}
235+
]
236+
}
237+
],
238+
"protocolApplied": [
239+
{
240+
"targetDisease": [
241+
{
242+
"coding": [
243+
{
244+
"system": "http://snomed.info/sct",
245+
"code": "6142004",
246+
"display": "Influenza"
247+
}
248+
]
249+
}
250+
],
251+
"doseNumberPositiveInt": 1
252+
}
253+
]
254+
},
255+
"search": {
256+
"mode": "match"
257+
}
258+
},
259+
{
260+
"fullUrl": "urn:uuid:e06dbb8d-ef9b-454f-9c5f-fde8460a0b6a",
261+
"resource": {
262+
"resourceType": "Patient",
263+
"id": "9449308357",
264+
"identifier": [
265+
{
266+
"system": "https://fhir.nhs.uk/Id/nhs-number",
267+
"value": "9449308357"
268+
}
269+
]
270+
},
271+
"search": {
272+
"mode": "include"
273+
}
274+
}
275+
],
276+
"total": 2
277+
}

spec/jobs/search_vaccination_records_in_nhs_job_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,5 +659,24 @@
659659
include_examples "calls StatusUpdater"
660660
end
661661
end
662+
663+
context "with a mismatching `Bundle.link`" do
664+
let(:body) do
665+
file_fixture("fhir/search_response_mismatching_bundle_link.json").read
666+
end
667+
668+
it "raises a warning, and sends to Sentry" do
669+
expect(Rails.logger).to receive(:warn)
670+
expect(Sentry).to receive(:capture_exception).with(
671+
NHS::ImmunisationsAPI::BundleLinkParamsMismatch
672+
)
673+
674+
perform
675+
end
676+
677+
it "adds 2 vaccination records anyway" do
678+
expect { perform }.to change { patient.vaccination_records.count }.by(2)
679+
end
680+
end
662681
end
663682
end

spec/lib/nhs/immunisations_api_spec.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -905,10 +905,21 @@
905905
}
906906
end
907907

908-
it "raises an error" do
909-
expect { perform_request }.to raise_error(
908+
it "raises a warning, and sends to Sentry" do
909+
expect(Rails.logger).to receive(:warn)
910+
expect(Sentry).to receive(:capture_exception).with(
910911
NHS::ImmunisationsAPI::BundleLinkParamsMismatch
911912
)
913+
914+
perform_request
915+
end
916+
917+
it "continues the job and consumes the records anyway" do
918+
expect(perform_request).to be_a FHIR::Bundle
919+
expect(perform_request.total).to be 2
920+
expect(perform_request.entry.size).to be 3
921+
expect(perform_request.entry[0].resource).to be_a FHIR::Immunization
922+
expect(perform_request.entry[1].resource).to be_a FHIR::Immunization
912923
end
913924
end
914925

0 commit comments

Comments
 (0)