Skip to content

Commit e5ba64d

Browse files
committed
add tests for fhir_server mode
1 parent 8b40133 commit e5ba64d

3 files changed

Lines changed: 370 additions & 0 deletions

File tree

tests/fhir_server/__init__.py

Whitespace-only changes.

tests/fhir_server/conftest.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import pytest
2+
from aiohttp import web
3+
4+
from app.fhir_server.operations import routes
5+
from app.fhir_server.settings import FHIRAppSettings
6+
7+
8+
@pytest.fixture
9+
async def fhir_server_client(fhir_client, aiohttp_client):
10+
app = web.Application()
11+
app.add_routes(routes)
12+
app["client"] = fhir_client
13+
app["settings"] = FHIRAppSettings(
14+
JUTE_SERVICE="http://jute:8090/parse-template",
15+
FHIRPATH_MAPPING_SERVICE=None,
16+
)
17+
return await aiohttp_client(app)
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
import json
2+
from unittest.mock import AsyncMock, MagicMock, patch
3+
4+
from tests.factories import make_launch_context_ext, make_target_structure_map_ext
5+
6+
7+
def _make_fake_structure_map(template_str):
8+
"""Return a mock fhirpy resource whose get_by_path returns template_str."""
9+
fake_sm = MagicMock()
10+
fake_sm.get_by_path.return_value = template_str
11+
mock_sm_set = MagicMock()
12+
mock_sm_set.search.return_value.get = AsyncMock(return_value=fake_sm)
13+
return mock_sm_set
14+
15+
16+
async def test_healthcheck(fhir_server_client):
17+
resp = await fhir_server_client.get("/healthcheck")
18+
assert resp.status == 200
19+
assert await resp.json() == {"status": "ok"}
20+
21+
22+
async def test_resolve_expression(fhir_server_client):
23+
body = {"env": {"patient_id": "p-1"}, "expression": "/Patient?_id={{%patient_id}}"}
24+
resp = await fhir_server_client.post("/Questionnaire/$resolve-expression", json=body)
25+
assert resp.status == 200
26+
assert await resp.json() == "/Patient?_id=p-1"
27+
28+
29+
async def test_assemble_handler(fhir_server_client, fhir_client, safe_db):
30+
q = fhir_client.resource(
31+
"Questionnaire",
32+
status="active",
33+
item=[{"linkId": "q1", "type": "display"}],
34+
)
35+
await q.save()
36+
37+
resp = await fhir_server_client.get(f"/Questionnaire/{q.id}/$assemble")
38+
assert resp.status == 200
39+
result = await resp.json()
40+
assert "id" not in result
41+
assert any(
42+
"assembledFrom" in e.get("url", "") for e in result.get("extension", [])
43+
)
44+
45+
46+
async def test_constraint_check_no_errors(fhir_server_client):
47+
qr = {"resourceType": "QuestionnaireResponse", "status": "completed"}
48+
parameters = {
49+
"resourceType": "Parameters",
50+
"parameter": [
51+
{
52+
"name": "questionnaire",
53+
"resource": {"resourceType": "Questionnaire", "status": "active"},
54+
},
55+
{"name": "questionnaire_response", "resource": qr},
56+
],
57+
}
58+
59+
resp = await fhir_server_client.post(
60+
"/QuestionnaireResponse/$constraint-check", json=parameters
61+
)
62+
assert resp.status == 200
63+
result = await resp.json()
64+
assert result["resourceType"] == "QuestionnaireResponse"
65+
66+
67+
async def test_questionnaire_context(fhir_server_client):
68+
parameters = {
69+
"resourceType": "Parameters",
70+
"parameter": [
71+
{
72+
"name": "questionnaire",
73+
"resource": {
74+
"resourceType": "Questionnaire",
75+
"status": "active",
76+
"item": [],
77+
},
78+
},
79+
],
80+
}
81+
82+
resp = await fhir_server_client.post("/Questionnaire/$context", json=parameters)
83+
assert resp.status == 200
84+
result = await resp.json()
85+
assert "Questionnaire" in result
86+
87+
88+
async def test_extract_collection_qr(fhir_server_client, fhir_client, safe_db):
89+
q = fhir_client.resource(
90+
"Questionnaire",
91+
status="active",
92+
item=[{"linkId": "q1", "type": "display"}],
93+
)
94+
await q.save()
95+
96+
qr = {
97+
"resourceType": "QuestionnaireResponse",
98+
"status": "completed",
99+
"questionnaire": q.id,
100+
}
101+
resp = await fhir_server_client.post("/Questionnaire/$extract", json=qr)
102+
assert resp.status == 200
103+
assert await resp.json() == []
104+
105+
106+
async def test_extract_collection_parameters(fhir_server_client):
107+
parameters = {
108+
"resourceType": "Parameters",
109+
"parameter": [
110+
{
111+
"name": "questionnaire",
112+
"resource": {
113+
"resourceType": "Questionnaire",
114+
"status": "active",
115+
"item": [],
116+
},
117+
},
118+
{
119+
"name": "questionnaire_response",
120+
"resource": {"resourceType": "QuestionnaireResponse", "status": "completed"},
121+
},
122+
],
123+
}
124+
125+
resp = await fhir_server_client.post("/Questionnaire/$extract", json=parameters)
126+
assert resp.status == 200
127+
assert await resp.json() == []
128+
129+
130+
async def test_extract_instance_qr(fhir_server_client, fhir_client, safe_db):
131+
q = fhir_client.resource(
132+
"Questionnaire",
133+
status="active",
134+
item=[{"linkId": "q1", "type": "display"}],
135+
)
136+
await q.save()
137+
138+
qr = {"resourceType": "QuestionnaireResponse", "status": "completed"}
139+
resp = await fhir_server_client.post(f"/Questionnaire/{q.id}/$extract", json=qr)
140+
assert resp.status == 200
141+
assert await resp.json() == []
142+
143+
144+
async def test_extract_instance_parameters(fhir_server_client, fhir_client, safe_db):
145+
q = fhir_client.resource(
146+
"Questionnaire",
147+
status="active",
148+
item=[{"linkId": "q1", "type": "display"}],
149+
)
150+
await q.save()
151+
152+
parameters = {
153+
"resourceType": "Parameters",
154+
"parameter": [
155+
{
156+
"name": "questionnaire_response",
157+
"resource": {"resourceType": "QuestionnaireResponse", "status": "completed"},
158+
},
159+
],
160+
}
161+
resp = await fhir_server_client.post(f"/Questionnaire/{q.id}/$extract", json=parameters)
162+
assert resp.status == 200
163+
assert await resp.json() == []
164+
165+
166+
async def test_extract_instance_missing_qr_error(fhir_server_client, fhir_client, safe_db):
167+
q = fhir_client.resource(
168+
"Questionnaire",
169+
status="active",
170+
item=[{"linkId": "q1", "type": "display"}],
171+
)
172+
await q.save()
173+
174+
parameters = {"resourceType": "Parameters", "parameter": []}
175+
resp = await fhir_server_client.post(f"/Questionnaire/{q.id}/$extract", json=parameters)
176+
assert resp.status == 500
177+
178+
179+
async def test_extract_instance_bad_resource_type_error(fhir_server_client, fhir_client, safe_db):
180+
q = fhir_client.resource(
181+
"Questionnaire",
182+
status="active",
183+
item=[{"linkId": "q1", "type": "display"}],
184+
)
185+
await q.save()
186+
187+
resp = await fhir_server_client.post(
188+
f"/Questionnaire/{q.id}/$extract",
189+
json={"resourceType": "Patient"},
190+
)
191+
assert resp.status == 500
192+
193+
194+
async def test_populate_collection(fhir_server_client):
195+
parameters = {
196+
"resourceType": "Parameters",
197+
"parameter": [
198+
{
199+
"name": "questionnaire",
200+
"resource": {
201+
"resourceType": "Questionnaire",
202+
"id": "test-q",
203+
"status": "active",
204+
"item": [],
205+
},
206+
},
207+
],
208+
}
209+
210+
resp = await fhir_server_client.post("/Questionnaire/$populate", json=parameters)
211+
assert resp.status == 200
212+
result = await resp.json()
213+
assert result["resourceType"] == "QuestionnaireResponse"
214+
215+
216+
async def test_populate_collection_missing_questionnaire(fhir_server_client):
217+
# Capital-Q "Questionnaire" with empty resource → env["Questionnaire"] = {} → falsy → 422
218+
parameters = {
219+
"resourceType": "Parameters",
220+
"parameter": [
221+
{"name": "Questionnaire", "resource": {}},
222+
],
223+
}
224+
225+
resp = await fhir_server_client.post("/Questionnaire/$populate", json=parameters)
226+
assert resp.status == 422
227+
result = await resp.json()
228+
assert result["error"] == "bad_request"
229+
230+
231+
async def test_populate_instance(fhir_server_client, fhir_client, safe_db):
232+
q = fhir_client.resource(
233+
"Questionnaire",
234+
status="active",
235+
item=[{"linkId": "q1", "type": "display"}],
236+
)
237+
await q.save()
238+
239+
resp = await fhir_server_client.post(
240+
f"/Questionnaire/{q.id}/$populate",
241+
json={"resourceType": "Parameters", "parameter": []},
242+
)
243+
assert resp.status == 200
244+
result = await resp.json()
245+
assert result["resourceType"] == "QuestionnaireResponse"
246+
247+
248+
async def test_extract_collection_with_jute_template(fhir_server_client, fhir_client, safe_db):
249+
# entry must be non-empty; Aidbox rejects Bundle.entry: []
250+
template_str = json.dumps({
251+
"resourceType": "Bundle",
252+
"type": "transaction",
253+
"entry": [
254+
{
255+
"request": {"method": "PUT", "url": "Patient/jute-extract-col"},
256+
"resource": {"resourceType": "Patient", "id": "jute-extract-col"},
257+
}
258+
],
259+
})
260+
mock_sm_set = _make_fake_structure_map(template_str)
261+
262+
parameters = {
263+
"resourceType": "Parameters",
264+
"parameter": [
265+
{
266+
"name": "questionnaire",
267+
"resource": {
268+
"resourceType": "Questionnaire",
269+
"status": "active",
270+
"item": [],
271+
"extension": [make_target_structure_map_ext("StructureMap/fake-sm")],
272+
},
273+
},
274+
{
275+
"name": "questionnaire_response",
276+
"resource": {"resourceType": "QuestionnaireResponse", "status": "completed"},
277+
},
278+
],
279+
}
280+
281+
with patch.object(fhir_client, "resources", return_value=mock_sm_set):
282+
resp = await fhir_server_client.post("/Questionnaire/$extract", json=parameters)
283+
284+
assert resp.status == 200
285+
result = await resp.json()
286+
assert isinstance(result, list)
287+
assert len(result) == 1
288+
289+
290+
async def test_extract_instance_with_jute_template(fhir_server_client, fhir_client, safe_db):
291+
q = fhir_client.resource(
292+
"Questionnaire",
293+
status="active",
294+
item=[{"linkId": "q1", "type": "display"}],
295+
extension=[make_target_structure_map_ext("StructureMap/fake-sm")],
296+
)
297+
await q.save()
298+
299+
template_str = json.dumps({
300+
"resourceType": "Bundle",
301+
"type": "transaction",
302+
"entry": [
303+
{
304+
"request": {"method": "PUT", "url": "Patient/jute-extract-inst"},
305+
"resource": {"resourceType": "Patient", "id": "jute-extract-inst"},
306+
}
307+
],
308+
})
309+
mock_sm_set = _make_fake_structure_map(template_str)
310+
original_resources = fhir_client.resources
311+
312+
def selective_resources(resource_type):
313+
if resource_type == "StructureMap":
314+
return mock_sm_set
315+
return original_resources(resource_type)
316+
317+
qr = {"resourceType": "QuestionnaireResponse", "status": "completed"}
318+
with patch.object(fhir_client, "resources", side_effect=selective_resources):
319+
resp = await fhir_server_client.post(f"/Questionnaire/{q.id}/$extract", json=qr)
320+
321+
assert resp.status == 200
322+
result = await resp.json()
323+
assert isinstance(result, list)
324+
assert len(result) == 1
325+
326+
327+
async def test_extract_instance_parameters_with_launch_context(
328+
fhir_server_client, fhir_client, safe_db
329+
):
330+
q = fhir_client.resource(
331+
"Questionnaire",
332+
status="active",
333+
item=[{"linkId": "q1", "type": "display"}],
334+
extension=[make_launch_context_ext("LaunchPatient", "Patient")],
335+
)
336+
await q.save()
337+
338+
parameters = {
339+
"resourceType": "Parameters",
340+
"parameter": [
341+
{
342+
"name": "questionnaire_response",
343+
"resource": {"resourceType": "QuestionnaireResponse", "status": "completed"},
344+
},
345+
{
346+
"name": "LaunchPatient",
347+
"resource": {"resourceType": "Patient"},
348+
},
349+
],
350+
}
351+
resp = await fhir_server_client.post(f"/Questionnaire/{q.id}/$extract", json=parameters)
352+
assert resp.status == 200
353+
assert await resp.json() == []

0 commit comments

Comments
 (0)