Coverage for cc_modules/tests/cc_redcap_tests.py: 16%
694 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_redcap_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===============================================================================
26"""
27import os
28import tempfile
29from typing import Generator, TYPE_CHECKING
30from unittest import mock, TestCase
32from pandas import DataFrame
33import pendulum
34import redcap
36from camcops_server.cc_modules.cc_constants import ConfigParamExportRecipient
37from camcops_server.cc_modules.cc_exportrecipient import ExportRecipient
38from camcops_server.cc_modules.cc_exportrecipientinfo import (
39 ExportRecipientInfo,
40)
41from camcops_server.cc_modules.cc_redcap import (
42 MISSING_EVENT_TAG_OR_ATTRIBUTE,
43 RedcapExportException,
44 RedcapFieldmap,
45 RedcapNewRecordUploader,
46 RedcapRecordStatus,
47 RedcapTaskExporter,
48)
49from camcops_server.cc_modules.cc_unittest import BasicDatabaseTestCase
51if TYPE_CHECKING:
52 from camcops_server.cc_modules.cc_patient import Patient
55# =============================================================================
56# Unit testing
57# =============================================================================
60class MockProject(mock.Mock):
61 def __init__(self, *args, **kwargs) -> None:
62 super().__init__(*args, **kwargs)
64 self.export_project_info = mock.Mock()
65 self.export_records = mock.Mock()
66 self.generate_next_record_name = mock.Mock()
67 self.import_file = mock.Mock()
68 self.import_records = mock.Mock()
69 self.is_longitudinal = mock.Mock(return_value=False)
72class MockRedcapTaskExporter(RedcapTaskExporter):
73 def __init__(self) -> None:
74 mock_project = MockProject()
75 self.get_project = mock.Mock(return_value=mock_project)
77 config = mock.Mock()
78 self.req = mock.Mock(config=config)
81class MockRedcapNewRecordUploader(RedcapNewRecordUploader):
82 # noinspection PyMissingConstructor
83 def __init__(self) -> None:
84 self.req = mock.Mock()
85 self.project = MockProject()
86 self.task = mock.Mock(tablename="mock_task")
89class RedcapExporterTests(TestCase):
90 def test_next_instance_id_converted_to_int(self) -> None:
91 import numpy
93 records = DataFrame(
94 {
95 "record_id": ["1", "1", "1", "1", "1"],
96 "redcap_repeat_instrument": [
97 "bmi",
98 "bmi",
99 "bmi",
100 "bmi",
101 "bmi",
102 ],
103 "redcap_repeat_instance": [
104 numpy.float64(1.0),
105 numpy.float64(2.0),
106 numpy.float64(3.0),
107 numpy.float64(4.0),
108 numpy.float64(5.0),
109 ],
110 }
111 )
113 next_instance_id = RedcapTaskExporter._get_next_instance_id(
114 records, "bmi", "record_id", "1"
115 )
117 self.assertEqual(next_instance_id, 6)
118 self.assertEqual(type(next_instance_id), int)
121class RedcapExportErrorTests(TestCase):
122 def test_raises_when_fieldmap_has_unknown_symbols(self) -> None:
123 exporter = MockRedcapNewRecordUploader()
125 task = mock.Mock(tablename="bmi")
126 fieldmap = {"pa_height": "sys.platform"}
128 field_dict = {}
130 with self.assertRaises(RedcapExportException) as cm:
131 exporter.transform_fields(field_dict, task, fieldmap)
133 message = str(cm.exception)
134 self.assertIn("Error in formula 'sys.platform':", message)
135 self.assertIn("Task: 'bmi'", message)
136 self.assertIn("REDCap field: 'pa_height'", message)
137 self.assertIn("'sys' is not defined", message)
139 def test_raises_when_fieldmap_empty_in_config(self) -> None:
141 exporter = MockRedcapTaskExporter()
143 recipient = mock.Mock(redcap_fieldmap_filename="")
144 with self.assertRaises(RedcapExportException) as cm:
145 exporter.get_fieldmap_filename(recipient)
147 message = str(cm.exception)
148 self.assertIn(
149 f"{ConfigParamExportRecipient.REDCAP_FIELDMAP_FILENAME} "
150 f"is empty in the config file",
151 message,
152 )
154 def test_raises_when_fieldmap_not_set_in_config(self) -> None:
156 exporter = MockRedcapTaskExporter()
158 recipient = mock.Mock(redcap_fieldmap_filename=None)
159 with self.assertRaises(RedcapExportException) as cm:
160 exporter.get_fieldmap_filename(recipient)
162 message = str(cm.exception)
163 self.assertIn(
164 f"{ConfigParamExportRecipient.REDCAP_FIELDMAP_FILENAME} "
165 f"is not set in the config file",
166 message,
167 )
169 def test_raises_when_error_from_redcap_on_import(self) -> None:
170 exporter = MockRedcapNewRecordUploader()
171 exporter.project.import_records.side_effect = redcap.RedcapError(
172 "Something went wrong"
173 )
175 with self.assertRaises(RedcapExportException) as cm:
176 record = {}
177 exporter.upload_record(record)
178 message = str(cm.exception)
180 self.assertIn("Something went wrong", message)
182 def test_raises_when_error_from_redcap_on_init(self) -> None:
183 with mock.patch("redcap.project.Project.__init__") as mock_init:
184 mock_init.side_effect = redcap.RedcapError("Something went wrong")
186 with self.assertRaises(RedcapExportException) as cm:
187 exporter = RedcapTaskExporter()
188 recipient = mock.Mock()
189 exporter.get_project(recipient)
191 message = str(cm.exception)
193 self.assertIn("Something went wrong", message)
195 def test_raises_when_field_not_a_file_field(self) -> None:
196 exporter = MockRedcapNewRecordUploader()
197 exporter.project.import_file.side_effect = ValueError(
198 "Error with file field"
199 )
201 task = mock.Mock()
203 with self.assertRaises(RedcapExportException) as cm:
204 record_id = 1
205 repeat_instance = 1
206 file_dict = {"medication_items": b"not a real file"}
207 exporter.upload_files(task, record_id, repeat_instance, file_dict)
208 message = str(cm.exception)
210 self.assertIn("Error with file field", message)
212 def test_raises_when_error_from_redcap_on_import_file(self) -> None:
213 exporter = MockRedcapNewRecordUploader()
214 exporter.project.import_file.side_effect = redcap.RedcapError(
215 "Something went wrong"
216 )
218 task = mock.Mock()
220 with self.assertRaises(RedcapExportException) as cm:
221 record_id = 1
222 repeat_instance = 1
223 file_dict = {"medication_items": b"not a real file"}
224 exporter.upload_files(task, record_id, repeat_instance, file_dict)
225 message = str(cm.exception)
227 self.assertIn("Something went wrong", message)
230class RedcapFieldmapTests(TestCase):
231 def test_raises_when_xml_file_missing(self) -> None:
232 with self.assertRaises(RedcapExportException) as cm:
233 RedcapFieldmap("/does/not/exist/bmi.xml")
235 message = str(cm.exception)
237 self.assertIn("Unable to open fieldmap file", message)
238 self.assertIn("bmi.xml", message)
240 def test_raises_when_fieldmap_missing(self) -> None:
241 with tempfile.NamedTemporaryFile(
242 mode="w", suffix="xml"
243 ) as fieldmap_file:
244 fieldmap_file.write(
245 """<?xml version="1.0" encoding="UTF-8"?>
246<someothertag></someothertag>
247"""
248 )
249 fieldmap_file.flush()
251 with self.assertRaises(RedcapExportException) as cm:
252 RedcapFieldmap(fieldmap_file.name)
254 message = str(cm.exception)
255 self.assertIn(
256 (
257 "Expected the root tag to be 'fieldmap' instead of "
258 "'someothertag'"
259 ),
260 message,
261 )
262 self.assertIn(fieldmap_file.name, message)
264 def test_raises_when_root_tag_missing(self) -> None:
265 with tempfile.NamedTemporaryFile(
266 mode="w", suffix="xml"
267 ) as fieldmap_file:
268 fieldmap_file.write(
269 """<?xml version="1.0" encoding="UTF-8"?>
270"""
271 )
272 fieldmap_file.flush()
274 with self.assertRaises(RedcapExportException) as cm:
275 RedcapFieldmap(fieldmap_file.name)
277 message = str(cm.exception)
278 self.assertIn("There was a problem parsing", message)
279 self.assertIn(fieldmap_file.name, message)
281 def test_raises_when_patient_missing(self) -> None:
282 with tempfile.NamedTemporaryFile(
283 mode="w", suffix="xml"
284 ) as fieldmap_file:
285 fieldmap_file.write(
286 """<?xml version="1.0" encoding="UTF-8"?>
287 <fieldmap>
288 </fieldmap>
289 """
290 )
291 fieldmap_file.flush()
293 with self.assertRaises(RedcapExportException) as cm:
294 RedcapFieldmap(fieldmap_file.name)
296 message = str(cm.exception)
297 self.assertIn("'patient' is missing from", message)
298 self.assertIn(fieldmap_file.name, message)
300 def test_raises_when_patient_missing_attributes(self) -> None:
301 with tempfile.NamedTemporaryFile(
302 mode="w", suffix="xml"
303 ) as fieldmap_file:
304 fieldmap_file.write(
305 """<?xml version="1.0" encoding="UTF-8"?>
306 <fieldmap>
307 <patient />
308 </fieldmap>
309 """
310 )
311 fieldmap_file.flush()
313 with self.assertRaises(RedcapExportException) as cm:
314 RedcapFieldmap(fieldmap_file.name)
316 message = str(cm.exception)
317 self.assertIn(
318 "'patient' must have attributes: instrument, redcap_field", message
319 )
320 self.assertIn(fieldmap_file.name, message)
322 def test_raises_when_record_missing(self) -> None:
323 with tempfile.NamedTemporaryFile(
324 mode="w", suffix="xml"
325 ) as fieldmap_file:
326 fieldmap_file.write(
327 """<?xml version="1.0" encoding="UTF-8"?>
328 <fieldmap>
329 <patient instrument="patient_record" redcap_field="patient_id" />
330 </fieldmap>
331 """ # noqa: E501
332 )
333 fieldmap_file.flush()
335 with self.assertRaises(RedcapExportException) as cm:
336 RedcapFieldmap(fieldmap_file.name)
338 message = str(cm.exception)
339 self.assertIn("'record' is missing from", message)
340 self.assertIn(fieldmap_file.name, message)
342 def test_raises_when_record_missing_attributes(self) -> None:
343 with tempfile.NamedTemporaryFile(
344 mode="w", suffix="xml"
345 ) as fieldmap_file:
346 fieldmap_file.write(
347 """<?xml version="1.0" encoding="UTF-8"?>
348 <fieldmap>
349 <patient instrument="patient_record" redcap_field="patient_id" />
350 <record />
351 </fieldmap>
352 """ # noqa: E501
353 )
354 fieldmap_file.flush()
356 with self.assertRaises(RedcapExportException) as cm:
357 RedcapFieldmap(fieldmap_file.name)
359 message = str(cm.exception)
360 self.assertIn(
361 "'record' must have attributes: instrument, redcap_field", message
362 )
363 self.assertIn(fieldmap_file.name, message)
365 def test_raises_when_instruments_missing(self) -> None:
366 with tempfile.NamedTemporaryFile(
367 mode="w", suffix="xml"
368 ) as fieldmap_file:
369 fieldmap_file.write(
370 """<?xml version="1.0" encoding="UTF-8"?>
371 <fieldmap>
372 <patient instrument="patient_record" redcap_field="patient_id" />
373 <record instrument="patient_record" redcap_field="record_id" />
374 </fieldmap>
375 """ # noqa: E501
376 )
377 fieldmap_file.flush()
379 with self.assertRaises(RedcapExportException) as cm:
380 RedcapFieldmap(fieldmap_file.name)
382 message = str(cm.exception)
383 self.assertIn("'instruments' tag is missing from", message)
384 self.assertIn(fieldmap_file.name, message)
386 def test_raises_when_instruments_missing_attributes(self) -> None:
387 with tempfile.NamedTemporaryFile(
388 mode="w", suffix="xml"
389 ) as fieldmap_file:
390 fieldmap_file.write(
391 """<?xml version="1.0" encoding="UTF-8"?>
392 <fieldmap>
393 <patient instrument="patient_record" redcap_field="patient_id" />
394 <record instrument="patient_record" redcap_field="record_id" />
395 <instruments>
396 <instrument />
397 </instruments>
398 </fieldmap>
399 """ # noqa: E501
400 )
401 fieldmap_file.flush()
403 with self.assertRaises(RedcapExportException) as cm:
404 RedcapFieldmap(fieldmap_file.name)
406 message = str(cm.exception)
407 self.assertIn("'instrument' must have attributes: name, task", message)
408 self.assertIn(fieldmap_file.name, message)
410 def test_raises_when_file_fields_missing_attributes(self) -> None:
411 with tempfile.NamedTemporaryFile(
412 mode="w", suffix="xml"
413 ) as fieldmap_file:
414 fieldmap_file.write(
415 """<?xml version="1.0" encoding="UTF-8"?>
416 <fieldmap>
417 <patient instrument="patient_record" redcap_field="patient_id" />
418 <record instrument="patient_record" redcap_field="record_id" />
419 <instruments>
420 <instrument name="bmi" task="bmi">
421 <files>
422 <field />
423 </files>
424 </instrument>
425 </instruments>
426 </fieldmap>
427 """ # noqa: E501
428 )
429 fieldmap_file.flush()
431 with self.assertRaises(RedcapExportException) as cm:
432 RedcapFieldmap(fieldmap_file.name)
434 message = str(cm.exception)
435 self.assertIn("'field' must have attributes: name, formula", message)
436 self.assertIn(fieldmap_file.name, message)
438 def test_raises_when_fields_missing_attributes(self) -> None:
439 with tempfile.NamedTemporaryFile(
440 mode="w", suffix="xml"
441 ) as fieldmap_file:
442 fieldmap_file.write(
443 """<?xml version="1.0" encoding="UTF-8"?>
444 <fieldmap>
445 <patient instrument="patient_record" redcap_field="patient_id" />
446 <record instrument="patient_record" redcap_field="record_id" />
447 <instruments>
448 <instrument name="bmi" task="bmi">
449 <fields>
450 <field />
451 </fields>
452 </instrument>
453 </instruments>
454 </fieldmap>
455 """ # noqa: E501
456 )
457 fieldmap_file.flush()
459 with self.assertRaises(RedcapExportException) as cm:
460 RedcapFieldmap(fieldmap_file.name)
462 message = str(cm.exception)
463 self.assertIn("'field' must have attributes: name, formula", message)
464 self.assertIn(fieldmap_file.name, message)
467# =============================================================================
468# Integration testing
469# =============================================================================
472class RedcapExportTestCase(BasicDatabaseTestCase):
473 fieldmap = ""
475 def setUp(self) -> None:
476 recipientinfo = ExportRecipientInfo()
478 self.recipient = ExportRecipient(recipientinfo)
479 self.recipient.primary_idnum = 1001
481 # auto increment doesn't work for BigInteger with SQLite
482 self.recipient.id = 1
483 self.recipient.recipient_name = "test"
484 self.recipient.redcap_fieldmap_filename = os.path.join(
485 self.tmpdir_obj.name, "redcap_fieldmap.xml"
486 )
487 self.write_fieldmaps(self.recipient.redcap_fieldmap_filename)
489 super().setUp()
491 def write_fieldmaps(self, filename: str) -> None:
492 with open(filename, "w") as f:
493 f.write(self.fieldmap)
495 def create_patient_with_idnum_1001(self) -> "Patient":
496 from camcops_server.cc_modules.cc_idnumdef import IdNumDefinition
497 from camcops_server.cc_modules.cc_patient import Patient
498 from camcops_server.cc_modules.cc_patientidnum import PatientIdNum
500 patient = Patient()
501 patient.id = 2
502 self.apply_standard_db_fields(patient)
503 patient.forename = "Forename2"
504 patient.surname = "Surname2"
505 patient.dob = pendulum.parse("1975-12-12")
506 self.dbsession.add(patient)
508 idnumdef_1001 = IdNumDefinition()
509 idnumdef_1001.which_idnum = 1001
510 idnumdef_1001.description = "Test idnumdef 1001"
511 self.dbsession.add(idnumdef_1001)
512 self.dbsession.commit()
514 patient_idnum1 = PatientIdNum()
515 patient_idnum1.id = 3
516 self.apply_standard_db_fields(patient_idnum1)
517 patient_idnum1.patient_id = patient.id
518 patient_idnum1.which_idnum = 1001
519 patient_idnum1.idnum_value = 555
520 self.dbsession.add(patient_idnum1)
521 self.dbsession.commit()
523 return patient
526class BmiRedcapExportTestCase(RedcapExportTestCase):
527 def __init__(self, *args, **kwargs) -> None:
528 super().__init__(*args, **kwargs)
529 self.id_sequence = self.get_id()
531 @staticmethod
532 def get_id() -> Generator[int, None, None]:
533 i = 1
535 while True:
536 yield i
537 i += 1
540class BmiRedcapValidFieldmapTestCase(BmiRedcapExportTestCase):
541 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
542<fieldmap>
543 <patient instrument="patient_record" redcap_field="patient_id" />
544 <record instrument="instrument_with_record_id" redcap_field="record_id" />
545 <instruments>
546 <instrument task="bmi" name="bmi">
547 <fields>
548 <field name="pa_height" formula="format(task.height_m, '.1f')" />
549 <field name="pa_weight" formula="format(task.mass_kg, '.1f')" />
550 <field name="bmi_date" formula="format_datetime(task.when_created, DateFormat.ISO8601_DATE_ONLY)" />
551 </fields>
552 </instrument>
553 </instruments>
554</fieldmap>""" # noqa: E501
557class BmiRedcapExportTests(BmiRedcapValidFieldmapTestCase):
558 """
559 These are more of a test of the fieldmap code than anything
560 related to the BMI task
561 """
563 def create_tasks(self) -> None:
564 from camcops_server.tasks.bmi import Bmi
566 patient = self.create_patient_with_idnum_1001()
567 self.task = Bmi()
568 self.apply_standard_task_fields(self.task)
569 self.task.id = next(self.id_sequence)
570 self.task.height_m = 1.83
571 self.task.mass_kg = 67.57
572 self.task.patient_id = patient.id
573 self.dbsession.add(self.task)
574 self.dbsession.commit()
576 def test_record_exported(self) -> None:
577 from camcops_server.cc_modules.cc_exportmodels import (
578 ExportedTask,
579 ExportedTaskRedcap,
580 )
582 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
583 exported_task_redcap = ExportedTaskRedcap(exported_task)
585 exporter = MockRedcapTaskExporter()
586 project = exporter.get_project()
587 project.export_records.return_value = DataFrame({"patient_id": []})
588 project.import_records.return_value = ["123,0"]
589 project.export_project_info.return_value = {
590 "record_autonumbering_enabled": 1
591 }
593 exporter.export_task(self.req, exported_task_redcap)
594 self.assertEqual(exported_task_redcap.redcap_record_id, "123")
595 self.assertEqual(exported_task_redcap.redcap_instrument_name, "bmi")
596 self.assertEqual(exported_task_redcap.redcap_instance_id, 1)
598 args, kwargs = project.export_records.call_args
600 self.assertIn("bmi", kwargs["forms"])
601 self.assertIn("patient_record", kwargs["forms"])
602 self.assertIn("instrument_with_record_id", kwargs["forms"])
604 # Initial call with original record
605 args, kwargs = project.import_records.call_args_list[0]
607 rows = args[0]
608 record = rows[0]
610 self.assertEqual(record["redcap_repeat_instrument"], "bmi")
611 self.assertEqual(record["redcap_repeat_instance"], 1)
612 self.assertEqual(record["record_id"], "0")
613 self.assertEqual(
614 record["bmi_complete"], RedcapRecordStatus.COMPLETE.value
615 )
616 self.assertEqual(record["bmi_date"], "2010-07-07")
618 self.assertEqual(record["pa_height"], "1.8")
619 self.assertEqual(record["pa_weight"], "67.6")
621 self.assertEqual(kwargs["return_content"], "auto_ids")
622 self.assertTrue(kwargs["force_auto_number"])
624 # Second call with updated patient ID
625 args, kwargs = project.import_records.call_args_list[1]
626 rows = args[0]
627 record = rows[0]
629 self.assertEqual(record["patient_id"], 555)
631 def test_record_exported_with_non_integer_id(self) -> None:
632 from camcops_server.cc_modules.cc_exportmodels import (
633 ExportedTask,
634 ExportedTaskRedcap,
635 )
637 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
638 exported_task_redcap = ExportedTaskRedcap(exported_task)
640 exporter = MockRedcapTaskExporter()
641 project = exporter.get_project()
642 project.export_records.return_value = DataFrame({"patient_id": []})
643 project.import_records.return_value = ["15-123,0"]
644 project.export_project_info.return_value = {
645 "record_autonumbering_enabled": 1
646 }
648 exporter.export_task(self.req, exported_task_redcap)
649 self.assertEqual(exported_task_redcap.redcap_record_id, "15-123")
651 def test_record_id_generated_when_no_autonumbering(self) -> None:
652 from camcops_server.cc_modules.cc_exportmodels import (
653 ExportedTask,
654 ExportedTaskRedcap,
655 )
657 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
658 exported_task_redcap = ExportedTaskRedcap(exported_task)
660 exporter = MockRedcapTaskExporter()
661 project = exporter.get_project()
662 project.export_records.return_value = DataFrame({"patient_id": []})
663 project.import_records.return_value = {"count": 1}
664 project.export_project_info.return_value = {
665 "record_autonumbering_enabled": 0
666 }
667 project.generate_next_record_name.return_value = "15-29"
669 exporter.export_task(self.req, exported_task_redcap)
671 # Initial call with original record
672 args, kwargs = project.import_records.call_args_list[0]
674 rows = args[0]
675 record = rows[0]
677 self.assertEqual(record["record_id"], "15-29")
678 self.assertEqual(kwargs["return_content"], "count")
679 self.assertFalse(kwargs["force_auto_number"])
681 def test_record_imported_when_no_existing_records(self) -> None:
682 from camcops_server.cc_modules.cc_exportmodels import (
683 ExportedTask,
684 ExportedTaskRedcap,
685 )
687 exporter = MockRedcapTaskExporter()
688 project = exporter.get_project()
689 project.export_records.return_value = DataFrame()
690 project.import_records.return_value = ["1,0"]
691 project.export_project_info.return_value = {
692 "record_autonumbering_enabled": 1
693 }
695 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
696 exported_task_redcap = ExportedTaskRedcap(exported_task)
697 exporter.export_task(self.req, exported_task_redcap)
699 self.assertEqual(exported_task_redcap.redcap_record_id, "1")
700 self.assertEqual(exported_task_redcap.redcap_instrument_name, "bmi")
701 self.assertEqual(exported_task_redcap.redcap_instance_id, 1)
704class BmiRedcapUpdateTests(BmiRedcapValidFieldmapTestCase):
705 def create_tasks(self) -> None:
706 from camcops_server.tasks.bmi import Bmi
708 patient = self.create_patient_with_idnum_1001()
709 self.task1 = Bmi()
710 self.apply_standard_task_fields(self.task1)
711 self.task1.id = next(self.id_sequence)
712 self.task1.height_m = 1.83
713 self.task1.mass_kg = 67.57
714 self.task1.patient_id = patient.id
715 self.dbsession.add(self.task1)
717 self.task2 = Bmi()
718 self.apply_standard_task_fields(self.task2)
719 self.task2.id = next(self.id_sequence)
720 self.task2.height_m = 1.83
721 self.task2.mass_kg = 68.5
722 self.task2.patient_id = patient.id
723 self.dbsession.add(self.task2)
724 self.dbsession.commit()
726 def test_existing_record_id_used_for_update(self) -> None:
727 from camcops_server.cc_modules.cc_exportmodels import (
728 ExportedTask,
729 ExportedTaskRedcap,
730 )
732 exporter = MockRedcapTaskExporter()
733 project = exporter.get_project()
734 project.export_records.return_value = DataFrame({"patient_id": []})
735 project.import_records.return_value = ["123,0"]
736 project.export_project_info.return_value = {
737 "record_autonumbering_enabled": 1
738 }
740 exported_task1 = ExportedTask(
741 task=self.task1, recipient=self.recipient
742 )
743 exported_task_redcap1 = ExportedTaskRedcap(exported_task1)
744 exporter.export_task(self.req, exported_task_redcap1)
745 self.assertEqual(exported_task_redcap1.redcap_record_id, "123")
746 self.assertEqual(exported_task_redcap1.redcap_instrument_name, "bmi")
747 self.assertEqual(exported_task_redcap1.redcap_instance_id, 1)
749 project.export_records.return_value = DataFrame(
750 {
751 "record_id": ["123"],
752 "patient_id": [555],
753 "redcap_repeat_instrument": ["bmi"],
754 "redcap_repeat_instance": [1],
755 }
756 )
757 exported_task2 = ExportedTask(
758 task=self.task2, recipient=self.recipient
759 )
760 exported_task_redcap2 = ExportedTaskRedcap(exported_task2)
762 exporter.export_task(self.req, exported_task_redcap2)
763 self.assertEqual(exported_task_redcap2.redcap_record_id, "123")
764 self.assertEqual(exported_task_redcap2.redcap_instrument_name, "bmi")
765 self.assertEqual(exported_task_redcap2.redcap_instance_id, 2)
767 # Third call (after initial record and patient ID)
768 args, kwargs = project.import_records.call_args_list[2]
770 rows = args[0]
771 record = rows[0]
773 self.assertEqual(record["record_id"], "123")
774 self.assertEqual(record["redcap_repeat_instance"], 2)
775 self.assertEqual(kwargs["return_content"], "count")
776 self.assertFalse(kwargs["force_auto_number"])
779class Phq9RedcapExportTests(RedcapExportTestCase):
780 """
781 These are more of a test of the fieldmap code than anything
782 related to the PHQ9 task. For these we have also renamed the record_id
783 field.
784 """
786 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
787<fieldmap>
788 <patient instrument="patient_record" redcap_field="patient_id" />
789 <record instrument="patient_record" redcap_field="my_record_id" />
790 <instruments>
791 <instrument task="phq9" name="patient_health_questionnaire_9">
792 <fields>
793 <field name="phq9_how_difficult" formula="task.q10 + 1 if task.q10 is not None else None" />
794 <field name="phq9_total_score" formula="task.total_score()" />
795 <field name="phq9_first_name" formula="task.patient.forename" />
796 <field name="phq9_last_name" formula="task.patient.surname" />
797 <field name="phq9_date_enrolled" formula="format_datetime(task.when_created,DateFormat.ISO8601_DATE_ONLY)" />
798 <field name="phq9_1" formula="task.q1" />
799 <field name="phq9_2" formula="task.q2" />
800 <field name="phq9_3" formula="task.q3" />
801 <field name="phq9_4" formula="task.q4" />
802 <field name="phq9_5" formula="task.q5" />
803 <field name="phq9_6" formula="task.q6" />
804 <field name="phq9_7" formula="task.q7" />
805 <field name="phq9_8" formula="task.q8" />
806 <field name="phq9_9" formula="task.q9" />
807 </fields>
808 </instrument>
809 </instruments>
810</fieldmap>""" # noqa: E501
812 def __init__(self, *args, **kwargs) -> None:
813 super().__init__(*args, **kwargs)
814 self.id_sequence = self.get_id()
816 @staticmethod
817 def get_id() -> Generator[int, None, None]:
818 i = 1
820 while True:
821 yield i
822 i += 1
824 def create_tasks(self) -> None:
825 from camcops_server.tasks.phq9 import Phq9
827 patient = self.create_patient_with_idnum_1001()
828 self.task = Phq9()
829 self.apply_standard_task_fields(self.task)
830 self.task.id = next(self.id_sequence)
831 self.task.q1 = 0
832 self.task.q2 = 1
833 self.task.q3 = 2
834 self.task.q4 = 3
835 self.task.q5 = 0
836 self.task.q6 = 1
837 self.task.q7 = 2
838 self.task.q8 = 3
839 self.task.q9 = 0
840 self.task.q10 = 3
841 self.task.patient_id = patient.id
842 self.dbsession.add(self.task)
843 self.dbsession.commit()
845 def test_record_exported(self) -> None:
846 from camcops_server.cc_modules.cc_exportmodels import (
847 ExportedTask,
848 ExportedTaskRedcap,
849 )
851 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
852 exported_task_redcap = ExportedTaskRedcap(exported_task)
854 exporter = MockRedcapTaskExporter()
855 project = exporter.get_project()
856 project.export_records.return_value = DataFrame({"patient_id": []})
857 project.import_records.return_value = ["123,0"]
858 project.export_project_info.return_value = {
859 "record_autonumbering_enabled": 1
860 }
862 exporter.export_task(self.req, exported_task_redcap)
863 self.assertEqual(exported_task_redcap.redcap_record_id, "123")
864 self.assertEqual(
865 exported_task_redcap.redcap_instrument_name,
866 "patient_health_questionnaire_9",
867 )
868 self.assertEqual(exported_task_redcap.redcap_instance_id, 1)
870 # Initial call with new record
871 args, kwargs = project.import_records.call_args_list[0]
873 rows = args[0]
874 record = rows[0]
876 self.assertEqual(
877 record["redcap_repeat_instrument"],
878 "patient_health_questionnaire_9",
879 )
880 self.assertEqual(record["my_record_id"], "0")
881 self.assertEqual(
882 record["patient_health_questionnaire_9_complete"],
883 RedcapRecordStatus.COMPLETE.value,
884 )
885 self.assertEqual(record["phq9_how_difficult"], 4)
886 self.assertEqual(record["phq9_total_score"], 12)
887 self.assertEqual(record["phq9_first_name"], "Forename2")
888 self.assertEqual(record["phq9_last_name"], "Surname2")
889 self.assertEqual(record["phq9_date_enrolled"], "2010-07-07")
891 self.assertEqual(record["phq9_1"], 0)
892 self.assertEqual(record["phq9_2"], 1)
893 self.assertEqual(record["phq9_3"], 2)
894 self.assertEqual(record["phq9_4"], 3)
895 self.assertEqual(record["phq9_5"], 0)
896 self.assertEqual(record["phq9_6"], 1)
897 self.assertEqual(record["phq9_7"], 2)
898 self.assertEqual(record["phq9_8"], 3)
899 self.assertEqual(record["phq9_9"], 0)
901 self.assertEqual(kwargs["return_content"], "auto_ids")
902 self.assertTrue(kwargs["force_auto_number"])
904 # Second call with patient ID
905 args, kwargs = project.import_records.call_args_list[1]
907 rows = args[0]
908 record = rows[0]
909 self.assertEqual(record["patient_id"], 555)
912class MedicationTherapyRedcapExportTests(RedcapExportTestCase):
913 """
914 These are more of a test of the file upload code than anything
915 related to the KhandakerMojoMedicationTherapy task
916 """
918 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
919<fieldmap>
920 <event name="event_1_arm_1" />
921 <patient instrument="patient_record" redcap_field="patient_id" />
922 <record instrument="patient_record" redcap_field="record_id" />
923 <instruments>
924 <instrument task="khandaker_mojo_medicationtherapy" name="medication_table">
925 <files>
926 <field name="medtbl_medication_items" formula="task.get_pdf(request)" />
927 </files>
928 </instrument>
929 </instruments>
930</fieldmap>""" # noqa: E501
932 def __init__(self, *args, **kwargs) -> None:
933 super().__init__(*args, **kwargs)
934 self.id_sequence = self.get_id()
936 @staticmethod
937 def get_id() -> Generator[int, None, None]:
938 i = 1
940 while True:
941 yield i
942 i += 1
944 def create_tasks(self) -> None:
945 from camcops_server.tasks.khandaker_mojo_medicationtherapy import (
946 KhandakerMojoMedicationTherapy,
947 )
949 patient = self.create_patient_with_idnum_1001()
950 self.task = KhandakerMojoMedicationTherapy()
951 self.apply_standard_task_fields(self.task)
952 self.task.id = next(self.id_sequence)
953 self.task.patient_id = patient.id
954 self.dbsession.add(self.task)
955 self.dbsession.commit()
957 def test_record_exported(self) -> None:
958 from camcops_server.cc_modules.cc_exportmodels import (
959 ExportedTask,
960 ExportedTaskRedcap,
961 )
963 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
964 exported_task_redcap = ExportedTaskRedcap(exported_task)
966 exporter = MockRedcapTaskExporter()
967 project = exporter.get_project()
968 project.export_records.return_value = DataFrame({"patient_id": []})
969 project.import_records.return_value = ["123,0"]
970 project.export_project_info.return_value = {
971 "record_autonumbering_enabled": 1
972 }
974 # We can't just look at the call_args on the mock object because
975 # the file will already have been closed by then
976 # noinspection PyUnusedLocal
977 def read_pdf_bytes(*import_file_args, **import_file_kwargs) -> None:
978 # record, field, fname, fobj
979 file_obj = import_file_args[3]
980 read_pdf_bytes.pdf_header = file_obj.read(5)
982 project.import_file.side_effect = read_pdf_bytes
984 exporter.export_task(self.req, exported_task_redcap)
985 self.assertEqual(exported_task_redcap.redcap_record_id, "123")
986 self.assertEqual(
987 exported_task_redcap.redcap_instrument_name, "medication_table"
988 )
989 self.assertEqual(exported_task_redcap.redcap_instance_id, 1)
991 args, kwargs = project.import_file.call_args
993 record_id = args[0]
994 fieldname = args[1]
995 filename = args[2]
997 self.assertEqual(record_id, "123")
998 self.assertEqual(fieldname, "medtbl_medication_items")
999 self.assertEqual(
1000 filename,
1001 "khandaker_mojo_medicationtherapy_123_medtbl_medication_items",
1002 )
1004 self.assertEqual(kwargs["repeat_instance"], 1)
1005 # noinspection PyUnresolvedReferences
1006 self.assertEqual(read_pdf_bytes.pdf_header, b"%PDF-")
1007 self.assertEqual(kwargs["event"], "event_1_arm_1")
1010class MultipleTaskRedcapExportTests(RedcapExportTestCase):
1011 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
1012<fieldmap>
1013 <patient instrument="patient_record" redcap_field="patient_id" />
1014 <record instrument="patient_record" redcap_field="record_id" />
1015 <instruments>
1016 <instrument task="bmi" name="bmi" event="bmi_event">
1017 <fields>
1018 <field name="pa_height" formula="format(task.height_m, '.1f')" />
1019 <field name="pa_weight" formula="format(task.mass_kg, '.1f')" />
1020 <field name="bmi_date" formula="format_datetime(task.when_created, DateFormat.ISO8601_DATE_ONLY)" />
1021 </fields>
1022 </instrument>
1023 <instrument task="khandaker_mojo_medicationtherapy" name="medication_table" event="mojo_event">
1024 <files>
1025 <field name="medtbl_medication_items" formula="task.get_pdf(request)" />
1026 </files>
1027 </instrument>
1028 </instruments>
1029</fieldmap>
1030""" # noqa: E501
1032 def __init__(self, *args, **kwargs) -> None:
1033 super().__init__(*args, **kwargs)
1034 self.id_sequence = self.get_id()
1036 @staticmethod
1037 def get_id() -> Generator[int, None, None]:
1038 i = 1
1040 while True:
1041 yield i
1042 i += 1
1044 def create_tasks(self) -> None:
1045 from camcops_server.tasks.khandaker_mojo_medicationtherapy import (
1046 KhandakerMojoMedicationTherapy,
1047 )
1049 patient = self.create_patient_with_idnum_1001()
1050 self.mojo_task = KhandakerMojoMedicationTherapy()
1051 self.apply_standard_task_fields(self.mojo_task)
1052 self.mojo_task.id = next(self.id_sequence)
1053 self.mojo_task.patient_id = patient.id
1054 self.dbsession.add(self.mojo_task)
1055 self.dbsession.commit()
1057 from camcops_server.tasks.bmi import Bmi
1059 self.bmi_task = Bmi()
1060 self.apply_standard_task_fields(self.bmi_task)
1061 self.bmi_task.id = next(self.id_sequence)
1062 self.bmi_task.height_m = 1.83
1063 self.bmi_task.mass_kg = 67.57
1064 self.bmi_task.patient_id = patient.id
1065 self.dbsession.add(self.bmi_task)
1066 self.dbsession.commit()
1068 def test_instance_ids_on_different_tasks_in_same_record(self) -> None:
1069 from camcops_server.cc_modules.cc_exportmodels import (
1070 ExportedTask,
1071 ExportedTaskRedcap,
1072 )
1074 exporter = MockRedcapTaskExporter()
1075 project = exporter.get_project()
1076 project.export_records.return_value = DataFrame({"patient_id": []})
1077 project.import_records.return_value = ["123,0"]
1078 project.export_project_info.return_value = {
1079 "record_autonumbering_enabled": 1
1080 }
1082 exported_task_mojo = ExportedTask(
1083 task=self.mojo_task, recipient=self.recipient
1084 )
1085 exported_task_redcap_mojo = ExportedTaskRedcap(exported_task_mojo)
1086 exporter.export_task(self.req, exported_task_redcap_mojo)
1087 self.assertEqual(exported_task_redcap_mojo.redcap_record_id, "123")
1088 args, kwargs = project.import_file.call_args
1090 self.assertEqual(kwargs["repeat_instance"], 1)
1092 project.export_records.return_value = DataFrame(
1093 {
1094 "record_id": ["123"],
1095 "patient_id": [555],
1096 "redcap_repeat_instrument": [
1097 "khandaker_mojo_medicationtherapy"
1098 ],
1099 "redcap_repeat_instance": [1],
1100 }
1101 )
1102 exported_task_bmi = ExportedTask(
1103 task=self.bmi_task, recipient=self.recipient
1104 )
1105 exported_task_redcap_bmi = ExportedTaskRedcap(exported_task_bmi)
1107 exporter.export_task(self.req, exported_task_redcap_bmi)
1109 # Import of second task, but is first instance
1110 # (third call to import_records)
1111 args, kwargs = project.import_records.call_args_list[2]
1113 rows = args[0]
1114 record = rows[0]
1116 self.assertEqual(record["redcap_repeat_instance"], 1)
1118 def test_imported_into_different_events(self) -> None:
1119 from camcops_server.cc_modules.cc_exportmodels import (
1120 ExportedTask,
1121 ExportedTaskRedcap,
1122 )
1124 exporter = MockRedcapTaskExporter()
1125 project = exporter.get_project()
1127 project.is_longitudinal = mock.Mock(return_value=True)
1128 project.export_records.return_value = DataFrame({"patient_id": []})
1129 project.import_records.return_value = ["123,0"]
1130 project.export_project_info.return_value = {
1131 "record_autonumbering_enabled": 1
1132 }
1134 exported_task_mojo = ExportedTask(
1135 task=self.mojo_task, recipient=self.recipient
1136 )
1137 exported_task_redcap_mojo = ExportedTaskRedcap(exported_task_mojo)
1139 exporter.export_task(self.req, exported_task_redcap_mojo)
1141 args, kwargs = project.import_records.call_args_list[0]
1142 rows = args[0]
1143 record = rows[0]
1145 self.assertEqual(record["redcap_event_name"], "mojo_event")
1146 args, kwargs = project.import_file.call_args
1148 self.assertEqual(kwargs["event"], "mojo_event")
1150 exported_task_bmi = ExportedTask(
1151 task=self.bmi_task, recipient=self.recipient
1152 )
1153 exported_task_redcap_bmi = ExportedTaskRedcap(exported_task_bmi)
1155 exporter.export_task(self.req, exported_task_redcap_bmi)
1157 # Import of second task (third call to import_records)
1158 args, kwargs = project.import_records.call_args_list[2]
1159 rows = args[0]
1160 record = rows[0]
1161 self.assertEqual(record["redcap_event_name"], "bmi_event")
1164class BadConfigurationRedcapTests(RedcapExportTestCase):
1165 def __init__(self, *args, **kwargs) -> None:
1166 super().__init__(*args, **kwargs)
1167 self.id_sequence = self.get_id()
1169 @staticmethod
1170 def get_id() -> Generator[int, None, None]:
1171 i = 1
1173 while True:
1174 yield i
1175 i += 1
1177 def create_tasks(self) -> None:
1178 from camcops_server.tasks.bmi import Bmi
1180 patient = self.create_patient_with_idnum_1001()
1181 self.task = Bmi()
1182 self.apply_standard_task_fields(self.task)
1183 self.task.id = next(self.id_sequence)
1184 self.task.height_m = 1.83
1185 self.task.mass_kg = 67.57
1186 self.task.patient_id = patient.id
1187 self.dbsession.add(self.task)
1188 self.dbsession.commit()
1191class MissingInstrumentRedcapTests(BadConfigurationRedcapTests):
1192 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
1193<fieldmap>
1194 <patient instrument="patient_record" redcap_field="patient_id" />
1195 <record instrument="patient_record" redcap_field="record_id" />
1196 <instruments>
1197 <instrument task="phq9" name="patient_health_questionnaire_9">
1198 <fields>
1199 </fields>
1200 </instrument>
1201 </instruments>
1202</fieldmap>""" # noqa: E501
1204 def test_raises_when_instrument_missing_from_fieldmap(self) -> None:
1205 from camcops_server.cc_modules.cc_exportmodels import (
1206 ExportedTask,
1207 ExportedTaskRedcap,
1208 )
1210 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
1211 exported_task_redcap = ExportedTaskRedcap(exported_task)
1213 exporter = MockRedcapTaskExporter()
1214 project = exporter.get_project()
1215 project.export_records.return_value = DataFrame({"patient_id": []})
1216 project.import_records.return_value = ["123,0"]
1218 with self.assertRaises(RedcapExportException) as cm:
1219 exporter.export_task(self.req, exported_task_redcap)
1221 message = str(cm.exception)
1222 self.assertIn(
1223 "Instrument for task 'bmi' is missing from the fieldmap", message
1224 )
1227class IncorrectRecordIdRedcapTests(BadConfigurationRedcapTests):
1228 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
1229<fieldmap>
1230 <patient instrument="patient_record" redcap_field="patient_id" />
1231 <record instrument="patient_record" redcap_field="my_record_id" />
1232 <instruments>
1233 <instrument task="bmi" name="bmi">
1234 <fields>
1235 </fields>
1236 </instrument>
1237 </instruments>
1238</fieldmap>""" # noqa: E501
1240 def test_raises_when_record_id_is_incorrect(self) -> None:
1241 from camcops_server.cc_modules.cc_exportmodels import (
1242 ExportedTask,
1243 ExportedTaskRedcap,
1244 )
1246 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
1247 exported_task_redcap = ExportedTaskRedcap(exported_task)
1249 exporter = MockRedcapTaskExporter()
1250 project = exporter.get_project()
1251 project.export_records.return_value = DataFrame(
1252 {
1253 "record_id": ["123"],
1254 "patient_id": [555],
1255 "redcap_repeat_instrument": ["bmi"],
1256 "redcap_repeat_instance": [1],
1257 }
1258 )
1259 project.import_records.return_value = ["123,0"]
1260 project.export_project_info.return_value = {
1261 "record_autonumbering_enabled": 1
1262 }
1264 with self.assertRaises(RedcapExportException) as cm:
1265 exporter.export_task(self.req, exported_task_redcap)
1267 message = str(cm.exception)
1268 self.assertIn("Field 'my_record_id' does not exist in REDCap", message)
1271class IncorrectPatientIdRedcapTests(BadConfigurationRedcapTests):
1272 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
1273<fieldmap>
1274 <patient instrument="patient_record" redcap_field="my_patient_id" />
1275 <record instrument="patient_record" redcap_field="record_id" />
1276 <instruments>
1277 <instrument task="bmi" name="bmi">
1278 <fields>
1279 </fields>
1280 </instrument>
1281 </instruments>
1282</fieldmap>""" # noqa: E501
1284 def test_raises_when_patient_id_is_incorrect(self) -> None:
1285 from camcops_server.cc_modules.cc_exportmodels import (
1286 ExportedTask,
1287 ExportedTaskRedcap,
1288 )
1290 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
1291 exported_task_redcap = ExportedTaskRedcap(exported_task)
1293 exporter = MockRedcapTaskExporter()
1294 project = exporter.get_project()
1295 project.export_records.return_value = DataFrame(
1296 {
1297 "record_id": ["123"],
1298 "patient_id": [555],
1299 "redcap_repeat_instrument": ["bmi"],
1300 "redcap_repeat_instance": [1],
1301 }
1302 )
1303 project.import_records.return_value = ["123,0"]
1304 project.export_project_info.return_value = {
1305 "record_autonumbering_enabled": 1
1306 }
1308 with self.assertRaises(RedcapExportException) as cm:
1309 exporter.export_task(self.req, exported_task_redcap)
1311 message = str(cm.exception)
1312 self.assertIn(
1313 "Field 'my_patient_id' does not exist in REDCap", message
1314 )
1317class MissingPatientInstrumentRedcapTests(BadConfigurationRedcapTests):
1318 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
1319<fieldmap>
1320 <patient instrument="patient_record" redcap_field="my_patient_id" />
1321 <record instrument="patient_record" redcap_field="record_id" />
1322 <instruments>
1323 <instrument task="bmi" name="bmi">
1324 <fields>
1325 </fields>
1326 </instrument>
1327 </instruments>
1328</fieldmap>""" # noqa: E501
1330 def test_raises_when_instrument_is_missing(self) -> None:
1331 from camcops_server.cc_modules.cc_exportmodels import (
1332 ExportedTask,
1333 ExportedTaskRedcap,
1334 )
1336 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
1337 exported_task_redcap = ExportedTaskRedcap(exported_task)
1339 exporter = MockRedcapTaskExporter()
1340 project = exporter.get_project()
1341 project.export_records.side_effect = redcap.RedcapError(
1342 "Something went wrong"
1343 )
1345 with self.assertRaises(RedcapExportException) as cm:
1346 exporter.export_task(self.req, exported_task_redcap)
1348 message = str(cm.exception)
1349 self.assertIn("Something went wrong", message)
1352class MissingEventRedcapTests(BadConfigurationRedcapTests):
1353 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
1354<fieldmap>
1355 <patient instrument="patient_record" redcap_field="my_patient_id" />
1356 <record instrument="patient_record" redcap_field="record_id" />
1357 <instruments>
1358 <instrument task="bmi" name="bmi">
1359 <fields>
1360 </fields>
1361 </instrument>
1362 </instruments>
1363</fieldmap>""" # noqa: E501
1365 def test_raises_for_longitudinal_project(self) -> None:
1366 from camcops_server.cc_modules.cc_exportmodels import (
1367 ExportedTask,
1368 ExportedTaskRedcap,
1369 )
1371 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
1372 exported_task_redcap = ExportedTaskRedcap(exported_task)
1374 exporter = MockRedcapTaskExporter()
1375 project = exporter.get_project()
1377 project.is_longitudinal = mock.Mock(return_value=True)
1379 with self.assertRaises(RedcapExportException) as cm:
1380 exporter.export_task(self.req, exported_task_redcap)
1382 message = str(cm.exception)
1383 self.assertEqual(MISSING_EVENT_TAG_OR_ATTRIBUTE, message)
1386class MissingInstrumentEventRedcapTests(BadConfigurationRedcapTests):
1387 fieldmap = """<?xml version="1.0" encoding="UTF-8"?>
1388<fieldmap>
1389 <patient instrument="patient_record" redcap_field="my_patient_id" />
1390 <record instrument="patient_record" redcap_field="record_id" />
1391 <instruments>
1392 <instrument task="bmi" name="bmi">
1393 <fields>
1394 </fields>
1395 </instrument>
1396 <instrument task="phq9" name="phq9" event="phq9_event">
1397 <fields>
1398 </fields>
1399 </instrument>
1400 </instruments>
1401</fieldmap>""" # noqa: E501
1403 def test_raises_when_instrument_missing_event(self) -> None:
1404 from camcops_server.cc_modules.cc_exportmodels import (
1405 ExportedTask,
1406 ExportedTaskRedcap,
1407 )
1409 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
1410 exported_task_redcap = ExportedTaskRedcap(exported_task)
1412 exporter = MockRedcapTaskExporter()
1413 project = exporter.get_project()
1415 project.is_longitudinal = mock.Mock(return_value=True)
1417 with self.assertRaises(RedcapExportException) as cm:
1418 exporter.export_task(self.req, exported_task_redcap)
1420 message = str(cm.exception)
1421 self.assertEqual(MISSING_EVENT_TAG_OR_ATTRIBUTE, message)
1424class AnonymousTaskRedcapTests(RedcapExportTestCase):
1425 def create_tasks(self) -> None:
1426 from camcops_server.tasks.apeq_cpft_perinatal import APEQCPFTPerinatal
1428 self.task = APEQCPFTPerinatal()
1429 self.apply_standard_task_fields(self.task)
1430 self.task.id = 1
1431 self.dbsession.add(self.task)
1432 self.dbsession.commit()
1434 def test_raises_when_task_is_anonymous(self) -> None:
1435 from camcops_server.cc_modules.cc_exportmodels import (
1436 ExportedTask,
1437 ExportedTaskRedcap,
1438 )
1440 exported_task = ExportedTask(task=self.task, recipient=self.recipient)
1441 exported_task_redcap = ExportedTaskRedcap(exported_task)
1443 exporter = MockRedcapTaskExporter()
1445 with self.assertRaises(RedcapExportException) as cm:
1446 exporter.export_task(self.req, exported_task_redcap)
1448 message = str(cm.exception)
1449 self.assertIn("Skipping anonymous task 'apeq_cpft_perinatal'", message)