Coverage for cc_modules/tests/cc_fhir_tests.py: 17%
424 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""camcops_server/cc_modules/tests/cc_fhir_tests.py
5===============================================================================
7 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
8 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27"""
29import datetime
30import json
31import logging
32from typing import Dict
33from unittest import mock
35from cardinal_pythonlib.httpconst import HttpMethod
36from cardinal_pythonlib.nhs import generate_random_nhs_number
37import pendulum
38from requests.exceptions import HTTPError
40from camcops_server.cc_modules.cc_constants import FHIRConst as Fc, JSON_INDENT
41from camcops_server.cc_modules.cc_exportmodels import (
42 ExportedTask,
43 ExportedTaskFhir,
44)
45from camcops_server.cc_modules.cc_exportrecipient import ExportRecipient
46from camcops_server.cc_modules.cc_exportrecipientinfo import (
47 ExportRecipientInfo,
48)
49from camcops_server.cc_modules.cc_fhir import (
50 fhir_reference_from_identifier,
51 fhir_sysval_from_id,
52 FhirExportException,
53 FhirTaskExporter,
54)
55from camcops_server.cc_modules.cc_pyramid import Routes
56from camcops_server.cc_modules.cc_unittest import DemoDatabaseTestCase
57from camcops_server.cc_modules.cc_version_string import (
58 CAMCOPS_SERVER_VERSION_STRING,
59)
60from camcops_server.tasks.apeqpt import Apeqpt
61from camcops_server.tasks.bmi import Bmi
62from camcops_server.tasks.gad7 import Gad7
63from camcops_server.tasks.diagnosis import (
64 DiagnosisIcd10,
65 DiagnosisIcd10Item,
66 DiagnosisIcd9CM,
67 DiagnosisIcd9CMItem,
68)
69from camcops_server.tasks.phq9 import Phq9
71log = logging.getLogger()
74# =============================================================================
75# Constants
76# =============================================================================
78TEST_NHS_NUMBER = generate_random_nhs_number()
79TEST_RIO_NUMBER = 12345
80TEST_FORENAME = "Gwendolyn"
81TEST_SURNAME = "Ryann"
82TEST_SEX = "F"
85# =============================================================================
86# Helper classes
87# =============================================================================
90class MockFhirTaskExporter(FhirTaskExporter):
91 pass
94class MockFhirResponse(mock.Mock):
95 def __init__(self, response_json: Dict):
96 super().__init__(
97 text=json.dumps(response_json),
98 json=mock.Mock(return_value=response_json),
99 )
102class FhirExportTestCase(DemoDatabaseTestCase):
103 def setUp(self) -> None:
104 super().setUp()
105 recipientinfo = ExportRecipientInfo()
107 self.recipient = ExportRecipient(recipientinfo)
108 self.recipient.primary_idnum = self.rio_iddef.which_idnum
109 self.recipient.fhir_api_url = "https://www.example.com/fhir"
111 # auto increment doesn't work for BigInteger with SQLite
112 self.recipient.id = 1
113 self.recipient.recipient_name = "test"
115 self.camcops_root_url = self.req.route_url(Routes.HOME).rstrip("/")
116 # ... no trailing slash
118 def create_fhir_patient(self) -> None:
119 self.patient = self.create_patient(
120 forename=TEST_FORENAME, surname=TEST_SURNAME, sex=TEST_SEX
121 )
122 self.patient_nhs = self.create_patient_idnum(
123 patient_id=self.patient.id,
124 which_idnum=self.nhs_iddef.which_idnum,
125 idnum_value=TEST_NHS_NUMBER,
126 )
127 self.patient_rio = self.create_patient_idnum(
128 patient_id=self.patient.id,
129 which_idnum=self.rio_iddef.which_idnum,
130 idnum_value=TEST_RIO_NUMBER,
131 )
134# =============================================================================
135# A generic patient-based task: PHQ9
136# =============================================================================
139class FhirTaskExporterPhq9Tests(FhirExportTestCase):
140 def create_tasks(self) -> None:
141 self.create_fhir_patient()
143 self.task = Phq9()
144 self.apply_standard_task_fields(self.task)
145 self.task.q1 = 0
146 self.task.q2 = 1
147 self.task.q3 = 2
148 self.task.q4 = 3
149 self.task.q5 = 0
150 self.task.q6 = 1
151 self.task.q7 = 2
152 self.task.q8 = 3
153 self.task.q9 = 0
154 self.task.q10 = 3
155 self.task.patient_id = self.patient.id
156 self.task.save_with_next_available_id(
157 self.req, self.patient._device_id
158 )
159 self.dbsession.commit()
161 def test_patient_exported(self) -> None:
162 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
163 exported_task_fhir = ExportedTaskFhir(exported_task)
165 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
167 response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE}
169 with mock.patch.object(
170 exporter.client.server,
171 "post_json",
172 return_value=MockFhirResponse(response_json),
173 ) as mock_post:
174 exporter.export_task()
176 args, kwargs = mock_post.call_args
178 sent_json = args[1]
180 self.assertEqual(sent_json[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_BUNDLE)
181 self.assertEqual(sent_json[Fc.TYPE], Fc.TRANSACTION)
183 patient = sent_json[Fc.ENTRY][0][Fc.RESOURCE]
184 self.assertEqual(patient[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_PATIENT)
186 identifier = patient[Fc.IDENTIFIER]
187 idnum_value = self.patient_rio.idnum_value
189 patient_id = self.patient.get_fhir_identifier(self.req, self.recipient)
191 self.assertEqual(identifier[0][Fc.SYSTEM], patient_id.system)
192 self.assertEqual(identifier[0][Fc.VALUE], str(idnum_value))
194 self.assertEqual(
195 patient[Fc.NAME][0][Fc.NAME_FAMILY], self.patient.surname
196 )
197 self.assertEqual(
198 patient[Fc.NAME][0][Fc.NAME_GIVEN], [self.patient.forename]
199 )
200 self.assertEqual(patient[Fc.GENDER], Fc.GENDER_FEMALE)
202 request = sent_json[Fc.ENTRY][0][Fc.REQUEST]
203 self.assertEqual(request[Fc.METHOD], HttpMethod.POST)
204 self.assertEqual(request[Fc.URL], Fc.RESOURCE_TYPE_PATIENT)
205 self.assertEqual(
206 request[Fc.IF_NONE_EXIST],
207 fhir_reference_from_identifier(patient_id),
208 )
210 def test_questionnaire_exported(self) -> None:
211 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
212 exported_task_fhir = ExportedTaskFhir(exported_task)
214 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
216 response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE}
218 with mock.patch.object(
219 exporter.client.server,
220 "post_json",
221 return_value=MockFhirResponse(response_json),
222 ) as mock_post:
223 exporter.export_task()
225 args, kwargs = mock_post.call_args
227 sent_json = args[1]
229 questionnaire = sent_json[Fc.ENTRY][1][Fc.RESOURCE]
230 self.assertEqual(
231 questionnaire[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE
232 )
233 self.assertEqual(questionnaire[Fc.STATUS], Fc.QSTATUS_ACTIVE)
235 identifier = questionnaire[Fc.IDENTIFIER]
237 questionnaire_url = (
238 f"{self.camcops_root_url}/{Routes.FHIR_QUESTIONNAIRE_SYSTEM}"
239 )
240 self.assertEqual(identifier[0][Fc.SYSTEM], questionnaire_url)
241 self.assertEqual(
242 identifier[0][Fc.VALUE], f"phq9/{CAMCOPS_SERVER_VERSION_STRING}"
243 )
245 question_1 = questionnaire[Fc.ITEM][0]
246 question_10 = questionnaire[Fc.ITEM][9]
247 self.assertEqual(question_1[Fc.LINK_ID], "q1")
248 self.assertEqual(
249 question_1[Fc.TEXT],
250 "1. Little interest or pleasure in doing things",
251 )
252 self.assertEqual(question_1[Fc.TYPE], Fc.QITEM_TYPE_CHOICE)
254 options = question_1[Fc.ANSWER_OPTION]
255 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0")
256 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "Not at all")
258 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1")
259 self.assertEqual(
260 options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Several days"
261 )
263 self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2")
264 self.assertEqual(
265 options[2][Fc.VALUE_CODING][Fc.DISPLAY], "More than half the days"
266 )
268 self.assertEqual(options[3][Fc.VALUE_CODING][Fc.CODE], "3")
269 self.assertEqual(
270 options[3][Fc.VALUE_CODING][Fc.DISPLAY], "Nearly every day"
271 )
273 self.assertEqual(question_10[Fc.LINK_ID], "q10")
274 self.assertEqual(
275 question_10[Fc.TEXT],
276 (
277 "10. If you checked off any problems, how difficult have "
278 "these problems made it for you to do your work, take care of "
279 "things at home, or get along with other people?"
280 ),
281 )
282 self.assertEqual(question_10[Fc.TYPE], Fc.QITEM_TYPE_CHOICE)
283 options = question_10[Fc.ANSWER_OPTION]
284 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0")
285 self.assertEqual(
286 options[0][Fc.VALUE_CODING][Fc.DISPLAY], "Not difficult at all"
287 )
289 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1")
290 self.assertEqual(
291 options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Somewhat difficult"
292 )
294 self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2")
295 self.assertEqual(
296 options[2][Fc.VALUE_CODING][Fc.DISPLAY], "Very difficult"
297 )
299 self.assertEqual(options[3][Fc.VALUE_CODING][Fc.CODE], "3")
300 self.assertEqual(
301 options[3][Fc.VALUE_CODING][Fc.DISPLAY], "Extremely difficult"
302 )
304 self.assertEqual(len(questionnaire[Fc.ITEM]), 10)
306 request = sent_json[Fc.ENTRY][1][Fc.REQUEST]
307 self.assertEqual(request[Fc.METHOD], HttpMethod.POST)
308 self.assertEqual(request[Fc.URL], Fc.RESOURCE_TYPE_QUESTIONNAIRE)
309 q_id = self.task._get_fhir_questionnaire_id(self.req)
310 self.assertEqual(
311 request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(q_id)
312 )
314 def test_questionnaire_response_exported(self) -> None:
315 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
316 exported_task_fhir = ExportedTaskFhir(exported_task)
318 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
320 response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE}
322 with mock.patch.object(
323 exporter.client.server,
324 "post_json",
325 return_value=MockFhirResponse(response_json),
326 ) as mock_post:
327 exporter.export_task()
329 args, kwargs = mock_post.call_args
331 sent_json = args[1]
333 response = sent_json[Fc.ENTRY][2][Fc.RESOURCE]
334 self.assertEqual(
335 response[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE
336 )
338 q_id = self.task._get_fhir_questionnaire_id(self.req)
339 self.assertEqual(response[Fc.QUESTIONNAIRE], fhir_sysval_from_id(q_id))
340 self.assertEqual(
341 response[Fc.AUTHORED], self.task.when_created.isoformat()
342 )
343 self.assertEqual(response[Fc.STATUS], Fc.QSTATUS_COMPLETED)
345 subject = response[Fc.SUBJECT]
346 identifier = subject[Fc.IDENTIFIER]
347 self.assertEqual(subject[Fc.TYPE], Fc.RESOURCE_TYPE_PATIENT)
348 idnum_value = self.patient_rio.idnum_value
350 patient_id = self.patient.get_fhir_identifier(self.req, self.recipient)
351 if isinstance(identifier, list):
352 test_identifier = identifier[0]
353 else: # only one
354 test_identifier = identifier
355 self.assertEqual(test_identifier[Fc.SYSTEM], patient_id.system)
356 self.assertEqual(test_identifier[Fc.VALUE], str(idnum_value))
358 request = sent_json[Fc.ENTRY][2][Fc.REQUEST]
359 self.assertEqual(request[Fc.METHOD], HttpMethod.POST)
360 self.assertEqual(
361 request[Fc.URL], Fc.RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE
362 )
363 qr_id = self.task._get_fhir_questionnaire_response_id(self.req)
364 self.assertEqual(
365 request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(qr_id)
366 )
368 item_1 = response[Fc.ITEM][0]
369 item_10 = response[Fc.ITEM][9]
370 self.assertEqual(item_1[Fc.LINK_ID], "q1")
371 self.assertEqual(
372 item_1[Fc.TEXT], "1. Little interest or pleasure in doing things"
373 )
374 answer_1 = item_1[Fc.ANSWER][0]
375 # noinspection PyUnresolvedReferences
376 self.assertEqual(answer_1[Fc.VALUE_INTEGER], self.task.q1)
378 self.assertEqual(item_10[Fc.LINK_ID], "q10")
379 self.assertEqual(
380 item_10[Fc.TEXT],
381 (
382 "10. If you checked off any problems, how difficult have "
383 "these problems made it for you to do your work, take care of "
384 "things at home, or get along with other people?"
385 ),
386 )
387 answer_10 = item_10[Fc.ANSWER][0]
388 self.assertEqual(answer_10[Fc.VALUE_INTEGER], self.task.q10)
390 self.assertEqual(len(response[Fc.ITEM]), 10)
392 # noinspection PyUnresolvedReferences
393 def test_exported_task_saved(self) -> None:
394 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
395 # auto increment doesn't work for BigInteger with SQLite
396 exported_task.id = 1
397 self.dbsession.add(exported_task)
399 exported_task_fhir = ExportedTaskFhir(exported_task)
400 self.dbsession.add(exported_task_fhir)
402 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
404 response_json = {
405 Fc.RESOURCE_TYPE: Fc.RESOURCE_TYPE_BUNDLE,
406 Fc.ID: "cae48957-e7e6-4649-97f8-0a882076ad0a",
407 Fc.TYPE: Fc.TRANSACTION_RESPONSE,
408 Fc.LINK: [
409 {Fc.RELATION: Fc.SELF, Fc.URL: "http://localhost:8080/fhir"}
410 ],
411 Fc.ENTRY: [
412 {
413 Fc.RESPONSE: {
414 Fc.STATUS: Fc.RESPONSE_STATUS_200_OK,
415 Fc.LOCATION: "Patient/1/_history/1",
416 Fc.ETAG: "1",
417 }
418 },
419 {
420 Fc.RESPONSE: {
421 Fc.STATUS: Fc.RESPONSE_STATUS_200_OK,
422 Fc.LOCATION: "Questionnaire/26/_history/1",
423 Fc.ETAG: "1",
424 }
425 },
426 {
427 Fc.RESPONSE: {
428 Fc.STATUS: Fc.RESPONSE_STATUS_201_CREATED,
429 Fc.LOCATION: "QuestionnaireResponse/42/_history/1",
430 Fc.ETAG: "1",
431 Fc.LAST_MODIFIED: "2021-05-24T09:30:11.098+00:00",
432 }
433 },
434 ],
435 }
437 with mock.patch.object(
438 exporter.client.server,
439 "post_json",
440 return_value=MockFhirResponse(response_json),
441 ):
442 exporter.export_task()
444 self.dbsession.commit()
446 entries = (
447 exported_task_fhir.entries
448 ) # type: List[ExportedTaskFhirEntry] # noqa
450 entries.sort(key=lambda e: e.location)
452 self.assertEqual(entries[0].status, Fc.RESPONSE_STATUS_200_OK)
453 self.assertEqual(entries[0].location, "Patient/1/_history/1")
454 self.assertEqual(entries[0].etag, "1")
456 self.assertEqual(entries[1].status, Fc.RESPONSE_STATUS_200_OK)
457 self.assertEqual(entries[1].location, "Questionnaire/26/_history/1")
458 self.assertEqual(entries[1].etag, "1")
460 self.assertEqual(entries[2].status, Fc.RESPONSE_STATUS_201_CREATED)
461 self.assertEqual(
462 entries[2].location, "QuestionnaireResponse/42/_history/1"
463 )
464 self.assertEqual(entries[2].etag, "1")
465 self.assertEqual(
466 entries[2].last_modified,
467 datetime.datetime(2021, 5, 24, 9, 30, 11, 98000),
468 )
470 def test_raises_when_http_error(self) -> None:
471 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
472 exported_task_fhir = ExportedTaskFhir(exported_task)
474 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
476 errmsg = "Something bad happened"
477 with mock.patch.object(
478 exporter.client.server,
479 "post_json",
480 side_effect=HTTPError(response=mock.Mock(text=errmsg)),
481 ):
482 with self.assertRaises(FhirExportException) as cm:
483 exporter.export_task()
485 message = str(cm.exception)
486 self.assertIn(errmsg, message)
488 def test_raises_when_fhirclient_raises(self) -> None:
489 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
490 exported_task_fhir = ExportedTaskFhir(exported_task)
492 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
494 exporter.client.server = None
495 with self.assertRaises(FhirExportException) as cm:
496 exporter.export_task()
498 message = str(cm.exception)
499 self.assertIn("Cannot create a resource without a server", message)
501 def test_raises_for_missing_api_url(self) -> None:
502 self.recipient.fhir_api_url = ""
503 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
504 exported_task_fhir = ExportedTaskFhir(exported_task)
506 with self.assertRaises(FhirExportException) as cm:
507 FhirTaskExporter(self.req, exported_task_fhir)
509 message = str(cm.exception)
510 self.assertIn("must be initialized with `base_uri`", message)
513# =============================================================================
514# A generic anonymous task: APEQPT
515# =============================================================================
517APEQPT_Q_WHEN = "Date and time the assessment tool was completed"
518OFFERED_PREFERENCE = "Have you been offered your preference?"
519SATISFIED_ASSESSMENT = "How satisfied were you with your assessment?"
520TELL_US = (
521 "Please use this space to tell us about your experience of our service."
522)
523PREFER_ANY = "Do you prefer any of the treatments among the options available?"
524GIVEN_INFO = (
525 "Were you given information about options for choosing a "
526 "treatment that is appropriate for your problems?"
527)
528APEQ_SATIS_A4 = "Completely satisfied"
529APEQ_SATIS_A3 = "Mostly satisfied"
530APEQ_SATIS_A2 = "Neither satisfied nor dissatisfied"
531APEQ_SATIS_A1 = "Not satisfied"
532APEQ_SATIS_A0 = "Not at all satisfied"
535class FhirTaskExporterAnonymousTests(FhirExportTestCase):
536 def create_tasks(self) -> None:
537 self.task = Apeqpt()
538 self.apply_standard_task_fields(self.task)
539 self.task.q_datetime = pendulum.now()
540 self.task.q1_choice = 0
541 self.task.q2_choice = 1
542 self.task.q3_choice = 2
543 self.task.q1_satisfaction = 3
544 self.task.q2_satisfaction = "Service experience"
546 self.task.save_with_next_available_id(self.req, self.server_device.id)
547 self.dbsession.commit()
549 def test_questionnaire_exported(self) -> None:
550 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
551 exported_task_fhir = ExportedTaskFhir(exported_task)
553 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
555 response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE}
557 with mock.patch.object(
558 exporter.client.server,
559 "post_json",
560 return_value=MockFhirResponse(response_json),
561 ) as mock_post:
562 exporter.export_task()
564 args, kwargs = mock_post.call_args
566 sent_json = args[1]
568 questionnaire = sent_json[Fc.ENTRY][0][Fc.RESOURCE]
569 self.assertEqual(
570 questionnaire[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE
571 )
572 self.assertEqual(questionnaire[Fc.STATUS], Fc.QSTATUS_ACTIVE)
574 identifier = questionnaire[Fc.IDENTIFIER]
576 questionnaire_url = (
577 f"{self.camcops_root_url}/{Routes.FHIR_QUESTIONNAIRE_SYSTEM}"
578 )
579 self.assertEqual(identifier[0][Fc.SYSTEM], questionnaire_url)
580 self.assertEqual(
581 identifier[0][Fc.VALUE], f"apeqpt/{CAMCOPS_SERVER_VERSION_STRING}"
582 )
584 self.assertEqual(len(questionnaire[Fc.ITEM]), 5)
585 (
586 q1_choice,
587 q2_choice,
588 q3_choice,
589 q1_satisfaction,
590 q2_satisfaction,
591 ) = questionnaire[Fc.ITEM]
593 # q1_choice
594 self.assertEqual(q1_choice[Fc.LINK_ID], "q1_choice")
595 self.assertEqual(q1_choice[Fc.TEXT], GIVEN_INFO)
596 self.assertEqual(q1_choice[Fc.TYPE], Fc.QITEM_TYPE_CHOICE)
598 options = q1_choice[Fc.ANSWER_OPTION]
599 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0")
600 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "No")
602 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1")
603 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Yes")
605 # q2_choice
606 self.assertEqual(q2_choice[Fc.LINK_ID], "q2_choice")
607 self.assertEqual(q2_choice[Fc.TEXT], PREFER_ANY)
608 self.assertEqual(q2_choice[Fc.TYPE], Fc.QITEM_TYPE_CHOICE)
609 options = q2_choice[Fc.ANSWER_OPTION]
610 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0")
611 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "No")
613 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1")
614 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Yes")
616 # q3_choice
617 self.assertEqual(q3_choice[Fc.LINK_ID], "q3_choice")
618 self.assertEqual(q3_choice[Fc.TEXT], OFFERED_PREFERENCE)
619 self.assertEqual(q3_choice[Fc.TYPE], Fc.QITEM_TYPE_CHOICE)
620 options = q3_choice[Fc.ANSWER_OPTION]
621 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0")
622 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "No")
624 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1")
625 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Yes")
627 self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2")
628 self.assertEqual(options[2][Fc.VALUE_CODING][Fc.DISPLAY], "N/A")
630 # q1_satisfaction
631 self.assertEqual(q1_satisfaction[Fc.LINK_ID], "q1_satisfaction")
632 self.assertEqual(q1_satisfaction[Fc.TEXT], SATISFIED_ASSESSMENT)
633 self.assertEqual(q1_satisfaction[Fc.TYPE], Fc.QITEM_TYPE_CHOICE)
634 options = q1_satisfaction[Fc.ANSWER_OPTION]
635 self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0")
636 self.assertEqual(
637 options[0][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A0
638 )
640 self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1")
641 self.assertEqual(
642 options[1][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A1
643 )
645 self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2")
646 self.assertEqual(
647 options[2][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A2
648 )
650 self.assertEqual(options[3][Fc.VALUE_CODING][Fc.CODE], "3")
651 self.assertEqual(
652 options[3][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A3
653 )
655 self.assertEqual(options[4][Fc.VALUE_CODING][Fc.CODE], "4")
656 self.assertEqual(
657 options[4][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A4
658 )
660 # q2 satisfaction
661 self.assertEqual(q2_satisfaction[Fc.LINK_ID], "q2_satisfaction")
662 self.assertEqual(q2_satisfaction[Fc.TEXT], TELL_US)
663 self.assertEqual(q2_satisfaction[Fc.TYPE], Fc.QITEM_TYPE_STRING)
665 request = sent_json[Fc.ENTRY][0][Fc.REQUEST]
666 self.assertEqual(request[Fc.METHOD], HttpMethod.POST)
667 self.assertEqual(request[Fc.URL], Fc.RESOURCE_TYPE_QUESTIONNAIRE)
668 q_id = self.task._get_fhir_questionnaire_id(self.req)
669 self.assertEqual(
670 request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(q_id)
671 )
673 def test_questionnaire_response_exported(self) -> None:
674 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
675 exported_task_fhir = ExportedTaskFhir(exported_task)
677 exporter = MockFhirTaskExporter(self.req, exported_task_fhir)
679 response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE}
681 with mock.patch.object(
682 exporter.client.server,
683 "post_json",
684 return_value=MockFhirResponse(response_json),
685 ) as mock_post:
686 exporter.export_task()
688 args, kwargs = mock_post.call_args
690 sent_json = args[1]
692 response = sent_json[Fc.ENTRY][1][Fc.RESOURCE]
693 self.assertEqual(
694 response[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE
695 )
696 q_id = self.task._get_fhir_questionnaire_id(self.req)
697 self.assertEqual(response[Fc.QUESTIONNAIRE], fhir_sysval_from_id(q_id))
698 self.assertEqual(
699 response[Fc.AUTHORED], self.task.when_created.isoformat()
700 )
701 self.assertEqual(response[Fc.STATUS], Fc.QSTATUS_COMPLETED)
703 request = sent_json[Fc.ENTRY][1][Fc.REQUEST]
704 self.assertEqual(request[Fc.METHOD], HttpMethod.POST)
705 self.assertEqual(request[Fc.URL], "QuestionnaireResponse")
706 qr_id = self.task._get_fhir_questionnaire_response_id(self.req)
707 self.assertEqual(
708 request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(qr_id)
709 )
711 self.assertEqual(len(response[Fc.ITEM]), 5)
712 (
713 q1_choice,
714 q2_choice,
715 q3_choice,
716 q1_satisfaction,
717 q2_satisfaction,
718 ) = response[Fc.ITEM]
720 # q1_choice
721 self.assertEqual(q1_choice[Fc.LINK_ID], "q1_choice")
722 self.assertEqual(q1_choice[Fc.TEXT], GIVEN_INFO)
723 q1_choice_answer = q1_choice[Fc.ANSWER][0]
724 self.assertEqual(
725 q1_choice_answer[Fc.VALUE_INTEGER], self.task.q1_choice
726 )
728 # q2_choice
729 self.assertEqual(q2_choice[Fc.LINK_ID], "q2_choice")
730 self.assertEqual(q2_choice[Fc.TEXT], PREFER_ANY)
731 q2_choice_answer = q2_choice[Fc.ANSWER][0]
732 self.assertEqual(
733 q2_choice_answer[Fc.VALUE_INTEGER], self.task.q2_choice
734 )
736 # q3_choice
737 self.assertEqual(q3_choice[Fc.LINK_ID], "q3_choice")
738 self.assertEqual(q3_choice[Fc.TEXT], OFFERED_PREFERENCE)
739 q3_choice_answer = q3_choice[Fc.ANSWER][0]
740 self.assertEqual(
741 q3_choice_answer[Fc.VALUE_INTEGER], self.task.q3_choice
742 )
744 # q1_satisfaction
745 self.assertEqual(q1_satisfaction[Fc.LINK_ID], "q1_satisfaction")
746 self.assertEqual(q1_satisfaction[Fc.TEXT], SATISFIED_ASSESSMENT)
747 q1_satisfaction_answer = q1_satisfaction[Fc.ANSWER][0]
748 self.assertEqual(
749 q1_satisfaction_answer[Fc.VALUE_INTEGER], self.task.q1_satisfaction
750 )
752 # q2 satisfaction
753 self.assertEqual(q2_satisfaction[Fc.LINK_ID], "q2_satisfaction")
754 self.assertEqual(q2_satisfaction[Fc.TEXT], TELL_US)
755 q2_satisfaction_answer = q2_satisfaction[Fc.ANSWER][0]
756 self.assertEqual(
757 q2_satisfaction_answer[Fc.VALUE_STRING], self.task.q2_satisfaction
758 )
761# =============================================================================
762# Tasks that add their own special details
763# =============================================================================
766class FhirTaskExporterBMITests(FhirExportTestCase):
767 def create_tasks(self) -> None:
768 self.create_fhir_patient()
770 self.task = Bmi()
771 self.apply_standard_task_fields(self.task)
772 self.task.mass_kg = 70
773 self.task.height_m = 1.8
774 self.task.waist_cm = 82
775 self.task.patient_id = self.patient.id
776 self.task.save_with_next_available_id(
777 self.req, self.patient._device_id
778 )
779 self.dbsession.commit()
781 def test_observations(self) -> None:
782 bundle = self.task.get_fhir_bundle(
783 self.req, self.recipient, skip_docs_if_other_content=True
784 )
785 bundle_str = json.dumps(bundle.as_json(), indent=JSON_INDENT)
786 log.debug(f"Bundle:\n{bundle_str}")
787 # The test is that it doesn't crash.
790class FhirTaskExporterDiagnosisIcd10Tests(FhirExportTestCase):
791 def create_tasks(self) -> None:
792 self.create_fhir_patient()
794 self.task = DiagnosisIcd10()
795 self.apply_standard_task_fields(self.task)
796 self.task.patient_id = self.patient.id
797 self.task.save_with_next_available_id(
798 self.req, self.patient._device_id
799 )
800 self.dbsession.commit()
802 # noinspection PyArgumentList
803 item1 = DiagnosisIcd10Item(
804 diagnosis_icd10_id=self.task.id,
805 seqnum=1,
806 code="F33.30",
807 description="Recurrent depressive disorder, current episode "
808 "severe with psychotic symptoms: "
809 "with mood-congruent psychotic symptoms",
810 comment="Cotard's syndrome",
811 )
812 self.apply_standard_db_fields(item1)
813 item1.save_with_next_available_id(self.req, self.task._device_id)
814 # noinspection PyArgumentList
815 item2 = DiagnosisIcd10Item(
816 diagnosis_icd10_id=self.task.id,
817 seqnum=2,
818 code="F43.1",
819 description="Post-traumatic stress disorder",
820 )
821 self.apply_standard_db_fields(item2)
822 item2.save_with_next_available_id(self.req, self.task._device_id)
824 def test_observations(self) -> None:
825 bundle = self.task.get_fhir_bundle(
826 self.req, self.recipient, skip_docs_if_other_content=True
827 )
828 bundle_str = json.dumps(bundle.as_json(), indent=JSON_INDENT)
829 log.debug(f"Bundle:\n{bundle_str}")
830 # The test is that it doesn't crash.
833class FhirTaskExporterDiagnosisIcd9CMTests(FhirExportTestCase):
834 def create_tasks(self) -> None:
835 self.create_fhir_patient()
837 self.task = DiagnosisIcd9CM()
838 self.apply_standard_task_fields(self.task)
839 self.task.patient_id = self.patient.id
840 self.task.save_with_next_available_id(
841 self.req, self.patient._device_id
842 )
843 self.dbsession.commit()
845 # noinspection PyArgumentList
846 item1 = DiagnosisIcd9CMItem(
847 diagnosis_icd9cm_id=self.task.id,
848 seqnum=1,
849 code="290.4",
850 description="Vascular dementia",
851 comment="or perhaps mixed dementia",
852 )
853 self.apply_standard_db_fields(item1)
854 item1.save_with_next_available_id(self.req, self.task._device_id)
855 # noinspection PyArgumentList
856 item2 = DiagnosisIcd9CMItem(
857 diagnosis_icd9cm_id=self.task.id,
858 seqnum=2,
859 code="303.0",
860 description="Acute alcoholic intoxication",
861 )
862 self.apply_standard_db_fields(item2)
863 item2.save_with_next_available_id(self.req, self.task._device_id)
865 def test_observations(self) -> None:
866 bundle = self.task.get_fhir_bundle(
867 self.req, self.recipient, skip_docs_if_other_content=True
868 )
869 bundle_str = json.dumps(bundle.as_json(), indent=JSON_INDENT)
870 log.debug(f"Bundle:\n{bundle_str}")
871 # The test is that it doesn't crash.
874class FhirTaskExporterGad7Tests(FhirExportTestCase):
875 """
876 The GAD7 is a standard questionnaire that we don't provide any special
877 FHIR support for; we rely on autodiscovery.
878 """
880 def create_tasks(self) -> None:
881 self.create_fhir_patient()
883 self.task = Gad7()
884 self.apply_standard_task_fields(self.task)
885 self.task.patient_id = self.patient.id
886 self.task.save_with_next_available_id(
887 self.req, self.patient._device_id
888 )
889 self.dbsession.commit()
891 def test_observations(self) -> None:
892 bundle = self.task.get_fhir_bundle(
893 self.req, self.recipient, skip_docs_if_other_content=True
894 )
895 bundle_str = json.dumps(bundle.as_json(), indent=JSON_INDENT)
896 log.critical(f"Bundle:\n{bundle_str}")
897 # The test is that it doesn't crash.