Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/tests/cc_forms_tests.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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. 

16 

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. 

21 

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/>. 

24 

25=============================================================================== 

26 

27""" 

28 

29import json 

30from pprint import pformat 

31from typing import Any, Dict 

32from unittest import mock, TestCase 

33 

34# noinspection PyProtectedMember 

35from colander import Invalid, null, Schema 

36from pendulum import Duration 

37 

38from camcops_server.cc_modules.cc_baseconstants import TEMPLATE_DIR 

39from camcops_server.cc_modules.cc_forms import ( 

40 DurationType, 

41 DurationWidget, 

42 GroupIpUseWidget, 

43 IpUseType, 

44 JsonType, 

45 JsonWidget, 

46 LoginSchema, 

47 TaskScheduleItemSchema, 

48 TaskScheduleNode, 

49 TaskScheduleSchema, 

50) 

51from camcops_server.cc_modules.cc_ipuse import IpContexts 

52from camcops_server.cc_modules.cc_pyramid import ViewParam 

53from camcops_server.cc_modules.cc_taskschedule import TaskSchedule 

54from camcops_server.cc_modules.cc_unittest import ( 

55 DemoDatabaseTestCase, 

56 DemoRequestTestCase, 

57) 

58 

59 

60# ============================================================================= 

61# Unit tests 

62# ============================================================================= 

63 

64class SchemaTestCase(DemoRequestTestCase): 

65 """ 

66 Unit tests. 

67 """ 

68 def serialize_deserialize(self, 

69 schema: Schema, 

70 appstruct: Dict[str, Any]) -> None: 

71 cstruct = schema.serialize(appstruct) 

72 final = schema.deserialize(cstruct) 

73 mismatch = False 

74 for k, v in appstruct.items(): 

75 if final[k] != v: 

76 mismatch = True 

77 break 

78 self.assertFalse(mismatch, msg=( 

79 "Elements of final don't match corresponding elements of starting " 

80 "appstruct:\n" 

81 f"final = {pformat(final)}\n" 

82 f"start = {pformat(appstruct)}" 

83 )) 

84 

85 

86class LoginSchemaTests(SchemaTestCase): 

87 def test_serialize_deserialize(self) -> None: 

88 appstruct = { 

89 ViewParam.USERNAME: "testuser", 

90 ViewParam.PASSWORD: "testpw", 

91 } 

92 schema = LoginSchema().bind(request=self.req) 

93 

94 self.serialize_deserialize(schema, appstruct) 

95 

96 

97class TaskScheduleSchemaTests(DemoDatabaseTestCase): 

98 def test_invalid_for_bad_template_placeholder(self) -> None: 

99 schema = TaskScheduleSchema().bind(request=self.req) 

100 appstruct = { 

101 ViewParam.NAME: "test", 

102 ViewParam.GROUP_ID: str(self.group.id), 

103 ViewParam.EMAIL_SUBJECT: "Subject", 

104 ViewParam.EMAIL_TEMPLATE: "{bad_key}", 

105 } 

106 

107 cstruct = schema.serialize(appstruct) 

108 with self.assertRaises(Invalid) as cm: 

109 schema.deserialize(cstruct) 

110 

111 self.assertIn("'bad_key' is not a valid placeholder", 

112 cm.exception.children[0].messages()[0]) 

113 

114 def test_invalid_for_mismatched_braces(self) -> None: 

115 schema = TaskScheduleSchema().bind(request=self.req) 

116 appstruct = { 

117 ViewParam.NAME: "test", 

118 ViewParam.GROUP_ID: str(self.group.id), 

119 ViewParam.EMAIL_SUBJECT: "Subject", 

120 ViewParam.EMAIL_TEMPLATE: "{server_url", 

121 } 

122 

123 cstruct = schema.serialize(appstruct) 

124 with self.assertRaises(Invalid) as cm: 

125 schema.deserialize(cstruct) 

126 

127 self.assertIn("Invalid email template", 

128 cm.exception.children[0].messages()[0]) 

129 

130 

131class TaskScheduleItemSchemaTests(SchemaTestCase): 

132 def test_serialize_deserialize(self) -> None: 

133 appstruct = { 

134 ViewParam.SCHEDULE_ID: 1, 

135 ViewParam.TABLE_NAME: "bmi", 

136 ViewParam.CLINICIAN_CONFIRMATION: False, 

137 ViewParam.DUE_FROM: Duration(days=90), 

138 ViewParam.DUE_WITHIN: Duration(days=100) 

139 } 

140 schema = TaskScheduleItemSchema().bind(request=self.req) 

141 self.serialize_deserialize(schema, appstruct) 

142 

143 def test_invalid_for_clinician_task_with_no_confirmation(self) -> None: 

144 schema = TaskScheduleItemSchema().bind(request=self.req) 

145 appstruct = { 

146 ViewParam.SCHEDULE_ID: 1, 

147 ViewParam.TABLE_NAME: "elixhauserci", 

148 ViewParam.CLINICIAN_CONFIRMATION: False, 

149 ViewParam.DUE_FROM: Duration(days=90), 

150 ViewParam.DUE_WITHIN: Duration(days=100) 

151 } 

152 

153 cstruct = schema.serialize(appstruct) 

154 with self.assertRaises(Invalid) as cm: 

155 schema.deserialize(cstruct) 

156 

157 self.assertIn("you must tick 'Allow clinician tasks'", 

158 cm.exception.messages()[0]) 

159 

160 def test_valid_for_clinician_task_with_confirmation(self) -> None: 

161 schema = TaskScheduleItemSchema().bind(request=mock.Mock()) 

162 appstruct = { 

163 ViewParam.SCHEDULE_ID: 1, 

164 ViewParam.TABLE_NAME: "elixhauserci", 

165 ViewParam.CLINICIAN_CONFIRMATION: True, 

166 ViewParam.DUE_FROM: Duration(days=90), 

167 ViewParam.DUE_WITHIN: Duration(days=100) 

168 } 

169 

170 try: 

171 schema.serialize(appstruct) 

172 except Invalid: 

173 self.fail("Validation failed unexpectedly") 

174 

175 def test_invalid_for_zero_due_within(self) -> None: 

176 schema = TaskScheduleItemSchema().bind(request=self.req) 

177 appstruct = { 

178 ViewParam.SCHEDULE_ID: 1, 

179 ViewParam.TABLE_NAME: "phq9", 

180 ViewParam.CLINICIAN_CONFIRMATION: False, 

181 ViewParam.DUE_FROM: Duration(days=90), 

182 ViewParam.DUE_WITHIN: Duration(days=0) 

183 } 

184 

185 cstruct = schema.serialize(appstruct) 

186 with self.assertRaises(Invalid) as cm: 

187 schema.deserialize(cstruct) 

188 

189 self.assertIn("must be more than zero days", 

190 cm.exception.messages()[0]) 

191 

192 def test_invalid_for_negative_due_within(self) -> None: 

193 schema = TaskScheduleItemSchema().bind(request=self.req) 

194 appstruct = { 

195 ViewParam.SCHEDULE_ID: 1, 

196 ViewParam.TABLE_NAME: "phq9", 

197 ViewParam.CLINICIAN_CONFIRMATION: False, 

198 ViewParam.DUE_FROM: Duration(days=90), 

199 ViewParam.DUE_WITHIN: Duration(days=-1) 

200 } 

201 

202 cstruct = schema.serialize(appstruct) 

203 with self.assertRaises(Invalid) as cm: 

204 schema.deserialize(cstruct) 

205 

206 self.assertIn("must be more than zero days", 

207 cm.exception.messages()[0]) 

208 

209 def test_invalid_for_negative_due_from(self) -> None: 

210 schema = TaskScheduleItemSchema().bind(request=self.req) 

211 appstruct = { 

212 ViewParam.SCHEDULE_ID: 1, 

213 ViewParam.TABLE_NAME: "phq9", 

214 ViewParam.CLINICIAN_CONFIRMATION: False, 

215 ViewParam.DUE_FROM: Duration(days=-1), 

216 ViewParam.DUE_WITHIN: Duration(days=10) 

217 } 

218 

219 cstruct = schema.serialize(appstruct) 

220 with self.assertRaises(Invalid) as cm: 

221 schema.deserialize(cstruct) 

222 

223 self.assertIn("must be zero or more days", 

224 cm.exception.messages()[0]) 

225 

226 

227class TaskScheduleItemSchemaIpTests(DemoDatabaseTestCase): 

228 def setUp(self) -> None: 

229 super().setUp() 

230 

231 self.schedule = TaskSchedule() 

232 self.schedule.group_id = self.group.id 

233 self.dbsession.add(self.schedule) 

234 self.dbsession.commit() 

235 

236 def create_tasks(self) -> None: 

237 # Speed things up a bit 

238 pass 

239 

240 def test_invalid_for_commercial_mismatch(self) -> None: 

241 self.group.ip_use.commercial = True 

242 self.dbsession.add(self.group) 

243 self.dbsession.commit() 

244 

245 schema = TaskScheduleItemSchema().bind(request=self.req) 

246 appstruct = { 

247 ViewParam.SCHEDULE_ID: self.schedule.id, 

248 ViewParam.TABLE_NAME: "mfi20", 

249 ViewParam.CLINICIAN_CONFIRMATION: False, 

250 ViewParam.DUE_FROM: Duration(days=0), 

251 ViewParam.DUE_WITHIN: Duration(days=10) 

252 } 

253 

254 cstruct = schema.serialize(appstruct) 

255 with self.assertRaises(Invalid) as cm: 

256 schema.deserialize(cstruct) 

257 

258 self.assertIn("prohibits commercial", 

259 cm.exception.messages()[0]) 

260 

261 def test_invalid_for_clinical_mismatch(self) -> None: 

262 self.group.ip_use.clinical = True 

263 self.dbsession.add(self.group) 

264 self.dbsession.commit() 

265 

266 schema = TaskScheduleItemSchema().bind(request=self.req) 

267 appstruct = { 

268 ViewParam.SCHEDULE_ID: self.schedule.id, 

269 ViewParam.TABLE_NAME: "mfi20", 

270 ViewParam.CLINICIAN_CONFIRMATION: False, 

271 ViewParam.DUE_FROM: Duration(days=0), 

272 ViewParam.DUE_WITHIN: Duration(days=10), 

273 } 

274 

275 cstruct = schema.serialize(appstruct) 

276 with self.assertRaises(Invalid) as cm: 

277 schema.deserialize(cstruct) 

278 

279 self.assertIn("prohibits clinical", 

280 cm.exception.messages()[0]) 

281 

282 def test_invalid_for_educational_mismatch(self) -> None: 

283 self.group.ip_use.educational = True 

284 self.dbsession.add(self.group) 

285 self.dbsession.commit() 

286 

287 schema = TaskScheduleItemSchema().bind(request=self.req) 

288 appstruct = { 

289 ViewParam.SCHEDULE_ID: self.schedule.id, 

290 ViewParam.TABLE_NAME: "mfi20", 

291 ViewParam.CLINICIAN_CONFIRMATION: True, 

292 ViewParam.DUE_FROM: Duration(days=0), 

293 ViewParam.DUE_WITHIN: Duration(days=10), 

294 } 

295 

296 cstruct = schema.serialize(appstruct) 

297 

298 # No real world example prohibits educational use 

299 mock_task_class = mock.Mock(prohibits_educational=True) 

300 with mock.patch.object(schema, "_get_task_class", 

301 return_value=mock_task_class): 

302 with self.assertRaises(Invalid) as cm: 

303 schema.deserialize(cstruct) 

304 

305 self.assertIn("prohibits educational", 

306 cm.exception.messages()[0]) 

307 

308 def test_invalid_for_research_mismatch(self) -> None: 

309 self.group.ip_use.research = True 

310 self.dbsession.add(self.group) 

311 self.dbsession.commit() 

312 

313 schema = TaskScheduleItemSchema().bind(request=self.req) 

314 appstruct = { 

315 ViewParam.SCHEDULE_ID: self.schedule.id, 

316 ViewParam.TABLE_NAME: "moca", 

317 ViewParam.CLINICIAN_CONFIRMATION: True, 

318 ViewParam.DUE_FROM: Duration(days=0), 

319 ViewParam.DUE_WITHIN: Duration(days=10), 

320 } 

321 

322 cstruct = schema.serialize(appstruct) 

323 with self.assertRaises(Invalid) as cm: 

324 schema.deserialize(cstruct) 

325 

326 self.assertIn("prohibits research", 

327 cm.exception.messages()[0]) 

328 

329 def test_invalid_for_missing_ip_use(self) -> None: 

330 self.group.ip_use = None 

331 self.dbsession.add(self.group) 

332 self.dbsession.commit() 

333 

334 schema = TaskScheduleItemSchema().bind(request=self.req) 

335 appstruct = { 

336 ViewParam.SCHEDULE_ID: self.schedule.id, 

337 ViewParam.TABLE_NAME: "moca", 

338 ViewParam.CLINICIAN_CONFIRMATION: True, 

339 ViewParam.DUE_FROM: Duration(days=0), 

340 ViewParam.DUE_WITHIN: Duration(days=10), 

341 } 

342 

343 cstruct = schema.serialize(appstruct) 

344 with self.assertRaises(Invalid) as cm: 

345 schema.deserialize(cstruct) 

346 

347 self.assertIn( 

348 f"The group '{self.group.name}' has no intellectual property " 

349 f"settings", 

350 cm.exception.messages()[0] 

351 ) 

352 

353 

354class DurationWidgetTests(TestCase): 

355 def setUp(self) -> None: 

356 super().setUp() 

357 self.request = mock.Mock(gettext=lambda t: t) 

358 

359 def test_serialize_renders_template_with_values(self) -> None: 

360 widget = DurationWidget(self.request) 

361 

362 field = mock.Mock() 

363 field.renderer = mock.Mock() 

364 

365 cstruct = { 

366 "months": 1, 

367 "weeks": 2, 

368 "days": 3, 

369 } 

370 

371 widget.serialize(field, cstruct, readonly=False) 

372 

373 args, kwargs = field.renderer.call_args 

374 

375 self.assertEqual(args[0], f"{TEMPLATE_DIR}/deform/duration.pt") 

376 self.assertFalse(kwargs["readonly"]) 

377 

378 self.assertEqual(kwargs["months"], 1) 

379 self.assertEqual(kwargs["weeks"], 2) 

380 self.assertEqual(kwargs["days"], 3) 

381 

382 self.assertEqual(kwargs["field"], field) 

383 

384 def test_serialize_renders_readonly_template_with_values(self) -> None: 

385 widget = DurationWidget(self.request) 

386 

387 field = mock.Mock() 

388 field.renderer = mock.Mock() 

389 

390 cstruct = { 

391 "months": 1, 

392 "weeks": 2, 

393 "days": 3, 

394 } 

395 

396 widget.serialize(field, cstruct, readonly=True) 

397 

398 args, kwargs = field.renderer.call_args 

399 

400 self.assertEqual(args[0], f"{TEMPLATE_DIR}/deform/readonly/duration.pt") 

401 self.assertTrue(kwargs["readonly"]) 

402 

403 def test_serialize_renders_readonly_template_if_widget_is_readonly( 

404 self) -> None: 

405 widget = DurationWidget(self.request, readonly=True) 

406 

407 field = mock.Mock() 

408 field.renderer = mock.Mock() 

409 

410 cstruct = { 

411 "months": 1, 

412 "weeks": 2, 

413 "days": 3, 

414 } 

415 

416 widget.serialize(field, cstruct) 

417 

418 args, kwargs = field.renderer.call_args 

419 

420 self.assertEqual(args[0], f"{TEMPLATE_DIR}/deform/readonly/duration.pt") 

421 

422 def test_serialize_with_null_defaults_to_blank_values(self) -> None: 

423 widget = DurationWidget(self.request) 

424 

425 field = mock.Mock() 

426 field.renderer = mock.Mock() 

427 

428 widget.serialize(field, null) 

429 

430 args, kwargs = field.renderer.call_args 

431 

432 self.assertEqual(kwargs["months"], "") 

433 self.assertEqual(kwargs["weeks"], "") 

434 self.assertEqual(kwargs["days"], "") 

435 

436 def test_serialize_none_defaults_to_blank_values(self) -> None: 

437 widget = DurationWidget(self.request) 

438 

439 field = mock.Mock() 

440 field.renderer = mock.Mock() 

441 

442 widget.serialize(field, None) 

443 

444 args, kwargs = field.renderer.call_args 

445 

446 self.assertEqual(kwargs["months"], "") 

447 self.assertEqual(kwargs["weeks"], "") 

448 self.assertEqual(kwargs["days"], "") 

449 

450 def test_deserialize_returns_valid_values(self) -> None: 

451 widget = DurationWidget(self.request) 

452 

453 pstruct = { 

454 "days": 1, 

455 "weeks": 2, 

456 "months": 3, 

457 } 

458 

459 # noinspection PyTypeChecker 

460 cstruct = widget.deserialize(None, pstruct) 

461 

462 self.assertEqual(cstruct["days"], 1) 

463 self.assertEqual(cstruct["weeks"], 2) 

464 self.assertEqual(cstruct["months"], 3) 

465 

466 def test_deserialize_defaults_to_zero_days(self) -> None: 

467 widget = DurationWidget(self.request) 

468 

469 # noinspection PyTypeChecker 

470 cstruct = widget.deserialize(None, {}) 

471 

472 self.assertEqual(cstruct["days"], 0) 

473 

474 def test_deserialize_fails_validation(self) -> None: 

475 widget = DurationWidget(self.request) 

476 

477 pstruct = { 

478 "days": "abc", 

479 "weeks": "def", 

480 "months": "ghi", 

481 } 

482 

483 with self.assertRaises(Invalid) as cm: 

484 # noinspection PyTypeChecker 

485 widget.deserialize(None, pstruct) 

486 

487 self.assertIn("Please enter a valid number of days or leave blank", 

488 cm.exception.messages()) 

489 self.assertIn("Please enter a valid number of weeks or leave blank", 

490 cm.exception.messages()) 

491 self.assertIn("Please enter a valid number of months or leave blank", 

492 cm.exception.messages()) 

493 self.assertEqual(cm.exception.value, pstruct) 

494 

495 

496class DurationTypeTests(TestCase): 

497 def test_deserialize_valid_duration(self) -> None: 

498 cstruct = {"days": 45} 

499 

500 duration_type = DurationType() 

501 duration = duration_type.deserialize(None, cstruct) 

502 assert duration is not None # for type checker 

503 

504 self.assertEqual(duration.days, 45) 

505 

506 def test_deserialize_none_returns_null(self) -> None: 

507 duration_type = DurationType() 

508 duration = duration_type.deserialize(None, None) 

509 self.assertIsNone(duration) 

510 

511 def test_deserialize_ignores_invalid_days(self) -> None: 

512 duration_type = DurationType() 

513 cstruct = {"days": "abc", "months": 1, "weeks": 1} 

514 duration = duration_type.deserialize(None, cstruct) 

515 assert duration is not None # for type checker 

516 

517 self.assertEqual(duration.days, 37) 

518 

519 def test_deserialize_ignores_invalid_months(self) -> None: 

520 duration_type = DurationType() 

521 cstruct = {"days": 1, "months": "abc", "weeks": 1} 

522 duration = duration_type.deserialize(None, cstruct) 

523 assert duration is not None # for type checker 

524 

525 self.assertEqual(duration.days, 8) 

526 

527 def test_deserialize_ignores_invalid_weeks(self) -> None: 

528 duration_type = DurationType() 

529 cstruct = {"days": 1, "months": 1, "weeks": "abc"} 

530 duration = duration_type.deserialize(None, cstruct) 

531 assert duration is not None # for type checker 

532 

533 self.assertEqual(duration.days, 31) 

534 

535 def test_serialize_valid_duration(self) -> None: 

536 duration = Duration(days=47) 

537 

538 duration_type = DurationType() 

539 cstruct = duration_type.serialize(None, duration) 

540 

541 # For type checker 

542 assert cstruct not in (null,) 

543 cstruct: Dict[Any, Any] 

544 

545 self.assertEqual(cstruct["days"], 3) 

546 self.assertEqual(cstruct["months"], 1) 

547 self.assertEqual(cstruct["weeks"], 2) 

548 

549 def test_serialize_null_returns_null(self) -> None: 

550 duration_type = DurationType() 

551 cstruct = duration_type.serialize(None, null) 

552 self.assertIs(cstruct, null) 

553 

554 

555class JsonWidgetTests(TestCase): 

556 def setUp(self) -> None: 

557 super().setUp() 

558 self.request = mock.Mock(gettext=lambda t: t) 

559 

560 def test_serialize_renders_template_with_values(self) -> None: 

561 widget = JsonWidget(self.request) 

562 

563 field = mock.Mock() 

564 field.renderer = mock.Mock() 

565 

566 cstruct = json.dumps({"a": "1", "b": "2", "c": "3"}) 

567 

568 widget.serialize(field, cstruct, readonly=False) 

569 

570 args, kwargs = field.renderer.call_args 

571 

572 self.assertEqual(args[0], f"{TEMPLATE_DIR}/deform/json.pt") 

573 self.assertFalse(kwargs["readonly"]) 

574 

575 self.assertEqual(kwargs["cstruct"], cstruct) 

576 self.assertEqual(kwargs["field"], field) 

577 

578 def test_serialize_renders_readonly_template_with_values(self) -> None: 

579 widget = JsonWidget(self.request) 

580 

581 field = mock.Mock() 

582 field.renderer = mock.Mock() 

583 

584 cstruct = json.dumps({"a": "1", "b": "2", "c": "3"}) 

585 

586 widget.serialize(field, cstruct, readonly=True) 

587 

588 args, kwargs = field.renderer.call_args 

589 

590 self.assertEqual(args[0], f"{TEMPLATE_DIR}/deform/readonly/json.pt") 

591 

592 self.assertEqual(kwargs["cstruct"], cstruct) 

593 self.assertEqual(kwargs["field"], field) 

594 self.assertTrue(kwargs["readonly"]) 

595 

596 def test_serialize_renders_readonly_template_if_widget_is_readonly( 

597 self) -> None: 

598 widget = JsonWidget(self.request, readonly=True) 

599 

600 field = mock.Mock() 

601 field.renderer = mock.Mock() 

602 

603 json_text = json.dumps({"a": "1", "b": "2", "c": "3"}) 

604 widget.serialize(field, json_text) 

605 

606 args, kwargs = field.renderer.call_args 

607 

608 self.assertEqual(args[0], f"{TEMPLATE_DIR}/deform/readonly/json.pt") 

609 

610 def test_serialize_with_null_defaults_to_empty_string(self) -> None: 

611 widget = JsonWidget(self.request) 

612 

613 field = mock.Mock() 

614 field.renderer = mock.Mock() 

615 

616 widget.serialize(field, null) 

617 

618 args, kwargs = field.renderer.call_args 

619 

620 self.assertEqual(kwargs["cstruct"], "") 

621 

622 def test_deserialize_passes_json(self) -> None: 

623 widget = JsonWidget(self.request) 

624 

625 pstruct = json.dumps({"a": "1", "b": "2", "c": "3"}) 

626 

627 # noinspection PyTypeChecker 

628 cstruct = widget.deserialize(None, pstruct) 

629 

630 self.assertEqual(cstruct, pstruct) 

631 

632 def test_deserialize_defaults_to_empty_json_string(self) -> None: 

633 widget = JsonWidget(self.request) 

634 

635 # noinspection PyTypeChecker 

636 cstruct = widget.deserialize(None, "{}") 

637 

638 self.assertEqual(cstruct, "{}") 

639 

640 def test_deserialize_invalid_json_fails_validation(self) -> None: 

641 widget = JsonWidget(self.request) 

642 

643 pstruct = "{" 

644 

645 with self.assertRaises(Invalid) as cm: 

646 # noinspection PyTypeChecker 

647 widget.deserialize(None, pstruct) 

648 

649 self.assertIn( 

650 "Please enter valid JSON", 

651 cm.exception.messages()[0] 

652 ) 

653 

654 self.assertEqual(cm.exception.value, "{") 

655 

656 

657class JsonTypeTests(TestCase): 

658 def test_deserialize_valid_json(self) -> None: 

659 original = {"one": 1, "two": 2, "three": 3} 

660 

661 json_type = JsonType() 

662 json_value = json_type.deserialize(None, json.dumps(original)) 

663 self.assertEqual(json_value, original) 

664 

665 def test_deserialize_null_returns_none(self) -> None: 

666 json_type = JsonType() 

667 json_value = json_type.deserialize(None, null) 

668 self.assertIsNone(json_value) 

669 

670 def test_deserialize_none_returns_null(self) -> None: 

671 json_type = JsonType() 

672 json_value = json_type.deserialize(None, None) 

673 self.assertIsNone(json_value) 

674 

675 def test_deserialize_invalid_json_returns_none(self) -> None: 

676 json_type = JsonType() 

677 json_value = json_type.deserialize(None, "{") 

678 self.assertIsNone(json_value) 

679 

680 def test_serialize_valid_appstruct(self) -> None: 

681 original = {"one": 1, "two": 2, "three": 3} 

682 

683 json_type = JsonType() 

684 json_string = json_type.serialize(None, original) 

685 self.assertEqual(json_string, json.dumps(original)) 

686 

687 def test_serialize_null_returns_null(self) -> None: 

688 json_type = JsonType() 

689 json_string = json_type.serialize(None, null) 

690 self.assertIs(json_string, null) 

691 

692 

693class TaskScheduleNodeTests(TestCase): 

694 

695 def test_deserialize_not_a_json_object_fails_validation(self) -> None: 

696 node = TaskScheduleNode() 

697 with self.assertRaises(Invalid) as cm: 

698 node.deserialize({}) 

699 

700 self.assertIn( 

701 "Please enter a valid JSON object", 

702 cm.exception.messages()[0] 

703 ) 

704 

705 self.assertEqual(cm.exception.value, "[{}]") 

706 

707 

708class GroupIpUseWidgetTests(TestCase): 

709 def setUp(self) -> None: 

710 super().setUp() 

711 self.request = mock.Mock(gettext=lambda t: t) 

712 

713 def test_serialize_renders_template_with_values(self) -> None: 

714 widget = GroupIpUseWidget(self.request) 

715 

716 field = mock.Mock() 

717 field.renderer = mock.Mock() 

718 

719 cstruct = { 

720 IpContexts.CLINICAL: False, 

721 IpContexts.COMMERCIAL: False, 

722 IpContexts.EDUCATIONAL: True, 

723 IpContexts.RESEARCH: True, 

724 } 

725 

726 widget.serialize(field, cstruct, readonly=False) 

727 

728 args, kwargs = field.renderer.call_args 

729 

730 self.assertEqual(args[0], f"{TEMPLATE_DIR}/deform/group_ip_use.pt") 

731 self.assertFalse(kwargs["readonly"]) 

732 

733 self.assertFalse(kwargs[IpContexts.CLINICAL]) 

734 self.assertFalse(kwargs[IpContexts.COMMERCIAL]) 

735 self.assertTrue(kwargs[IpContexts.EDUCATIONAL]) 

736 self.assertTrue(kwargs[IpContexts.RESEARCH]) 

737 self.assertEqual(kwargs["field"], field) 

738 

739 def test_serialize_renders_readonly_template(self) -> None: 

740 widget = GroupIpUseWidget(self.request) 

741 

742 field = mock.Mock() 

743 field.renderer = mock.Mock() 

744 

745 cstruct = { 

746 IpContexts.CLINICAL: False, 

747 IpContexts.COMMERCIAL: False, 

748 IpContexts.EDUCATIONAL: True, 

749 IpContexts.RESEARCH: True, 

750 } 

751 

752 widget.serialize(field, cstruct, readonly=True) 

753 

754 args, kwargs = field.renderer.call_args 

755 

756 self.assertEqual(args[0], 

757 f"{TEMPLATE_DIR}/deform/readonly/group_ip_use.pt") 

758 self.assertTrue(kwargs["readonly"]) 

759 

760 def test_serialize_readonly_widget_renders_readonly_template(self) -> None: 

761 widget = GroupIpUseWidget(self.request, readonly=True) 

762 

763 field = mock.Mock() 

764 field.renderer = mock.Mock() 

765 

766 cstruct = { 

767 IpContexts.CLINICAL: False, 

768 IpContexts.COMMERCIAL: False, 

769 IpContexts.EDUCATIONAL: True, 

770 IpContexts.RESEARCH: True, 

771 } 

772 

773 widget.serialize(field, cstruct) 

774 

775 args, kwargs = field.renderer.call_args 

776 

777 self.assertEqual(args[0], 

778 f"{TEMPLATE_DIR}/deform/readonly/group_ip_use.pt") 

779 

780 def test_serialize_with_null_defaults_to_false_values(self) -> None: 

781 widget = GroupIpUseWidget(self.request) 

782 

783 field = mock.Mock() 

784 field.renderer = mock.Mock() 

785 

786 widget.serialize(field, null) 

787 

788 args, kwargs = field.renderer.call_args 

789 

790 self.assertFalse(kwargs[IpContexts.CLINICAL]) 

791 self.assertFalse(kwargs[IpContexts.COMMERCIAL]) 

792 self.assertFalse(kwargs[IpContexts.EDUCATIONAL]) 

793 self.assertFalse(kwargs[IpContexts.RESEARCH]) 

794 

795 def test_serialize_with_none_defaults_to_false_values(self) -> None: 

796 widget = GroupIpUseWidget(self.request) 

797 

798 field = mock.Mock() 

799 field.renderer = mock.Mock() 

800 

801 widget.serialize(field, None) 

802 

803 args, kwargs = field.renderer.call_args 

804 

805 self.assertFalse(kwargs[IpContexts.CLINICAL]) 

806 self.assertFalse(kwargs[IpContexts.COMMERCIAL]) 

807 self.assertFalse(kwargs[IpContexts.EDUCATIONAL]) 

808 self.assertFalse(kwargs[IpContexts.RESEARCH]) 

809 

810 def test_deserialize_with_null_defaults_to_false_values(self) -> None: 

811 widget = GroupIpUseWidget(self.request) 

812 

813 field = None # Not used 

814 # noinspection PyTypeChecker 

815 cstruct = widget.deserialize(field, null) 

816 

817 self.assertFalse(cstruct[IpContexts.CLINICAL]) 

818 self.assertFalse(cstruct[IpContexts.COMMERCIAL]) 

819 self.assertFalse(cstruct[IpContexts.EDUCATIONAL]) 

820 self.assertFalse(cstruct[IpContexts.RESEARCH]) 

821 

822 def test_deserialize_converts_to_bool_values(self) -> None: 

823 widget = GroupIpUseWidget(self.request) 

824 

825 field = None # Not used 

826 

827 # It shouldn't matter what the values are set to so long as the keys 

828 # are present. In practice the values will be set to "1" 

829 pstruct = { 

830 IpContexts.EDUCATIONAL: "1", 

831 IpContexts.RESEARCH: "1", 

832 } 

833 

834 # noinspection PyTypeChecker 

835 cstruct = widget.deserialize(field, pstruct) 

836 

837 self.assertFalse(cstruct[IpContexts.CLINICAL]) 

838 self.assertFalse(cstruct[IpContexts.COMMERCIAL]) 

839 self.assertTrue(cstruct[IpContexts.EDUCATIONAL]) 

840 self.assertTrue(cstruct[IpContexts.RESEARCH]) 

841 

842 

843class IpUseTypeTests(TestCase): 

844 def test_deserialize_none_returns_none(self) -> None: 

845 ip_use_type = IpUseType() 

846 

847 node = None # not used 

848 self.assertIsNone(ip_use_type.deserialize(node, None), None) 

849 

850 def test_deserialize_null_returns_none(self) -> None: 

851 ip_use_type = IpUseType() 

852 

853 node = None # not used 

854 self.assertIsNone(ip_use_type.deserialize(node, null), None) 

855 

856 def test_deserialize_returns_ip_use_object(self) -> None: 

857 ip_use_type = IpUseType() 

858 

859 node = None # not used 

860 

861 cstruct = { 

862 IpContexts.CLINICAL: False, 

863 IpContexts.COMMERCIAL: True, 

864 IpContexts.EDUCATIONAL: False, 

865 IpContexts.RESEARCH: True, 

866 } 

867 ip_use = ip_use_type.deserialize(node, cstruct) 

868 

869 self.assertFalse(ip_use.clinical) 

870 self.assertTrue(ip_use.commercial) 

871 self.assertFalse(ip_use.educational) 

872 self.assertTrue(ip_use.research)