def export_task(req: "CamcopsRequest", recipient: ExportRecipient, task: Task) -> None: """ Exports a single task, checking that it remains valid to do so. Args: req: a :class:`camcops_server.cc_modules.cc_request.CamcopsRequest` recipient: an :class:`camcops_server.cc_modules.cc_exportmodels.ExportRecipient` task: a :class:`camcops_server.cc_modules.cc_task.Task` """ # noqa # Double-check it's OK! Just in case, for example, an old backend task has # persisted, or someone's managed to get an iffy back-end request in some # other way. if not recipient.is_task_suitable(task): # Warning will already have been emitted. return cfg = req.config lockfilename = cfg.get_export_lockfilename_task( recipient_name=recipient.recipient_name, basetable=task.tablename, pk=task.get_pk(), ) dbsession = req.dbsession try: with lockfile.FileLock(lockfilename, timeout=0): # doesn't wait # We recheck the export status once we hold the lock, in case # multiple jobs are competing to export it. if ExportedTask.task_already_exported( dbsession=dbsession, recipient_name=recipient.recipient_name, basetable=task.tablename, task_pk=task.get_pk()): log.info( "Task {!r} already exported to recipient {!r}; " "ignoring", task, recipient) # Not a warning; it's normal to see these because it allows the # client API to skip some checks for speed. return # OK; safe to export now. et = ExportedTask(recipient, task) dbsession.add(et) et.export(req) dbsession.commit() # so the ExportedTask is visible to others ASAP except lockfile.AlreadyLocked: log.warning( "Export logfile {!r} already locked by another process; " "aborting", lockfilename)
def test_raises_for_missing_api_url(self) -> None: self.recipient.fhir_api_url = "" exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) with self.assertRaises(FhirExportException) as cm: FhirTaskExporter(self.req, exported_task_fhir) message = str(cm.exception) self.assertIn("must be initialized with `base_uri`", message)
def test_raises_when_fhirclient_raises(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) exporter.client.server = None with self.assertRaises(FhirExportException) as cm: exporter.export_task() message = str(cm.exception) self.assertIn("Cannot create a resource without a server", message)
def test_patient_exported(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE} with mock.patch.object( exporter.client.server, "post_json", return_value=MockFhirResponse(response_json), ) as mock_post: exporter.export_task() args, kwargs = mock_post.call_args sent_json = args[1] self.assertEqual(sent_json[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_BUNDLE) self.assertEqual(sent_json[Fc.TYPE], Fc.TRANSACTION) patient = sent_json[Fc.ENTRY][0][Fc.RESOURCE] self.assertEqual(patient[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_PATIENT) identifier = patient[Fc.IDENTIFIER] idnum_value = self.patient_rio.idnum_value patient_id = self.patient.get_fhir_identifier(self.req, self.recipient) self.assertEqual(identifier[0][Fc.SYSTEM], patient_id.system) self.assertEqual(identifier[0][Fc.VALUE], str(idnum_value)) self.assertEqual( patient[Fc.NAME][0][Fc.NAME_FAMILY], self.patient.surname ) self.assertEqual( patient[Fc.NAME][0][Fc.NAME_GIVEN], [self.patient.forename] ) self.assertEqual(patient[Fc.GENDER], Fc.GENDER_FEMALE) request = sent_json[Fc.ENTRY][0][Fc.REQUEST] self.assertEqual(request[Fc.METHOD], HttpMethod.POST) self.assertEqual(request[Fc.URL], Fc.RESOURCE_TYPE_PATIENT) self.assertEqual( request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(patient_id), )
def test_raises_when_http_error(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) errmsg = "Something bad happened" with mock.patch.object( exporter.client.server, "post_json", side_effect=HTTPError(response=mock.Mock(text=errmsg)), ): with self.assertRaises(FhirExportException) as cm: exporter.export_task() message = str(cm.exception) self.assertIn(errmsg, message)
def test_questionnaire_response_exported(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE} with mock.patch.object( exporter.client.server, "post_json", return_value=MockFhirResponse(response_json), ) as mock_post: exporter.export_task() args, kwargs = mock_post.call_args sent_json = args[1] response = sent_json[Fc.ENTRY][1][Fc.RESOURCE] self.assertEqual( response[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE ) q_id = self.task._get_fhir_questionnaire_id(self.req) self.assertEqual(response[Fc.QUESTIONNAIRE], fhir_sysval_from_id(q_id)) self.assertEqual( response[Fc.AUTHORED], self.task.when_created.isoformat() ) self.assertEqual(response[Fc.STATUS], Fc.QSTATUS_COMPLETED) request = sent_json[Fc.ENTRY][1][Fc.REQUEST] self.assertEqual(request[Fc.METHOD], HttpMethod.POST) self.assertEqual(request[Fc.URL], "QuestionnaireResponse") qr_id = self.task._get_fhir_questionnaire_response_id(self.req) self.assertEqual( request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(qr_id) ) self.assertEqual(len(response[Fc.ITEM]), 5) ( q1_choice, q2_choice, q3_choice, q1_satisfaction, q2_satisfaction, ) = response[Fc.ITEM] # q1_choice self.assertEqual(q1_choice[Fc.LINK_ID], "q1_choice") self.assertEqual(q1_choice[Fc.TEXT], GIVEN_INFO) q1_choice_answer = q1_choice[Fc.ANSWER][0] self.assertEqual( q1_choice_answer[Fc.VALUE_INTEGER], self.task.q1_choice ) # q2_choice self.assertEqual(q2_choice[Fc.LINK_ID], "q2_choice") self.assertEqual(q2_choice[Fc.TEXT], PREFER_ANY) q2_choice_answer = q2_choice[Fc.ANSWER][0] self.assertEqual( q2_choice_answer[Fc.VALUE_INTEGER], self.task.q2_choice ) # q3_choice self.assertEqual(q3_choice[Fc.LINK_ID], "q3_choice") self.assertEqual(q3_choice[Fc.TEXT], OFFERED_PREFERENCE) q3_choice_answer = q3_choice[Fc.ANSWER][0] self.assertEqual( q3_choice_answer[Fc.VALUE_INTEGER], self.task.q3_choice ) # q1_satisfaction self.assertEqual(q1_satisfaction[Fc.LINK_ID], "q1_satisfaction") self.assertEqual(q1_satisfaction[Fc.TEXT], SATISFIED_ASSESSMENT) q1_satisfaction_answer = q1_satisfaction[Fc.ANSWER][0] self.assertEqual( q1_satisfaction_answer[Fc.VALUE_INTEGER], self.task.q1_satisfaction ) # q2 satisfaction self.assertEqual(q2_satisfaction[Fc.LINK_ID], "q2_satisfaction") self.assertEqual(q2_satisfaction[Fc.TEXT], TELL_US) q2_satisfaction_answer = q2_satisfaction[Fc.ANSWER][0] self.assertEqual( q2_satisfaction_answer[Fc.VALUE_STRING], self.task.q2_satisfaction )
def test_questionnaire_exported(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE} with mock.patch.object( exporter.client.server, "post_json", return_value=MockFhirResponse(response_json), ) as mock_post: exporter.export_task() args, kwargs = mock_post.call_args sent_json = args[1] questionnaire = sent_json[Fc.ENTRY][0][Fc.RESOURCE] self.assertEqual( questionnaire[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE ) self.assertEqual(questionnaire[Fc.STATUS], Fc.QSTATUS_ACTIVE) identifier = questionnaire[Fc.IDENTIFIER] questionnaire_url = ( f"{self.camcops_root_url}/{Routes.FHIR_QUESTIONNAIRE_SYSTEM}" ) self.assertEqual(identifier[0][Fc.SYSTEM], questionnaire_url) self.assertEqual( identifier[0][Fc.VALUE], f"apeqpt/{CAMCOPS_SERVER_VERSION_STRING}" ) self.assertEqual(len(questionnaire[Fc.ITEM]), 5) ( q1_choice, q2_choice, q3_choice, q1_satisfaction, q2_satisfaction, ) = questionnaire[Fc.ITEM] # q1_choice self.assertEqual(q1_choice[Fc.LINK_ID], "q1_choice") self.assertEqual(q1_choice[Fc.TEXT], GIVEN_INFO) self.assertEqual(q1_choice[Fc.TYPE], Fc.QITEM_TYPE_CHOICE) options = q1_choice[Fc.ANSWER_OPTION] self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0") self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "No") self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1") self.assertEqual(options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Yes") # q2_choice self.assertEqual(q2_choice[Fc.LINK_ID], "q2_choice") self.assertEqual(q2_choice[Fc.TEXT], PREFER_ANY) self.assertEqual(q2_choice[Fc.TYPE], Fc.QITEM_TYPE_CHOICE) options = q2_choice[Fc.ANSWER_OPTION] self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0") self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "No") self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1") self.assertEqual(options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Yes") # q3_choice self.assertEqual(q3_choice[Fc.LINK_ID], "q3_choice") self.assertEqual(q3_choice[Fc.TEXT], OFFERED_PREFERENCE) self.assertEqual(q3_choice[Fc.TYPE], Fc.QITEM_TYPE_CHOICE) options = q3_choice[Fc.ANSWER_OPTION] self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0") self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "No") self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1") self.assertEqual(options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Yes") self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2") self.assertEqual(options[2][Fc.VALUE_CODING][Fc.DISPLAY], "N/A") # q1_satisfaction self.assertEqual(q1_satisfaction[Fc.LINK_ID], "q1_satisfaction") self.assertEqual(q1_satisfaction[Fc.TEXT], SATISFIED_ASSESSMENT) self.assertEqual(q1_satisfaction[Fc.TYPE], Fc.QITEM_TYPE_CHOICE) options = q1_satisfaction[Fc.ANSWER_OPTION] self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0") self.assertEqual( options[0][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A0 ) self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1") self.assertEqual( options[1][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A1 ) self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2") self.assertEqual( options[2][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A2 ) self.assertEqual(options[3][Fc.VALUE_CODING][Fc.CODE], "3") self.assertEqual( options[3][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A3 ) self.assertEqual(options[4][Fc.VALUE_CODING][Fc.CODE], "4") self.assertEqual( options[4][Fc.VALUE_CODING][Fc.DISPLAY], APEQ_SATIS_A4 ) # q2 satisfaction self.assertEqual(q2_satisfaction[Fc.LINK_ID], "q2_satisfaction") self.assertEqual(q2_satisfaction[Fc.TEXT], TELL_US) self.assertEqual(q2_satisfaction[Fc.TYPE], Fc.QITEM_TYPE_STRING) request = sent_json[Fc.ENTRY][0][Fc.REQUEST] self.assertEqual(request[Fc.METHOD], HttpMethod.POST) self.assertEqual(request[Fc.URL], Fc.RESOURCE_TYPE_QUESTIONNAIRE) q_id = self.task._get_fhir_questionnaire_id(self.req) self.assertEqual( request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(q_id) )
def test_exported_task_saved(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) # auto increment doesn't work for BigInteger with SQLite exported_task.id = 1 self.dbsession.add(exported_task) exported_task_fhir = ExportedTaskFhir(exported_task) self.dbsession.add(exported_task_fhir) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) response_json = { Fc.RESOURCE_TYPE: Fc.RESOURCE_TYPE_BUNDLE, Fc.ID: "cae48957-e7e6-4649-97f8-0a882076ad0a", Fc.TYPE: Fc.TRANSACTION_RESPONSE, Fc.LINK: [ {Fc.RELATION: Fc.SELF, Fc.URL: "http://localhost:8080/fhir"} ], Fc.ENTRY: [ { Fc.RESPONSE: { Fc.STATUS: Fc.RESPONSE_STATUS_200_OK, Fc.LOCATION: "Patient/1/_history/1", Fc.ETAG: "1", } }, { Fc.RESPONSE: { Fc.STATUS: Fc.RESPONSE_STATUS_200_OK, Fc.LOCATION: "Questionnaire/26/_history/1", Fc.ETAG: "1", } }, { Fc.RESPONSE: { Fc.STATUS: Fc.RESPONSE_STATUS_201_CREATED, Fc.LOCATION: "QuestionnaireResponse/42/_history/1", Fc.ETAG: "1", Fc.LAST_MODIFIED: "2021-05-24T09:30:11.098+00:00", } }, ], } with mock.patch.object( exporter.client.server, "post_json", return_value=MockFhirResponse(response_json), ): exporter.export_task() self.dbsession.commit() entries = ( exported_task_fhir.entries ) # type: List[ExportedTaskFhirEntry] # noqa entries.sort(key=lambda e: e.location) self.assertEqual(entries[0].status, Fc.RESPONSE_STATUS_200_OK) self.assertEqual(entries[0].location, "Patient/1/_history/1") self.assertEqual(entries[0].etag, "1") self.assertEqual(entries[1].status, Fc.RESPONSE_STATUS_200_OK) self.assertEqual(entries[1].location, "Questionnaire/26/_history/1") self.assertEqual(entries[1].etag, "1") self.assertEqual(entries[2].status, Fc.RESPONSE_STATUS_201_CREATED) self.assertEqual( entries[2].location, "QuestionnaireResponse/42/_history/1" ) self.assertEqual(entries[2].etag, "1") self.assertEqual( entries[2].last_modified, datetime.datetime(2021, 5, 24, 9, 30, 11, 98000), )
def test_questionnaire_response_exported(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE} with mock.patch.object( exporter.client.server, "post_json", return_value=MockFhirResponse(response_json), ) as mock_post: exporter.export_task() args, kwargs = mock_post.call_args sent_json = args[1] response = sent_json[Fc.ENTRY][2][Fc.RESOURCE] self.assertEqual( response[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE ) q_id = self.task._get_fhir_questionnaire_id(self.req) self.assertEqual(response[Fc.QUESTIONNAIRE], fhir_sysval_from_id(q_id)) self.assertEqual( response[Fc.AUTHORED], self.task.when_created.isoformat() ) self.assertEqual(response[Fc.STATUS], Fc.QSTATUS_COMPLETED) subject = response[Fc.SUBJECT] identifier = subject[Fc.IDENTIFIER] self.assertEqual(subject[Fc.TYPE], Fc.RESOURCE_TYPE_PATIENT) idnum_value = self.patient_rio.idnum_value patient_id = self.patient.get_fhir_identifier(self.req, self.recipient) if isinstance(identifier, list): test_identifier = identifier[0] else: # only one test_identifier = identifier self.assertEqual(test_identifier[Fc.SYSTEM], patient_id.system) self.assertEqual(test_identifier[Fc.VALUE], str(idnum_value)) request = sent_json[Fc.ENTRY][2][Fc.REQUEST] self.assertEqual(request[Fc.METHOD], HttpMethod.POST) self.assertEqual( request[Fc.URL], Fc.RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE ) qr_id = self.task._get_fhir_questionnaire_response_id(self.req) self.assertEqual( request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(qr_id) ) item_1 = response[Fc.ITEM][0] item_10 = response[Fc.ITEM][9] self.assertEqual(item_1[Fc.LINK_ID], "q1") self.assertEqual( item_1[Fc.TEXT], "1. Little interest or pleasure in doing things" ) answer_1 = item_1[Fc.ANSWER][0] # noinspection PyUnresolvedReferences self.assertEqual(answer_1[Fc.VALUE_INTEGER], self.task.q1) self.assertEqual(item_10[Fc.LINK_ID], "q10") self.assertEqual( item_10[Fc.TEXT], ( "10. If you checked off any problems, how difficult have " "these problems made it for you to do your work, take care of " "things at home, or get along with other people?" ), ) answer_10 = item_10[Fc.ANSWER][0] self.assertEqual(answer_10[Fc.VALUE_INTEGER], self.task.q10) self.assertEqual(len(response[Fc.ITEM]), 10)
def test_questionnaire_exported(self) -> None: exported_task = ExportedTask(task=self.task, recipient=self.recipient) exported_task_fhir = ExportedTaskFhir(exported_task) exporter = MockFhirTaskExporter(self.req, exported_task_fhir) response_json = {Fc.TYPE: Fc.TRANSACTION_RESPONSE} with mock.patch.object( exporter.client.server, "post_json", return_value=MockFhirResponse(response_json), ) as mock_post: exporter.export_task() args, kwargs = mock_post.call_args sent_json = args[1] questionnaire = sent_json[Fc.ENTRY][1][Fc.RESOURCE] self.assertEqual( questionnaire[Fc.RESOURCE_TYPE], Fc.RESOURCE_TYPE_QUESTIONNAIRE ) self.assertEqual(questionnaire[Fc.STATUS], Fc.QSTATUS_ACTIVE) identifier = questionnaire[Fc.IDENTIFIER] questionnaire_url = ( f"{self.camcops_root_url}/{Routes.FHIR_QUESTIONNAIRE_SYSTEM}" ) self.assertEqual(identifier[0][Fc.SYSTEM], questionnaire_url) self.assertEqual( identifier[0][Fc.VALUE], f"phq9/{CAMCOPS_SERVER_VERSION_STRING}" ) question_1 = questionnaire[Fc.ITEM][0] question_10 = questionnaire[Fc.ITEM][9] self.assertEqual(question_1[Fc.LINK_ID], "q1") self.assertEqual( question_1[Fc.TEXT], "1. Little interest or pleasure in doing things", ) self.assertEqual(question_1[Fc.TYPE], Fc.QITEM_TYPE_CHOICE) options = question_1[Fc.ANSWER_OPTION] self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0") self.assertEqual(options[0][Fc.VALUE_CODING][Fc.DISPLAY], "Not at all") self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1") self.assertEqual( options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Several days" ) self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2") self.assertEqual( options[2][Fc.VALUE_CODING][Fc.DISPLAY], "More than half the days" ) self.assertEqual(options[3][Fc.VALUE_CODING][Fc.CODE], "3") self.assertEqual( options[3][Fc.VALUE_CODING][Fc.DISPLAY], "Nearly every day" ) self.assertEqual(question_10[Fc.LINK_ID], "q10") self.assertEqual( question_10[Fc.TEXT], ( "10. If you checked off any problems, how difficult have " "these problems made it for you to do your work, take care of " "things at home, or get along with other people?" ), ) self.assertEqual(question_10[Fc.TYPE], Fc.QITEM_TYPE_CHOICE) options = question_10[Fc.ANSWER_OPTION] self.assertEqual(options[0][Fc.VALUE_CODING][Fc.CODE], "0") self.assertEqual( options[0][Fc.VALUE_CODING][Fc.DISPLAY], "Not difficult at all" ) self.assertEqual(options[1][Fc.VALUE_CODING][Fc.CODE], "1") self.assertEqual( options[1][Fc.VALUE_CODING][Fc.DISPLAY], "Somewhat difficult" ) self.assertEqual(options[2][Fc.VALUE_CODING][Fc.CODE], "2") self.assertEqual( options[2][Fc.VALUE_CODING][Fc.DISPLAY], "Very difficult" ) self.assertEqual(options[3][Fc.VALUE_CODING][Fc.CODE], "3") self.assertEqual( options[3][Fc.VALUE_CODING][Fc.DISPLAY], "Extremely difficult" ) self.assertEqual(len(questionnaire[Fc.ITEM]), 10) request = sent_json[Fc.ENTRY][1][Fc.REQUEST] self.assertEqual(request[Fc.METHOD], HttpMethod.POST) self.assertEqual(request[Fc.URL], Fc.RESOURCE_TYPE_QUESTIONNAIRE) q_id = self.task._get_fhir_questionnaire_id(self.req) self.assertEqual( request[Fc.IF_NONE_EXIST], fhir_reference_from_identifier(q_id) )