Coverage for cc_modules/tests/cc_redcap_tests.py: 19%

558 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-30 13:48 +0000

1"""camcops_server/cc_modules/tests/cc_redcap_tests.py 

2 

3=============================================================================== 

4 

5 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

6 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

7 

8 This file is part of CamCOPS. 

9 

10 CamCOPS is free software: you can redistribute it and/or modify 

11 it under the terms of the GNU General Public License as published by 

12 the Free Software Foundation, either version 3 of the License, or 

13 (at your option) any later version. 

14 

15 CamCOPS is distributed in the hope that it will be useful, 

16 but WITHOUT ANY WARRANTY; without even the implied warranty of 

17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18 GNU General Public License for more details. 

19 

20 You should have received a copy of the GNU General Public License 

21 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

22 

23=============================================================================== 

24""" 

25 

26import os 

27import tempfile 

28from typing import Any, Dict, Generator 

29from unittest import mock, TestCase 

30 

31from pandas import DataFrame 

32import pendulum 

33import redcap 

34 

35from camcops_server.cc_modules.cc_constants import ConfigParamExportRecipient 

36from camcops_server.cc_modules.cc_exportmodels import ( 

37 ExportedTask, 

38 ExportedTaskRedcap, 

39) 

40from camcops_server.cc_modules.cc_exportrecipient import ExportRecipient 

41from camcops_server.cc_modules.cc_exportrecipientinfo import ( 

42 ExportRecipientInfo, 

43) 

44from camcops_server.cc_modules.cc_redcap import ( 

45 MISSING_EVENT_TAG_OR_ATTRIBUTE, 

46 RedcapExportException, 

47 RedcapFieldmap, 

48 RedcapNewRecordUploader, 

49 RedcapRecordStatus, 

50 RedcapTaskExporter, 

51) 

52from camcops_server.cc_modules.cc_testfactories import ( 

53 NHSPatientIdNumFactory, 

54 PatientFactory, 

55) 

56from camcops_server.cc_modules.cc_unittest import DemoRequestTestCase 

57from camcops_server.tasks.tests.factories import ( 

58 APEQCPFTPerinatalFactory, 

59 BmiFactory, 

60 KhandakerMojoMedicationTherapyFactory, 

61 Phq9Factory, 

62) 

63 

64# ============================================================================= 

65# Unit testing 

66# ============================================================================= 

67 

68 

69class MockProject(mock.Mock): 

70 def __init__(self, *args, **kwargs) -> None: 

71 super().__init__(*args, **kwargs) 

72 

73 self.export_project_info = mock.Mock() 

74 self.export_records = mock.Mock() 

75 self.generate_next_record_name = mock.Mock() 

76 self.import_file = mock.Mock() 

77 self.import_records = mock.Mock() 

78 self.is_longitudinal = mock.Mock(return_value=False) 

79 

80 

81class MockRedcapTaskExporter(RedcapTaskExporter): 

82 def __init__(self) -> None: 

83 mock_project = MockProject() 

84 self.get_project = mock.Mock(return_value=mock_project) 

85 

86 config = mock.Mock() 

87 self.req = mock.Mock(config=config) 

88 

89 

90class MockRedcapNewRecordUploader(RedcapNewRecordUploader): 

91 # noinspection PyMissingConstructor 

92 def __init__(self) -> None: 

93 self.req = mock.Mock() 

94 self.project = MockProject() 

95 self.task = mock.Mock(tablename="mock_task") 

96 

97 

98class RedcapExporterTests(TestCase): 

99 def test_next_instance_id_converted_to_int(self) -> None: 

100 import numpy 

101 

102 records = DataFrame( 

103 { 

104 "record_id": ["1", "1", "1", "1", "1"], 

105 "redcap_repeat_instrument": [ 

106 "bmi", 

107 "bmi", 

108 "bmi", 

109 "bmi", 

110 "bmi", 

111 ], 

112 "redcap_repeat_instance": [ 

113 numpy.float64(1.0), 

114 numpy.float64(2.0), 

115 numpy.float64(3.0), 

116 numpy.float64(4.0), 

117 numpy.float64(5.0), 

118 ], 

119 } 

120 ) 

121 

122 next_instance_id = RedcapTaskExporter._get_next_instance_id( 

123 records, "bmi", "record_id", "1" 

124 ) 

125 

126 self.assertEqual(next_instance_id, 6) 

127 self.assertEqual(type(next_instance_id), int) 

128 

129 

130class RedcapExportErrorTests(TestCase): 

131 def test_raises_when_fieldmap_has_unknown_symbols(self) -> None: 

132 exporter = MockRedcapNewRecordUploader() 

133 

134 task = mock.Mock(tablename="bmi") 

135 fieldmap = {"pa_height": "sys.platform"} 

136 

137 field_dict: Dict[str, Any] = {} 

138 

139 with self.assertRaises(RedcapExportException) as cm: 

140 exporter.transform_fields(field_dict, task, fieldmap) 

141 

142 message = str(cm.exception) 

143 self.assertIn("Error in formula 'sys.platform':", message) 

144 self.assertIn("Task: 'bmi'", message) 

145 self.assertIn("REDCap field: 'pa_height'", message) 

146 self.assertIn("'sys' is not defined", message) 

147 

148 def test_raises_when_fieldmap_empty_in_config(self) -> None: 

149 

150 exporter = MockRedcapTaskExporter() 

151 

152 recipient = mock.Mock(redcap_fieldmap_filename="") 

153 with self.assertRaises(RedcapExportException) as cm: 

154 exporter.get_fieldmap_filename(recipient) 

155 

156 message = str(cm.exception) 

157 self.assertIn( 

158 f"{ConfigParamExportRecipient.REDCAP_FIELDMAP_FILENAME} " 

159 f"is empty in the config file", 

160 message, 

161 ) 

162 

163 def test_raises_when_fieldmap_not_set_in_config(self) -> None: 

164 

165 exporter = MockRedcapTaskExporter() 

166 

167 recipient = mock.Mock(redcap_fieldmap_filename=None) 

168 with self.assertRaises(RedcapExportException) as cm: 

169 exporter.get_fieldmap_filename(recipient) 

170 

171 message = str(cm.exception) 

172 self.assertIn( 

173 f"{ConfigParamExportRecipient.REDCAP_FIELDMAP_FILENAME} " 

174 f"is not set in the config file", 

175 message, 

176 ) 

177 

178 def test_raises_when_error_from_redcap_on_import(self) -> None: 

179 exporter = MockRedcapNewRecordUploader() 

180 exporter.project.import_records.side_effect = redcap.RedcapError( 

181 "Something went wrong" 

182 ) 

183 

184 with self.assertRaises(RedcapExportException) as cm: 

185 record: Dict[str, Any] = {} 

186 exporter.upload_record(record) 

187 message = str(cm.exception) 

188 

189 self.assertIn("Something went wrong", message) 

190 

191 def test_raises_when_error_from_redcap_on_init(self) -> None: 

192 with mock.patch("redcap.project.Project.__init__") as mock_init: 

193 mock_init.side_effect = redcap.RedcapError("Something went wrong") 

194 

195 with self.assertRaises(RedcapExportException) as cm: 

196 exporter = RedcapTaskExporter() 

197 recipient = mock.Mock() 

198 exporter.get_project(recipient) 

199 

200 message = str(cm.exception) 

201 

202 self.assertIn("Something went wrong", message) 

203 

204 def test_raises_when_field_not_a_file_field(self) -> None: 

205 exporter = MockRedcapNewRecordUploader() 

206 exporter.project.import_file.side_effect = ValueError( 

207 "Error with file field" 

208 ) 

209 

210 task = mock.Mock() 

211 

212 with self.assertRaises(RedcapExportException) as cm: 

213 record_id = 1 

214 repeat_instance = 1 

215 file_dict = {"medication_items": b"not a real file"} 

216 exporter.upload_files(task, record_id, repeat_instance, file_dict) 

217 message = str(cm.exception) 

218 

219 self.assertIn("Error with file field", message) 

220 

221 def test_raises_when_error_from_redcap_on_import_file(self) -> None: 

222 exporter = MockRedcapNewRecordUploader() 

223 exporter.project.import_file.side_effect = redcap.RedcapError( 

224 "Something went wrong" 

225 ) 

226 

227 task = mock.Mock() 

228 

229 with self.assertRaises(RedcapExportException) as cm: 

230 record_id = 1 

231 repeat_instance = 1 

232 file_dict = {"medication_items": b"not a real file"} 

233 exporter.upload_files(task, record_id, repeat_instance, file_dict) 

234 message = str(cm.exception) 

235 

236 self.assertIn("Something went wrong", message) 

237 

238 

239class RedcapFieldmapTests(TestCase): 

240 def test_raises_when_xml_file_missing(self) -> None: 

241 with self.assertRaises(RedcapExportException) as cm: 

242 RedcapFieldmap("/does/not/exist/bmi.xml") 

243 

244 message = str(cm.exception) 

245 

246 self.assertIn("Unable to open fieldmap file", message) 

247 self.assertIn("bmi.xml", message) 

248 

249 def test_raises_when_fieldmap_missing(self) -> None: 

250 with tempfile.NamedTemporaryFile( 

251 mode="w", suffix="xml" 

252 ) as fieldmap_file: 

253 fieldmap_file.write( 

254 """<?xml version="1.0" encoding="UTF-8"?> 

255<someothertag></someothertag> 

256""" 

257 ) 

258 fieldmap_file.flush() 

259 

260 with self.assertRaises(RedcapExportException) as cm: 

261 RedcapFieldmap(fieldmap_file.name) 

262 

263 message = str(cm.exception) 

264 self.assertIn( 

265 ( 

266 "Expected the root tag to be 'fieldmap' instead of " 

267 "'someothertag'" 

268 ), 

269 message, 

270 ) 

271 self.assertIn(fieldmap_file.name, message) 

272 

273 def test_raises_when_root_tag_missing(self) -> None: 

274 with tempfile.NamedTemporaryFile( 

275 mode="w", suffix="xml" 

276 ) as fieldmap_file: 

277 fieldmap_file.write( 

278 """<?xml version="1.0" encoding="UTF-8"?> 

279""" 

280 ) 

281 fieldmap_file.flush() 

282 

283 with self.assertRaises(RedcapExportException) as cm: 

284 RedcapFieldmap(fieldmap_file.name) 

285 

286 message = str(cm.exception) 

287 self.assertIn("There was a problem parsing", message) 

288 self.assertIn(fieldmap_file.name, message) 

289 

290 def test_raises_when_patient_missing(self) -> None: 

291 with tempfile.NamedTemporaryFile( 

292 mode="w", suffix="xml" 

293 ) as fieldmap_file: 

294 fieldmap_file.write( 

295 """<?xml version="1.0" encoding="UTF-8"?> 

296 <fieldmap> 

297 </fieldmap> 

298 """ 

299 ) 

300 fieldmap_file.flush() 

301 

302 with self.assertRaises(RedcapExportException) as cm: 

303 RedcapFieldmap(fieldmap_file.name) 

304 

305 message = str(cm.exception) 

306 self.assertIn("'patient' is missing from", message) 

307 self.assertIn(fieldmap_file.name, message) 

308 

309 def test_raises_when_patient_missing_attributes(self) -> None: 

310 with tempfile.NamedTemporaryFile( 

311 mode="w", suffix="xml" 

312 ) as fieldmap_file: 

313 fieldmap_file.write( 

314 """<?xml version="1.0" encoding="UTF-8"?> 

315 <fieldmap> 

316 <patient /> 

317 </fieldmap> 

318 """ 

319 ) 

320 fieldmap_file.flush() 

321 

322 with self.assertRaises(RedcapExportException) as cm: 

323 RedcapFieldmap(fieldmap_file.name) 

324 

325 message = str(cm.exception) 

326 self.assertIn( 

327 "'patient' must have attributes: instrument, redcap_field", message 

328 ) 

329 self.assertIn(fieldmap_file.name, message) 

330 

331 def test_raises_when_record_missing(self) -> None: 

332 with tempfile.NamedTemporaryFile( 

333 mode="w", suffix="xml" 

334 ) as fieldmap_file: 

335 fieldmap_file.write( 

336 """<?xml version="1.0" encoding="UTF-8"?> 

337 <fieldmap> 

338 <patient instrument="patient_record" redcap_field="patient_id" /> 

339 </fieldmap> 

340 """ # noqa: E501 

341 ) 

342 fieldmap_file.flush() 

343 

344 with self.assertRaises(RedcapExportException) as cm: 

345 RedcapFieldmap(fieldmap_file.name) 

346 

347 message = str(cm.exception) 

348 self.assertIn("'record' is missing from", message) 

349 self.assertIn(fieldmap_file.name, message) 

350 

351 def test_raises_when_record_missing_attributes(self) -> None: 

352 with tempfile.NamedTemporaryFile( 

353 mode="w", suffix="xml" 

354 ) as fieldmap_file: 

355 fieldmap_file.write( 

356 """<?xml version="1.0" encoding="UTF-8"?> 

357 <fieldmap> 

358 <patient instrument="patient_record" redcap_field="patient_id" /> 

359 <record /> 

360 </fieldmap> 

361 """ # noqa: E501 

362 ) 

363 fieldmap_file.flush() 

364 

365 with self.assertRaises(RedcapExportException) as cm: 

366 RedcapFieldmap(fieldmap_file.name) 

367 

368 message = str(cm.exception) 

369 self.assertIn( 

370 "'record' must have attributes: instrument, redcap_field", message 

371 ) 

372 self.assertIn(fieldmap_file.name, message) 

373 

374 def test_raises_when_instruments_missing(self) -> None: 

375 with tempfile.NamedTemporaryFile( 

376 mode="w", suffix="xml" 

377 ) as fieldmap_file: 

378 fieldmap_file.write( 

379 """<?xml version="1.0" encoding="UTF-8"?> 

380 <fieldmap> 

381 <patient instrument="patient_record" redcap_field="patient_id" /> 

382 <record instrument="patient_record" redcap_field="record_id" /> 

383 </fieldmap> 

384 """ # noqa: E501 

385 ) 

386 fieldmap_file.flush() 

387 

388 with self.assertRaises(RedcapExportException) as cm: 

389 RedcapFieldmap(fieldmap_file.name) 

390 

391 message = str(cm.exception) 

392 self.assertIn("'instruments' tag is missing from", message) 

393 self.assertIn(fieldmap_file.name, message) 

394 

395 def test_raises_when_instruments_missing_attributes(self) -> None: 

396 with tempfile.NamedTemporaryFile( 

397 mode="w", suffix="xml" 

398 ) as fieldmap_file: 

399 fieldmap_file.write( 

400 """<?xml version="1.0" encoding="UTF-8"?> 

401 <fieldmap> 

402 <patient instrument="patient_record" redcap_field="patient_id" /> 

403 <record instrument="patient_record" redcap_field="record_id" /> 

404 <instruments> 

405 <instrument /> 

406 </instruments> 

407 </fieldmap> 

408 """ # noqa: E501 

409 ) 

410 fieldmap_file.flush() 

411 

412 with self.assertRaises(RedcapExportException) as cm: 

413 RedcapFieldmap(fieldmap_file.name) 

414 

415 message = str(cm.exception) 

416 self.assertIn("'instrument' must have attributes: name, task", message) 

417 self.assertIn(fieldmap_file.name, message) 

418 

419 def test_raises_when_file_fields_missing_attributes(self) -> None: 

420 with tempfile.NamedTemporaryFile( 

421 mode="w", suffix="xml" 

422 ) as fieldmap_file: 

423 fieldmap_file.write( 

424 """<?xml version="1.0" encoding="UTF-8"?> 

425 <fieldmap> 

426 <patient instrument="patient_record" redcap_field="patient_id" /> 

427 <record instrument="patient_record" redcap_field="record_id" /> 

428 <instruments> 

429 <instrument name="bmi" task="bmi"> 

430 <files> 

431 <field /> 

432 </files> 

433 </instrument> 

434 </instruments> 

435 </fieldmap> 

436 """ # noqa: E501 

437 ) 

438 fieldmap_file.flush() 

439 

440 with self.assertRaises(RedcapExportException) as cm: 

441 RedcapFieldmap(fieldmap_file.name) 

442 

443 message = str(cm.exception) 

444 self.assertIn("'field' must have attributes: name, formula", message) 

445 self.assertIn(fieldmap_file.name, message) 

446 

447 def test_raises_when_fields_missing_attributes(self) -> None: 

448 with tempfile.NamedTemporaryFile( 

449 mode="w", suffix="xml" 

450 ) as fieldmap_file: 

451 fieldmap_file.write( 

452 """<?xml version="1.0" encoding="UTF-8"?> 

453 <fieldmap> 

454 <patient instrument="patient_record" redcap_field="patient_id" /> 

455 <record instrument="patient_record" redcap_field="record_id" /> 

456 <instruments> 

457 <instrument name="bmi" task="bmi"> 

458 <fields> 

459 <field /> 

460 </fields> 

461 </instrument> 

462 </instruments> 

463 </fieldmap> 

464 """ # noqa: E501 

465 ) 

466 fieldmap_file.flush() 

467 

468 with self.assertRaises(RedcapExportException) as cm: 

469 RedcapFieldmap(fieldmap_file.name) 

470 

471 message = str(cm.exception) 

472 self.assertIn("'field' must have attributes: name, formula", message) 

473 self.assertIn(fieldmap_file.name, message) 

474 

475 

476# ============================================================================= 

477# Integration testing 

478# ============================================================================= 

479 

480 

481class RedcapExportTestCase(DemoRequestTestCase): 

482 fieldmap = "" 

483 

484 def setUp(self) -> None: 

485 super().setUp() 

486 

487 self.patient = PatientFactory() 

488 self.patient_idnum = NHSPatientIdNumFactory(patient=self.patient) 

489 

490 recipientinfo = ExportRecipientInfo() 

491 

492 self.recipient = ExportRecipient(recipientinfo) 

493 self.recipient.primary_idnum = self.patient_idnum.which_idnum 

494 

495 # auto increment doesn't work for BigInteger with SQLite 

496 self.recipient.id = 1 

497 self.recipient.recipient_name = "test" 

498 self.recipient.redcap_fieldmap_filename = os.path.join( 

499 self.tmpdir_obj.name, "redcap_fieldmap.xml" 

500 ) 

501 self.write_fieldmaps(self.recipient.redcap_fieldmap_filename) 

502 

503 def write_fieldmaps(self, filename: str) -> None: 

504 with open(filename, "w") as f: 

505 f.write(self.fieldmap) 

506 

507 

508class BmiRedcapValidFieldmapTestCase(RedcapExportTestCase): 

509 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

510<fieldmap> 

511 <patient instrument="patient_record" redcap_field="patient_id" /> 

512 <record instrument="instrument_with_record_id" redcap_field="record_id" /> 

513 <instruments> 

514 <instrument task="bmi" name="bmi"> 

515 <fields> 

516 <field name="pa_height" formula="format(task.height_m, '.1f')" /> 

517 <field name="pa_weight" formula="format(task.mass_kg, '.1f')" /> 

518 <field name="bmi_date" formula="format_datetime(task.when_created, DateFormat.ISO8601_DATE_ONLY)" /> 

519 </fields> 

520 </instrument> 

521 </instruments> 

522</fieldmap>""" # noqa: E501 

523 

524 

525class BmiRedcapExportTests(BmiRedcapValidFieldmapTestCase): 

526 """ 

527 These are more of a test of the fieldmap code than anything 

528 related to the BMI task 

529 """ 

530 

531 def setUp(self) -> None: 

532 super().setUp() 

533 

534 self.task = BmiFactory( 

535 patient=self.patient, 

536 height_m=1.83, 

537 mass_kg=67.57, 

538 when_created=pendulum.parse("2010-07-07"), 

539 ) 

540 

541 def test_record_exported(self) -> None: 

542 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

543 exported_task_redcap = ExportedTaskRedcap(exported_task) 

544 

545 exporter = MockRedcapTaskExporter() 

546 project = exporter.get_project() 

547 project.export_records.return_value = DataFrame({"patient_id": []}) 

548 project.import_records.return_value = ["123,0"] 

549 project.export_project_info.return_value = { 

550 "record_autonumbering_enabled": 1 

551 } 

552 

553 exporter.export_task(self.req, exported_task_redcap) 

554 self.assertEqual(exported_task_redcap.redcap_record_id, "123") 

555 self.assertEqual(exported_task_redcap.redcap_instrument_name, "bmi") 

556 self.assertEqual(exported_task_redcap.redcap_instance_id, 1) 

557 

558 args, kwargs = project.export_records.call_args 

559 

560 self.assertIn("bmi", kwargs["forms"]) 

561 self.assertIn("patient_record", kwargs["forms"]) 

562 self.assertIn("instrument_with_record_id", kwargs["forms"]) 

563 

564 # Initial call with original record 

565 args, kwargs = project.import_records.call_args_list[0] 

566 

567 rows = args[0] 

568 record = rows[0] 

569 

570 self.assertEqual(record["redcap_repeat_instrument"], "bmi") 

571 self.assertEqual(record["redcap_repeat_instance"], 1) 

572 self.assertEqual(record["record_id"], "0") 

573 self.assertEqual( 

574 record["bmi_complete"], RedcapRecordStatus.COMPLETE.value 

575 ) 

576 self.assertEqual(record["bmi_date"], "2010-07-07") 

577 

578 self.assertEqual(record["pa_height"], "1.8") 

579 self.assertEqual(record["pa_weight"], "67.6") 

580 

581 self.assertEqual(kwargs["return_content"], "auto_ids") 

582 self.assertTrue(kwargs["force_auto_number"]) 

583 

584 # Second call with updated patient ID 

585 args, kwargs = project.import_records.call_args_list[1] 

586 rows = args[0] 

587 record = rows[0] 

588 

589 self.assertEqual(record["patient_id"], self.patient_idnum.idnum_value) 

590 

591 def test_record_exported_with_non_integer_id(self) -> None: 

592 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

593 exported_task_redcap = ExportedTaskRedcap(exported_task) 

594 

595 exporter = MockRedcapTaskExporter() 

596 project = exporter.get_project() 

597 project.export_records.return_value = DataFrame({"patient_id": []}) 

598 project.import_records.return_value = ["15-123,0"] 

599 project.export_project_info.return_value = { 

600 "record_autonumbering_enabled": 1 

601 } 

602 

603 exporter.export_task(self.req, exported_task_redcap) 

604 self.assertEqual(exported_task_redcap.redcap_record_id, "15-123") 

605 

606 def test_record_id_generated_when_no_autonumbering(self) -> None: 

607 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

608 exported_task_redcap = ExportedTaskRedcap(exported_task) 

609 

610 exporter = MockRedcapTaskExporter() 

611 project = exporter.get_project() 

612 project.export_records.return_value = DataFrame({"patient_id": []}) 

613 project.import_records.return_value = {"count": 1} 

614 project.export_project_info.return_value = { 

615 "record_autonumbering_enabled": 0 

616 } 

617 project.generate_next_record_name.return_value = "15-29" 

618 

619 exporter.export_task(self.req, exported_task_redcap) 

620 

621 # Initial call with original record 

622 args, kwargs = project.import_records.call_args_list[0] 

623 

624 rows = args[0] 

625 record = rows[0] 

626 

627 self.assertEqual(record["record_id"], "15-29") 

628 self.assertEqual(kwargs["return_content"], "count") 

629 self.assertFalse(kwargs["force_auto_number"]) 

630 

631 def test_record_imported_when_no_existing_records(self) -> None: 

632 exporter = MockRedcapTaskExporter() 

633 project = exporter.get_project() 

634 project.export_records.return_value = DataFrame() 

635 project.import_records.return_value = ["1,0"] 

636 project.export_project_info.return_value = { 

637 "record_autonumbering_enabled": 1 

638 } 

639 

640 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

641 exported_task_redcap = ExportedTaskRedcap(exported_task) 

642 exporter.export_task(self.req, exported_task_redcap) 

643 

644 self.assertEqual(exported_task_redcap.redcap_record_id, "1") 

645 self.assertEqual(exported_task_redcap.redcap_instrument_name, "bmi") 

646 self.assertEqual(exported_task_redcap.redcap_instance_id, 1) 

647 

648 

649class BmiRedcapUpdateTests(BmiRedcapValidFieldmapTestCase): 

650 def setUp(self) -> None: 

651 super().setUp() 

652 

653 self.task1 = BmiFactory( 

654 patient=self.patient, 

655 ) 

656 self.task2 = BmiFactory( 

657 patient=self.patient, 

658 ) 

659 

660 def test_existing_record_id_used_for_update(self) -> None: 

661 exporter = MockRedcapTaskExporter() 

662 project = exporter.get_project() 

663 project.export_records.return_value = DataFrame({"patient_id": []}) 

664 project.import_records.return_value = ["123,0"] 

665 project.export_project_info.return_value = { 

666 "record_autonumbering_enabled": 1 

667 } 

668 

669 exported_task1 = ExportedTask( 

670 task=self.task1, recipient=self.recipient 

671 ) 

672 exported_task_redcap1 = ExportedTaskRedcap(exported_task1) 

673 exporter.export_task(self.req, exported_task_redcap1) 

674 self.assertEqual(exported_task_redcap1.redcap_record_id, "123") 

675 self.assertEqual(exported_task_redcap1.redcap_instrument_name, "bmi") 

676 self.assertEqual(exported_task_redcap1.redcap_instance_id, 1) 

677 

678 project.export_records.return_value = DataFrame( 

679 { 

680 "record_id": ["123"], 

681 "patient_id": [self.patient_idnum.idnum_value], 

682 "redcap_repeat_instrument": ["bmi"], 

683 "redcap_repeat_instance": [1], 

684 } 

685 ) 

686 exported_task2 = ExportedTask( 

687 task=self.task2, recipient=self.recipient 

688 ) 

689 exported_task_redcap2 = ExportedTaskRedcap(exported_task2) 

690 

691 exporter.export_task(self.req, exported_task_redcap2) 

692 self.assertEqual(exported_task_redcap2.redcap_record_id, "123") 

693 self.assertEqual(exported_task_redcap2.redcap_instrument_name, "bmi") 

694 self.assertEqual(exported_task_redcap2.redcap_instance_id, 2) 

695 

696 # Third call (after initial record and patient ID) 

697 args, kwargs = project.import_records.call_args_list[2] 

698 

699 rows = args[0] 

700 record = rows[0] 

701 

702 self.assertEqual(record["record_id"], "123") 

703 self.assertEqual(record["redcap_repeat_instance"], 2) 

704 self.assertEqual(kwargs["return_content"], "count") 

705 self.assertFalse(kwargs["force_auto_number"]) 

706 

707 

708class Phq9RedcapExportTests(RedcapExportTestCase): 

709 """ 

710 These are more of a test of the fieldmap code than anything 

711 related to the PHQ9 task. For these we have also renamed the record_id 

712 field. 

713 """ 

714 

715 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

716<fieldmap> 

717 <patient instrument="patient_record" redcap_field="patient_id" /> 

718 <record instrument="patient_record" redcap_field="my_record_id" /> 

719 <instruments> 

720 <instrument task="phq9" name="patient_health_questionnaire_9"> 

721 <fields> 

722 <field name="phq9_how_difficult" formula="task.q10 + 1 if task.q10 is not None else None" /> 

723 <field name="phq9_total_score" formula="task.total_score()" /> 

724 <field name="phq9_first_name" formula="task.patient.forename" /> 

725 <field name="phq9_last_name" formula="task.patient.surname" /> 

726 <field name="phq9_date_enrolled" formula="format_datetime(task.when_created,DateFormat.ISO8601_DATE_ONLY)" /> 

727 <field name="phq9_1" formula="task.q1" /> 

728 <field name="phq9_2" formula="task.q2" /> 

729 <field name="phq9_3" formula="task.q3" /> 

730 <field name="phq9_4" formula="task.q4" /> 

731 <field name="phq9_5" formula="task.q5" /> 

732 <field name="phq9_6" formula="task.q6" /> 

733 <field name="phq9_7" formula="task.q7" /> 

734 <field name="phq9_8" formula="task.q8" /> 

735 <field name="phq9_9" formula="task.q9" /> 

736 </fields> 

737 </instrument> 

738 </instruments> 

739</fieldmap>""" # noqa: E501 

740 

741 def __init__(self, *args, **kwargs) -> None: 

742 super().__init__(*args, **kwargs) 

743 

744 def setUp(self) -> None: 

745 super().setUp() 

746 

747 self.task = Phq9Factory( 

748 patient=self.patient, 

749 q1=0, 

750 q2=1, 

751 q3=2, 

752 q4=3, 

753 q5=0, 

754 q6=1, 

755 q7=2, 

756 q8=3, 

757 q9=0, 

758 q10=3, 

759 when_created=pendulum.parse("2010-07-07"), 

760 ) 

761 

762 def test_record_exported(self) -> None: 

763 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

764 exported_task_redcap = ExportedTaskRedcap(exported_task) 

765 

766 exporter = MockRedcapTaskExporter() 

767 project = exporter.get_project() 

768 project.export_records.return_value = DataFrame({"patient_id": []}) 

769 project.import_records.return_value = ["123,0"] 

770 project.export_project_info.return_value = { 

771 "record_autonumbering_enabled": 1 

772 } 

773 

774 exporter.export_task(self.req, exported_task_redcap) 

775 self.assertEqual(exported_task_redcap.redcap_record_id, "123") 

776 self.assertEqual( 

777 exported_task_redcap.redcap_instrument_name, 

778 "patient_health_questionnaire_9", 

779 ) 

780 self.assertEqual(exported_task_redcap.redcap_instance_id, 1) 

781 

782 # Initial call with new record 

783 args, kwargs = project.import_records.call_args_list[0] 

784 

785 rows = args[0] 

786 record = rows[0] 

787 

788 self.assertEqual( 

789 record["redcap_repeat_instrument"], 

790 "patient_health_questionnaire_9", 

791 ) 

792 self.assertEqual(record["my_record_id"], "0") 

793 self.assertEqual( 

794 record["patient_health_questionnaire_9_complete"], 

795 RedcapRecordStatus.COMPLETE.value, 

796 ) 

797 self.assertEqual(record["phq9_how_difficult"], 4) 

798 self.assertEqual(record["phq9_total_score"], 12) 

799 self.assertEqual(record["phq9_first_name"], self.patient.forename) 

800 self.assertEqual(record["phq9_last_name"], self.patient.surname) 

801 self.assertEqual(record["phq9_date_enrolled"], "2010-07-07") 

802 

803 self.assertEqual(record["phq9_1"], 0) 

804 self.assertEqual(record["phq9_2"], 1) 

805 self.assertEqual(record["phq9_3"], 2) 

806 self.assertEqual(record["phq9_4"], 3) 

807 self.assertEqual(record["phq9_5"], 0) 

808 self.assertEqual(record["phq9_6"], 1) 

809 self.assertEqual(record["phq9_7"], 2) 

810 self.assertEqual(record["phq9_8"], 3) 

811 self.assertEqual(record["phq9_9"], 0) 

812 

813 self.assertEqual(kwargs["return_content"], "auto_ids") 

814 self.assertTrue(kwargs["force_auto_number"]) 

815 

816 # Second call with patient ID 

817 args, kwargs = project.import_records.call_args_list[1] 

818 

819 rows = args[0] 

820 record = rows[0] 

821 self.assertEqual(record["patient_id"], self.patient_idnum.idnum_value) 

822 

823 

824class MedicationTherapyRedcapExportTests(RedcapExportTestCase): 

825 """ 

826 These are more of a test of the file upload code than anything 

827 related to the KhandakerMojoMedicationTherapy task 

828 """ 

829 

830 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

831<fieldmap> 

832 <event name="event_1_arm_1" /> 

833 <patient instrument="patient_record" redcap_field="patient_id" /> 

834 <record instrument="patient_record" redcap_field="record_id" /> 

835 <instruments> 

836 <instrument task="khandaker_mojo_medicationtherapy" name="medication_table"> 

837 <files> 

838 <field name="medtbl_medication_items" formula="task.get_pdf(request)" /> 

839 </files> 

840 </instrument> 

841 </instruments> 

842</fieldmap>""" # noqa: E501 

843 

844 def __init__(self, *args, **kwargs) -> None: 

845 super().__init__(*args, **kwargs) 

846 self.id_sequence = self.get_id() 

847 

848 @staticmethod 

849 def get_id() -> Generator[int, None, None]: 

850 i = 1 

851 

852 while True: 

853 yield i 

854 i += 1 

855 

856 def setUp(self) -> None: 

857 super().setUp() 

858 

859 self.task = KhandakerMojoMedicationTherapyFactory( 

860 patient=self.patient, 

861 ) 

862 

863 def test_record_exported(self) -> None: 

864 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

865 exported_task_redcap = ExportedTaskRedcap(exported_task) 

866 

867 exporter = MockRedcapTaskExporter() 

868 project = exporter.get_project() 

869 project.export_records.return_value = DataFrame({"patient_id": []}) 

870 project.import_records.return_value = ["123,0"] 

871 project.export_project_info.return_value = { 

872 "record_autonumbering_enabled": 1 

873 } 

874 

875 # We can't just look at the call_args on the mock object because 

876 # the file will already have been closed by then 

877 # noinspection PyUnusedLocal 

878 def read_pdf_bytes(*import_file_args, **import_file_kwargs) -> None: 

879 # record, field, fname, fobj 

880 file_obj = import_file_args[3] 

881 read_pdf_bytes.pdf_header = file_obj.read(5) 

882 

883 project.import_file.side_effect = read_pdf_bytes 

884 

885 exporter.export_task(self.req, exported_task_redcap) 

886 self.assertEqual(exported_task_redcap.redcap_record_id, "123") 

887 self.assertEqual( 

888 exported_task_redcap.redcap_instrument_name, "medication_table" 

889 ) 

890 self.assertEqual(exported_task_redcap.redcap_instance_id, 1) 

891 

892 args, kwargs = project.import_file.call_args 

893 

894 record_id = args[0] 

895 fieldname = args[1] 

896 filename = args[2] 

897 

898 self.assertEqual(record_id, "123") 

899 self.assertEqual(fieldname, "medtbl_medication_items") 

900 self.assertEqual( 

901 filename, 

902 "khandaker_mojo_medicationtherapy_123_medtbl_medication_items", 

903 ) 

904 

905 self.assertEqual(kwargs["repeat_instance"], 1) 

906 # noinspection PyUnresolvedReferences 

907 self.assertEqual(read_pdf_bytes.pdf_header, b"%PDF-") 

908 self.assertEqual(kwargs["event"], "event_1_arm_1") 

909 

910 

911class MultipleTaskRedcapExportTests(RedcapExportTestCase): 

912 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

913<fieldmap> 

914 <patient instrument="patient_record" redcap_field="patient_id" /> 

915 <record instrument="patient_record" redcap_field="record_id" /> 

916 <instruments> 

917 <instrument task="bmi" name="bmi" event="bmi_event"> 

918 <fields> 

919 <field name="pa_height" formula="format(task.height_m, '.1f')" /> 

920 <field name="pa_weight" formula="format(task.mass_kg, '.1f')" /> 

921 <field name="bmi_date" formula="format_datetime(task.when_created, DateFormat.ISO8601_DATE_ONLY)" /> 

922 </fields> 

923 </instrument> 

924 <instrument task="khandaker_mojo_medicationtherapy" name="medication_table" event="mojo_event"> 

925 <files> 

926 <field name="medtbl_medication_items" formula="task.get_pdf(request)" /> 

927 </files> 

928 </instrument> 

929 </instruments> 

930</fieldmap> 

931""" # noqa: E501 

932 

933 def __init__(self, *args, **kwargs) -> None: 

934 super().__init__(*args, **kwargs) 

935 

936 def setUp(self) -> None: 

937 super().setUp() 

938 

939 self.mojo_task = KhandakerMojoMedicationTherapyFactory( 

940 patient=self.patient 

941 ) 

942 

943 self.bmi_task = BmiFactory(patient=self.patient) 

944 

945 def test_instance_ids_on_different_tasks_in_same_record(self) -> None: 

946 exporter = MockRedcapTaskExporter() 

947 project = exporter.get_project() 

948 project.export_records.return_value = DataFrame({"patient_id": []}) 

949 project.import_records.return_value = ["123,0"] 

950 project.export_project_info.return_value = { 

951 "record_autonumbering_enabled": 1 

952 } 

953 

954 exported_task_mojo = ExportedTask( 

955 task=self.mojo_task, recipient=self.recipient 

956 ) 

957 exported_task_redcap_mojo = ExportedTaskRedcap(exported_task_mojo) 

958 exporter.export_task(self.req, exported_task_redcap_mojo) 

959 self.assertEqual(exported_task_redcap_mojo.redcap_record_id, "123") 

960 args, kwargs = project.import_file.call_args 

961 

962 self.assertEqual(kwargs["repeat_instance"], 1) 

963 

964 project.export_records.return_value = DataFrame( 

965 { 

966 "record_id": ["123"], 

967 "patient_id": [self.patient_idnum.idnum_value], 

968 "redcap_repeat_instrument": [ 

969 "khandaker_mojo_medicationtherapy" 

970 ], 

971 "redcap_repeat_instance": [1], 

972 } 

973 ) 

974 exported_task_bmi = ExportedTask( 

975 task=self.bmi_task, recipient=self.recipient 

976 ) 

977 exported_task_redcap_bmi = ExportedTaskRedcap(exported_task_bmi) 

978 

979 exporter.export_task(self.req, exported_task_redcap_bmi) 

980 

981 # Import of second task, but is first instance 

982 # (third call to import_records) 

983 args, kwargs = project.import_records.call_args_list[2] 

984 

985 rows = args[0] 

986 record = rows[0] 

987 

988 self.assertEqual(record["redcap_repeat_instance"], 1) 

989 

990 def test_imported_into_different_events(self) -> None: 

991 exporter = MockRedcapTaskExporter() 

992 project = exporter.get_project() 

993 

994 project.is_longitudinal = mock.Mock(return_value=True) 

995 project.export_records.return_value = DataFrame({"patient_id": []}) 

996 project.import_records.return_value = ["123,0"] 

997 project.export_project_info.return_value = { 

998 "record_autonumbering_enabled": 1 

999 } 

1000 

1001 exported_task_mojo = ExportedTask( 

1002 task=self.mojo_task, recipient=self.recipient 

1003 ) 

1004 exported_task_redcap_mojo = ExportedTaskRedcap(exported_task_mojo) 

1005 

1006 exporter.export_task(self.req, exported_task_redcap_mojo) 

1007 

1008 args, kwargs = project.import_records.call_args_list[0] 

1009 rows = args[0] 

1010 record = rows[0] 

1011 

1012 self.assertEqual(record["redcap_event_name"], "mojo_event") 

1013 args, kwargs = project.import_file.call_args 

1014 

1015 self.assertEqual(kwargs["event"], "mojo_event") 

1016 

1017 exported_task_bmi = ExportedTask( 

1018 task=self.bmi_task, recipient=self.recipient 

1019 ) 

1020 exported_task_redcap_bmi = ExportedTaskRedcap(exported_task_bmi) 

1021 

1022 exporter.export_task(self.req, exported_task_redcap_bmi) 

1023 

1024 # Import of second task (third call to import_records) 

1025 args, kwargs = project.import_records.call_args_list[2] 

1026 rows = args[0] 

1027 record = rows[0] 

1028 self.assertEqual(record["redcap_event_name"], "bmi_event") 

1029 

1030 

1031class BadConfigurationRedcapTests(RedcapExportTestCase): 

1032 def __init__(self, *args, **kwargs) -> None: 

1033 super().__init__(*args, **kwargs) 

1034 

1035 def setUp(self) -> None: 

1036 super().setUp() 

1037 

1038 self.task = BmiFactory(patient=self.patient) 

1039 

1040 

1041class MissingInstrumentRedcapTests(BadConfigurationRedcapTests): 

1042 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

1043<fieldmap> 

1044 <patient instrument="patient_record" redcap_field="patient_id" /> 

1045 <record instrument="patient_record" redcap_field="record_id" /> 

1046 <instruments> 

1047 <instrument task="phq9" name="patient_health_questionnaire_9"> 

1048 <fields> 

1049 </fields> 

1050 </instrument> 

1051 </instruments> 

1052</fieldmap>""" 

1053 

1054 def test_raises_when_instrument_missing_from_fieldmap(self) -> None: 

1055 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

1056 exported_task_redcap = ExportedTaskRedcap(exported_task) 

1057 

1058 exporter = MockRedcapTaskExporter() 

1059 project = exporter.get_project() 

1060 project.export_records.return_value = DataFrame({"patient_id": []}) 

1061 project.import_records.return_value = ["123,0"] 

1062 

1063 with self.assertRaises(RedcapExportException) as cm: 

1064 exporter.export_task(self.req, exported_task_redcap) 

1065 

1066 message = str(cm.exception) 

1067 self.assertIn( 

1068 "Instrument for task 'bmi' is missing from the fieldmap", message 

1069 ) 

1070 

1071 

1072class IncorrectRecordIdRedcapTests(BadConfigurationRedcapTests): 

1073 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

1074<fieldmap> 

1075 <patient instrument="patient_record" redcap_field="patient_id" /> 

1076 <record instrument="patient_record" redcap_field="my_record_id" /> 

1077 <instruments> 

1078 <instrument task="bmi" name="bmi"> 

1079 <fields> 

1080 </fields> 

1081 </instrument> 

1082 </instruments> 

1083</fieldmap>""" 

1084 

1085 def test_raises_when_record_id_is_incorrect(self) -> None: 

1086 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

1087 exported_task_redcap = ExportedTaskRedcap(exported_task) 

1088 

1089 exporter = MockRedcapTaskExporter() 

1090 project = exporter.get_project() 

1091 project.export_records.return_value = DataFrame( 

1092 { 

1093 "record_id": ["123"], 

1094 "patient_id": [self.patient_idnum.idnum_value], 

1095 "redcap_repeat_instrument": ["bmi"], 

1096 "redcap_repeat_instance": [1], 

1097 } 

1098 ) 

1099 project.import_records.return_value = ["123,0"] 

1100 project.export_project_info.return_value = { 

1101 "record_autonumbering_enabled": 1 

1102 } 

1103 

1104 with self.assertRaises(RedcapExportException) as cm: 

1105 exporter.export_task(self.req, exported_task_redcap) 

1106 

1107 message = str(cm.exception) 

1108 self.assertIn("Field 'my_record_id' does not exist in REDCap", message) 

1109 

1110 

1111class IncorrectPatientIdRedcapTests(BadConfigurationRedcapTests): 

1112 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

1113<fieldmap> 

1114 <patient instrument="patient_record" redcap_field="my_patient_id" /> 

1115 <record instrument="patient_record" redcap_field="record_id" /> 

1116 <instruments> 

1117 <instrument task="bmi" name="bmi"> 

1118 <fields> 

1119 </fields> 

1120 </instrument> 

1121 </instruments> 

1122</fieldmap>""" 

1123 

1124 def test_raises_when_patient_id_is_incorrect(self) -> None: 

1125 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

1126 exported_task_redcap = ExportedTaskRedcap(exported_task) 

1127 

1128 exporter = MockRedcapTaskExporter() 

1129 project = exporter.get_project() 

1130 project.export_records.return_value = DataFrame( 

1131 { 

1132 "record_id": ["123"], 

1133 "patient_id": [self.patient_idnum.idnum_value], 

1134 "redcap_repeat_instrument": ["bmi"], 

1135 "redcap_repeat_instance": [1], 

1136 } 

1137 ) 

1138 project.import_records.return_value = ["123,0"] 

1139 project.export_project_info.return_value = { 

1140 "record_autonumbering_enabled": 1 

1141 } 

1142 

1143 with self.assertRaises(RedcapExportException) as cm: 

1144 exporter.export_task(self.req, exported_task_redcap) 

1145 

1146 message = str(cm.exception) 

1147 self.assertIn( 

1148 "Field 'my_patient_id' does not exist in REDCap", message 

1149 ) 

1150 

1151 

1152class MissingPatientInstrumentRedcapTests(BadConfigurationRedcapTests): 

1153 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

1154<fieldmap> 

1155 <patient instrument="patient_record" redcap_field="my_patient_id" /> 

1156 <record instrument="patient_record" redcap_field="record_id" /> 

1157 <instruments> 

1158 <instrument task="bmi" name="bmi"> 

1159 <fields> 

1160 </fields> 

1161 </instrument> 

1162 </instruments> 

1163</fieldmap>""" 

1164 

1165 def test_raises_when_instrument_is_missing(self) -> None: 

1166 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

1167 exported_task_redcap = ExportedTaskRedcap(exported_task) 

1168 

1169 exporter = MockRedcapTaskExporter() 

1170 project = exporter.get_project() 

1171 project.export_records.side_effect = redcap.RedcapError( 

1172 "Something went wrong" 

1173 ) 

1174 

1175 with self.assertRaises(RedcapExportException) as cm: 

1176 exporter.export_task(self.req, exported_task_redcap) 

1177 

1178 message = str(cm.exception) 

1179 self.assertIn("Something went wrong", message) 

1180 

1181 

1182class MissingEventRedcapTests(BadConfigurationRedcapTests): 

1183 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

1184<fieldmap> 

1185 <patient instrument="patient_record" redcap_field="my_patient_id" /> 

1186 <record instrument="patient_record" redcap_field="record_id" /> 

1187 <instruments> 

1188 <instrument task="bmi" name="bmi"> 

1189 <fields> 

1190 </fields> 

1191 </instrument> 

1192 </instruments> 

1193</fieldmap>""" 

1194 

1195 def test_raises_for_longitudinal_project(self) -> None: 

1196 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

1197 exported_task_redcap = ExportedTaskRedcap(exported_task) 

1198 

1199 exporter = MockRedcapTaskExporter() 

1200 project = exporter.get_project() 

1201 

1202 project.is_longitudinal = mock.Mock(return_value=True) 

1203 

1204 with self.assertRaises(RedcapExportException) as cm: 

1205 exporter.export_task(self.req, exported_task_redcap) 

1206 

1207 message = str(cm.exception) 

1208 self.assertEqual(MISSING_EVENT_TAG_OR_ATTRIBUTE, message) 

1209 

1210 

1211class MissingInstrumentEventRedcapTests(BadConfigurationRedcapTests): 

1212 fieldmap = """<?xml version="1.0" encoding="UTF-8"?> 

1213<fieldmap> 

1214 <patient instrument="patient_record" redcap_field="my_patient_id" /> 

1215 <record instrument="patient_record" redcap_field="record_id" /> 

1216 <instruments> 

1217 <instrument task="bmi" name="bmi"> 

1218 <fields> 

1219 </fields> 

1220 </instrument> 

1221 <instrument task="phq9" name="phq9" event="phq9_event"> 

1222 <fields> 

1223 </fields> 

1224 </instrument> 

1225 </instruments> 

1226</fieldmap>""" 

1227 

1228 def test_raises_when_instrument_missing_event(self) -> None: 

1229 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

1230 exported_task_redcap = ExportedTaskRedcap(exported_task) 

1231 

1232 exporter = MockRedcapTaskExporter() 

1233 project = exporter.get_project() 

1234 

1235 project.is_longitudinal = mock.Mock(return_value=True) 

1236 

1237 with self.assertRaises(RedcapExportException) as cm: 

1238 exporter.export_task(self.req, exported_task_redcap) 

1239 

1240 message = str(cm.exception) 

1241 self.assertEqual(MISSING_EVENT_TAG_OR_ATTRIBUTE, message) 

1242 

1243 

1244class AnonymousTaskRedcapTests(RedcapExportTestCase): 

1245 def setUp(self) -> None: 

1246 super().setUp() 

1247 

1248 self.task = APEQCPFTPerinatalFactory() 

1249 

1250 def test_raises_when_task_is_anonymous(self) -> None: 

1251 exported_task = ExportedTask(task=self.task, recipient=self.recipient) 

1252 exported_task_redcap = ExportedTaskRedcap(exported_task) 

1253 

1254 exporter = MockRedcapTaskExporter() 

1255 

1256 with self.assertRaises(RedcapExportException) as cm: 

1257 exporter.export_task(self.req, exported_task_redcap) 

1258 

1259 message = str(cm.exception) 

1260 self.assertIn("Skipping anonymous task 'apeq_cpft_perinatal'", message)