Coverage for cc_modules/tests/client_api_tests.py: 12%

317 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/tests/client_api_tests.py 

5 

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

7 

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

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

10 

11 This file is part of CamCOPS. 

12 

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

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

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

16 (at your option) any later version. 

17 

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

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

20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21 GNU General Public License for more details. 

22 

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

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

25 

26=============================================================================== 

27 

28""" 

29 

30import json 

31 

32import string 

33from typing import Dict 

34 

35from cardinal_pythonlib.classes import class_attribute_names 

36from cardinal_pythonlib.convert import ( 

37 base64_64format_encode, 

38 hex_xformat_encode, 

39) 

40from cardinal_pythonlib.nhs import generate_random_nhs_number 

41from cardinal_pythonlib.sql.literals import sql_quote_string 

42from cardinal_pythonlib.text import escape_newlines, unescape_newlines 

43from pyramid.response import Response 

44 

45from camcops_server.cc_modules.cc_client_api_core import ( 

46 fail_server_error, 

47 fail_unsupported_operation, 

48 fail_user_error, 

49 ServerErrorException, 

50 TabletParam, 

51 UserErrorException, 

52) 

53from camcops_server.cc_modules.cc_convert import decode_values 

54from camcops_server.cc_modules.cc_ipuse import IpUse 

55from camcops_server.cc_modules.cc_proquint import uuid_from_proquint 

56from camcops_server.cc_modules.cc_unittest import ( 

57 BasicDatabaseTestCase, 

58 DemoDatabaseTestCase, 

59) 

60from camcops_server.cc_modules.cc_user import User 

61from camcops_server.cc_modules.cc_version import MINIMUM_TABLET_VERSION 

62from camcops_server.cc_modules.cc_validators import ( 

63 validate_alphanum_underscore, 

64) 

65from camcops_server.cc_modules.client_api import ( 

66 client_api, 

67 DEVICE_STORED_VAR_TABLENAME_DEFUNCT, 

68 FAILURE_CODE, 

69 make_single_user_mode_username, 

70 Operations, 

71 SUCCESS_CODE, 

72) 

73 

74 

75TEST_NHS_NUMBER = generate_random_nhs_number() 

76 

77 

78def get_reply_dict_from_response(response: Response) -> Dict[str, str]: 

79 """ 

80 For unit testing: convert the text in a :class:`Response` back to a 

81 dictionary, so we can check it was correct. 

82 """ 

83 txt = str(response) 

84 d = {} # type: Dict[str, str] 

85 # Format is: "200 OK\r\n<other headers>\r\n\r\n<content>" 

86 # There's a blank line between the heads and the body. 

87 http_gap = "\r\n\r\n" 

88 camcops_linesplit = "\n" 

89 camcops_k_v_sep = ":" 

90 try: 

91 start_of_content = txt.index(http_gap) + len(http_gap) 

92 txt = txt[start_of_content:] 

93 for line in txt.split(camcops_linesplit): 

94 if not line: 

95 continue 

96 colon_pos = line.index(camcops_k_v_sep) 

97 key = line[:colon_pos] 

98 value = line[colon_pos + len(camcops_k_v_sep) :] # noqa: E203 

99 key = key.strip() 

100 value = value.strip() 

101 d[key] = value 

102 return d 

103 except ValueError: 

104 return {} 

105 

106 

107class ClientApiTests(DemoDatabaseTestCase): 

108 """ 

109 Unit tests. 

110 """ 

111 

112 def test_client_api_basics(self) -> None: 

113 self.announce("test_client_api_basics") 

114 

115 with self.assertRaises(UserErrorException): 

116 fail_user_error("testmsg") 

117 with self.assertRaises(ServerErrorException): 

118 fail_server_error("testmsg") 

119 with self.assertRaises(UserErrorException): 

120 fail_unsupported_operation("duffop") 

121 

122 # Encoding/decoding tests 

123 # data = bytearray("hello") 

124 data = b"hello" 

125 enc_b64data = base64_64format_encode(data) 

126 enc_hexdata = hex_xformat_encode(data) 

127 not_enc_1 = "X'012345'" 

128 not_enc_2 = "64'aGVsbG8='" 

129 teststring = """one, two, 3, 4.5, NULL, 'hello "hi 

130 with linebreak"', 'NULL', 'quote''s here', {b}, {h}, {s1}, {s2}""" 

131 sql_csv_testdict = { 

132 teststring.format( 

133 b=enc_b64data, 

134 h=enc_hexdata, 

135 s1=sql_quote_string(not_enc_1), 

136 s2=sql_quote_string(not_enc_2), 

137 ): [ 

138 "one", 

139 "two", 

140 3, 

141 4.5, 

142 None, 

143 'hello "hi\n with linebreak"', 

144 "NULL", 

145 "quote's here", 

146 data, 

147 data, 

148 not_enc_1, 

149 not_enc_2, 

150 ], 

151 "": [], 

152 } 

153 for k, v in sql_csv_testdict.items(): 

154 r = decode_values(k) 

155 self.assertEqual( 

156 r, 

157 v, 

158 "Mismatch! Result: {r!s}\n" 

159 "Should have been: {v!s}\n" 

160 "Key was: {k!s}".format(r=r, v=v, k=k), 

161 ) 

162 

163 # Newline encoding/decodine 

164 ts2 = ( 

165 "slash \\ newline \n ctrl_r \r special \\n other special \\r " 

166 "quote ' doublequote \" " 

167 ) 

168 self.assertEqual( 

169 unescape_newlines(escape_newlines(ts2)), 

170 ts2, 

171 "Bug in escape_newlines() or unescape_newlines()", 

172 ) 

173 

174 # TODO: client_api.ClientApiTests: more tests here... ? 

175 

176 def test_client_api_antique_support_1(self) -> None: 

177 self.announce("test_client_api_antique_support_1") 

178 self.req.fake_request_post_from_dict( 

179 { 

180 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

181 TabletParam.DEVICE: self.other_device.name, 

182 TabletParam.OPERATION: Operations.WHICH_KEYS_TO_SEND, 

183 TabletParam.TABLE: DEVICE_STORED_VAR_TABLENAME_DEFUNCT, 

184 } 

185 ) 

186 response = client_api(self.req) 

187 d = get_reply_dict_from_response(response) 

188 self.assertEqual(d[TabletParam.SUCCESS], SUCCESS_CODE) 

189 

190 def test_client_api_antique_support_2(self) -> None: 

191 self.announce("test_client_api_antique_support_2") 

192 self.req.fake_request_post_from_dict( 

193 { 

194 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

195 TabletParam.DEVICE: self.other_device.name, 

196 TabletParam.OPERATION: Operations.WHICH_KEYS_TO_SEND, 

197 TabletParam.TABLE: "nonexistent_table", 

198 } 

199 ) 

200 response = client_api(self.req) 

201 d = get_reply_dict_from_response(response) 

202 self.assertEqual(d[TabletParam.SUCCESS], FAILURE_CODE) 

203 

204 def test_client_api_antique_support_3(self) -> None: 

205 self.announce("test_client_api_antique_support_3") 

206 self.req.fake_request_post_from_dict( 

207 { 

208 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

209 TabletParam.DEVICE: self.other_device.name, 

210 TabletParam.OPERATION: Operations.UPLOAD_TABLE, 

211 TabletParam.TABLE: DEVICE_STORED_VAR_TABLENAME_DEFUNCT, 

212 } 

213 ) 

214 response = client_api(self.req) 

215 d = get_reply_dict_from_response(response) 

216 self.assertEqual(d[TabletParam.SUCCESS], SUCCESS_CODE) 

217 

218 def test_client_api_validators(self) -> None: 

219 self.announce("test_client_api_validators") 

220 for x in class_attribute_names(Operations): 

221 try: 

222 validate_alphanum_underscore(x, self.req) 

223 except ValueError: 

224 self.fail(f"Operations.{x} fails validate_alphanum_underscore") 

225 

226 

227class PatientRegistrationTests(BasicDatabaseTestCase): 

228 def test_returns_patient_info(self) -> None: 

229 import datetime 

230 

231 patient = self.create_patient( 

232 forename="JO", 

233 surname="PATIENT", 

234 dob=datetime.date(1958, 4, 19), 

235 sex="F", 

236 address="Address", 

237 gp="GP", 

238 other="Other", 

239 as_server_patient=True, 

240 ) 

241 

242 self.create_patient_idnum( 

243 patient_id=patient.id, 

244 which_idnum=self.nhs_iddef.which_idnum, 

245 idnum_value=TEST_NHS_NUMBER, 

246 as_server_patient=True, 

247 ) 

248 

249 proquint = patient.uuid_as_proquint 

250 

251 # For type checker 

252 assert proquint is not None 

253 assert self.other_device.name is not None 

254 

255 self.req.fake_request_post_from_dict( 

256 { 

257 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

258 TabletParam.DEVICE: self.other_device.name, 

259 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

260 TabletParam.PATIENT_PROQUINT: proquint, 

261 } 

262 ) 

263 response = client_api(self.req) 

264 reply_dict = get_reply_dict_from_response(response) 

265 

266 self.assertEqual( 

267 reply_dict[TabletParam.SUCCESS], SUCCESS_CODE, msg=reply_dict 

268 ) 

269 

270 patient_dict = json.loads(reply_dict[TabletParam.PATIENT_INFO])[0] 

271 

272 self.assertEqual(patient_dict[TabletParam.SURNAME], "PATIENT") 

273 self.assertEqual(patient_dict[TabletParam.FORENAME], "JO") 

274 self.assertEqual(patient_dict[TabletParam.SEX], "F") 

275 self.assertEqual(patient_dict[TabletParam.DOB], "1958-04-19") 

276 self.assertEqual(patient_dict[TabletParam.ADDRESS], "Address") 

277 self.assertEqual(patient_dict[TabletParam.GP], "GP") 

278 self.assertEqual(patient_dict[TabletParam.OTHER], "Other") 

279 self.assertEqual( 

280 patient_dict[f"idnum{self.nhs_iddef.which_idnum}"], TEST_NHS_NUMBER 

281 ) 

282 

283 def test_creates_user(self) -> None: 

284 from camcops_server.cc_modules.cc_taskindex import ( 

285 PatientIdNumIndexEntry, 

286 ) 

287 

288 patient = self.create_patient( 

289 _group_id=self.group.id, as_server_patient=True 

290 ) 

291 idnum = self.create_patient_idnum( 

292 patient_id=patient.id, 

293 which_idnum=self.nhs_iddef.which_idnum, 

294 idnum_value=TEST_NHS_NUMBER, 

295 as_server_patient=True, 

296 ) 

297 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession) 

298 

299 proquint = patient.uuid_as_proquint 

300 

301 # For type checker 

302 assert proquint is not None 

303 assert self.other_device.name is not None 

304 

305 self.req.fake_request_post_from_dict( 

306 { 

307 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

308 TabletParam.DEVICE: self.other_device.name, 

309 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

310 TabletParam.PATIENT_PROQUINT: proquint, 

311 } 

312 ) 

313 response = client_api(self.req) 

314 reply_dict = get_reply_dict_from_response(response) 

315 

316 self.assertEqual( 

317 reply_dict[TabletParam.SUCCESS], SUCCESS_CODE, msg=reply_dict 

318 ) 

319 

320 username = reply_dict[TabletParam.USER] 

321 self.assertEqual( 

322 username, 

323 make_single_user_mode_username( 

324 self.other_device.name, patient._pk 

325 ), 

326 ) 

327 password = reply_dict[TabletParam.PASSWORD] 

328 self.assertEqual(len(password), 32) 

329 

330 valid_chars = string.ascii_letters + string.digits + string.punctuation 

331 self.assertTrue(all(c in valid_chars for c in password)) 

332 

333 user = ( 

334 self.req.dbsession.query(User) 

335 .filter(User.username == username) 

336 .one_or_none() 

337 ) 

338 self.assertIsNotNone(user) 

339 self.assertEqual(user.upload_group, patient.group) 

340 self.assertTrue(user.auto_generated) 

341 self.assertTrue(user.may_register_devices) 

342 self.assertTrue(user.may_upload) 

343 

344 def test_does_not_create_user_when_name_exists(self) -> None: 

345 from camcops_server.cc_modules.cc_taskindex import ( 

346 PatientIdNumIndexEntry, 

347 ) 

348 

349 patient = self.create_patient( 

350 _group_id=self.group.id, as_server_patient=True 

351 ) 

352 idnum = self.create_patient_idnum( 

353 patient_id=patient.id, 

354 which_idnum=self.nhs_iddef.which_idnum, 

355 idnum_value=TEST_NHS_NUMBER, 

356 as_server_patient=True, 

357 ) 

358 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession) 

359 

360 proquint = patient.uuid_as_proquint 

361 

362 user = User( 

363 username=make_single_user_mode_username( 

364 self.other_device.name, patient._pk 

365 ) 

366 ) 

367 user.set_password(self.req, "old password") 

368 self.dbsession.add(user) 

369 self.dbsession.commit() 

370 

371 self.req.fake_request_post_from_dict( 

372 { 

373 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

374 TabletParam.DEVICE: self.other_device.name, 

375 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

376 TabletParam.PATIENT_PROQUINT: proquint, 

377 } 

378 ) 

379 response = client_api(self.req) 

380 reply_dict = get_reply_dict_from_response(response) 

381 

382 self.assertEqual( 

383 reply_dict[TabletParam.SUCCESS], SUCCESS_CODE, msg=reply_dict 

384 ) 

385 

386 username = reply_dict[TabletParam.USER] 

387 self.assertEqual( 

388 username, 

389 make_single_user_mode_username( 

390 self.other_device.name, patient._pk 

391 ), 

392 ) 

393 password = reply_dict[TabletParam.PASSWORD] 

394 self.assertEqual(len(password), 32) 

395 

396 valid_chars = string.ascii_letters + string.digits + string.punctuation 

397 self.assertTrue(all(c in valid_chars for c in password)) 

398 

399 user = ( 

400 self.req.dbsession.query(User) 

401 .filter(User.username == username) 

402 .one_or_none() 

403 ) 

404 self.assertIsNotNone(user) 

405 self.assertEqual(user.upload_group, patient.group) 

406 self.assertTrue(user.auto_generated) 

407 self.assertTrue(user.may_register_devices) 

408 self.assertTrue(user.may_upload) 

409 

410 def test_raises_for_invalid_proquint(self) -> None: 

411 # For type checker 

412 assert self.other_device.name is not None 

413 

414 self.req.fake_request_post_from_dict( 

415 { 

416 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

417 TabletParam.DEVICE: self.other_device.name, 

418 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

419 TabletParam.PATIENT_PROQUINT: "invalid", 

420 } 

421 ) 

422 response = client_api(self.req) 

423 reply_dict = get_reply_dict_from_response(response) 

424 

425 self.assertEqual( 

426 reply_dict[TabletParam.SUCCESS], FAILURE_CODE, msg=reply_dict 

427 ) 

428 self.assertIn( 

429 "no patient with access key 'invalid'", 

430 reply_dict[TabletParam.ERROR], 

431 ) 

432 

433 def test_raises_for_missing_valid_proquint(self) -> None: 

434 valid_proquint = "sazom-diliv-navol-hubot-mufur-mamuv-kojus-loluv-v" 

435 

436 # Error message is same as for invalid proquint so make sure our 

437 # test proquint really is valid (should not raise) 

438 uuid_from_proquint(valid_proquint) 

439 

440 assert self.other_device.name is not None 

441 

442 self.req.fake_request_post_from_dict( 

443 { 

444 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

445 TabletParam.DEVICE: self.other_device.name, 

446 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

447 TabletParam.PATIENT_PROQUINT: valid_proquint, 

448 } 

449 ) 

450 response = client_api(self.req) 

451 reply_dict = get_reply_dict_from_response(response) 

452 

453 self.assertEqual( 

454 reply_dict[TabletParam.SUCCESS], FAILURE_CODE, msg=reply_dict 

455 ) 

456 self.assertIn( 

457 f"no patient with access key '{valid_proquint}'", 

458 reply_dict[TabletParam.ERROR], 

459 ) 

460 

461 def test_raises_when_no_patient_idnums(self) -> None: 

462 # In theory this shouldn't be possible in normal operation as the 

463 # patient cannot be created without any idnums 

464 patient = self.create_patient(as_server_patient=True) 

465 

466 proquint = patient.uuid_as_proquint 

467 self.req.fake_request_post_from_dict( 

468 { 

469 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

470 TabletParam.DEVICE: self.other_device.name, 

471 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

472 TabletParam.PATIENT_PROQUINT: proquint, 

473 } 

474 ) 

475 

476 response = client_api(self.req) 

477 reply_dict = get_reply_dict_from_response(response) 

478 self.assertEqual( 

479 reply_dict[TabletParam.SUCCESS], FAILURE_CODE, msg=reply_dict 

480 ) 

481 self.assertIn( 

482 "Patient has no ID numbers", reply_dict[TabletParam.ERROR] 

483 ) 

484 

485 def test_raises_when_patient_not_created_on_server(self) -> None: 

486 patient = self.create_patient( 

487 _device_id=self.other_device.id, as_server_patient=True 

488 ) 

489 

490 proquint = patient.uuid_as_proquint 

491 self.req.fake_request_post_from_dict( 

492 { 

493 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

494 TabletParam.DEVICE: self.other_device.name, 

495 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

496 TabletParam.PATIENT_PROQUINT: proquint, 

497 } 

498 ) 

499 

500 response = client_api(self.req) 

501 reply_dict = get_reply_dict_from_response(response) 

502 self.assertEqual( 

503 reply_dict[TabletParam.SUCCESS], FAILURE_CODE, msg=reply_dict 

504 ) 

505 self.assertIn( 

506 f"no patient with access key '{proquint}'", 

507 reply_dict[TabletParam.ERROR], 

508 ) 

509 

510 def test_returns_ip_use_flags(self) -> None: 

511 import datetime 

512 from camcops_server.cc_modules.cc_taskindex import ( 

513 PatientIdNumIndexEntry, 

514 ) 

515 

516 patient = self.create_patient( 

517 forename="JO", 

518 surname="PATIENT", 

519 dob=datetime.date(1958, 4, 19), 

520 sex="F", 

521 address="Address", 

522 gp="GP", 

523 other="Other", 

524 as_server_patient=True, 

525 ) 

526 idnum = self.create_patient_idnum( 

527 patient_id=patient.id, 

528 which_idnum=self.nhs_iddef.which_idnum, 

529 idnum_value=TEST_NHS_NUMBER, 

530 as_server_patient=True, 

531 ) 

532 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession) 

533 

534 patient.group.ip_use = IpUse() 

535 

536 patient.group.ip_use.commercial = True 

537 patient.group.ip_use.clinical = True 

538 patient.group.ip_use.educational = False 

539 patient.group.ip_use.research = False 

540 

541 self.dbsession.add(patient.group) 

542 self.dbsession.commit() 

543 

544 proquint = patient.uuid_as_proquint 

545 

546 # For type checker 

547 assert proquint is not None 

548 assert self.other_device.name is not None 

549 

550 self.req.fake_request_post_from_dict( 

551 { 

552 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

553 TabletParam.DEVICE: self.other_device.name, 

554 TabletParam.OPERATION: Operations.REGISTER_PATIENT, 

555 TabletParam.PATIENT_PROQUINT: proquint, 

556 } 

557 ) 

558 response = client_api(self.req) 

559 reply_dict = get_reply_dict_from_response(response) 

560 

561 self.assertEqual( 

562 reply_dict[TabletParam.SUCCESS], SUCCESS_CODE, msg=reply_dict 

563 ) 

564 

565 ip_use_info = json.loads(reply_dict[TabletParam.IP_USE_INFO]) 

566 

567 self.assertEqual(ip_use_info[TabletParam.IP_USE_COMMERCIAL], 1) 

568 self.assertEqual(ip_use_info[TabletParam.IP_USE_CLINICAL], 1) 

569 self.assertEqual(ip_use_info[TabletParam.IP_USE_EDUCATIONAL], 0) 

570 self.assertEqual(ip_use_info[TabletParam.IP_USE_RESEARCH], 0) 

571 

572 

573class GetTaskSchedulesTests(BasicDatabaseTestCase): 

574 def test_returns_task_schedules(self) -> None: 

575 from pendulum import DateTime as Pendulum, Duration, local, parse 

576 

577 from camcops_server.cc_modules.cc_taskindex import ( 

578 PatientIdNumIndexEntry, 

579 TaskIndexEntry, 

580 ) 

581 from camcops_server.cc_modules.cc_taskschedule import ( 

582 PatientTaskSchedule, 

583 TaskSchedule, 

584 TaskScheduleItem, 

585 ) 

586 from camcops_server.tasks.bmi import Bmi 

587 

588 schedule1 = TaskSchedule() 

589 schedule1.group_id = self.group.id 

590 schedule1.name = "Test 1" 

591 self.dbsession.add(schedule1) 

592 

593 schedule2 = TaskSchedule() 

594 schedule2.group_id = self.group.id 

595 self.dbsession.add(schedule2) 

596 self.dbsession.commit() 

597 

598 item1 = TaskScheduleItem() 

599 item1.schedule_id = schedule1.id 

600 item1.task_table_name = "phq9" 

601 item1.due_from = Duration(days=0) 

602 item1.due_by = Duration(days=7) 

603 self.dbsession.add(item1) 

604 

605 item2 = TaskScheduleItem() 

606 item2.schedule_id = schedule1.id 

607 item2.task_table_name = "bmi" 

608 item2.due_from = Duration(days=0) 

609 item2.due_by = Duration(days=8) 

610 self.dbsession.add(item2) 

611 

612 item3 = TaskScheduleItem() 

613 item3.schedule_id = schedule1.id 

614 item3.task_table_name = "phq9" 

615 item3.due_from = Duration(days=30) 

616 item3.due_by = Duration(days=37) 

617 self.dbsession.add(item3) 

618 

619 item4 = TaskScheduleItem() 

620 item4.schedule_id = schedule1.id 

621 item4.task_table_name = "gmcpq" 

622 item4.due_from = Duration(days=30) 

623 item4.due_by = Duration(days=38) 

624 self.dbsession.add(item4) 

625 self.dbsession.commit() 

626 

627 patient = self.create_patient() 

628 idnum = self.create_patient_idnum( 

629 patient_id=patient.id, 

630 which_idnum=self.nhs_iddef.which_idnum, 

631 idnum_value=TEST_NHS_NUMBER, 

632 ) 

633 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession) 

634 

635 server_patient = self.create_patient(as_server_patient=True) 

636 _ = self.create_patient_idnum( 

637 patient_id=server_patient.id, 

638 which_idnum=self.nhs_iddef.which_idnum, 

639 idnum_value=TEST_NHS_NUMBER, 

640 as_server_patient=True, 

641 ) 

642 

643 schedule_1 = PatientTaskSchedule() 

644 schedule_1.patient_pk = server_patient.pk 

645 schedule_1.schedule_id = schedule1.id 

646 schedule_1.settings = { 

647 "bmi": {"bmi_key": "bmi_value"}, 

648 "phq9": {"phq9_key": "phq9_value"}, 

649 } 

650 schedule_1.start_datetime = local(2020, 7, 31) 

651 self.dbsession.add(schedule_1) 

652 

653 schedule_2 = PatientTaskSchedule() 

654 schedule_2.patient_pk = server_patient.pk 

655 schedule_2.schedule_id = schedule2.id 

656 self.dbsession.add(schedule_2) 

657 

658 bmi = Bmi() 

659 self.apply_standard_task_fields(bmi) 

660 bmi.id = 1 

661 bmi.height_m = 1.83 

662 bmi.mass_kg = 67.57 

663 bmi.patient_id = patient.id 

664 bmi.when_created = local(2020, 8, 1) 

665 self.dbsession.add(bmi) 

666 self.dbsession.commit() 

667 self.assertTrue(bmi.is_complete()) 

668 

669 TaskIndexEntry.index_task( 

670 bmi, self.dbsession, indexed_at_utc=Pendulum.utcnow() 

671 ) 

672 self.dbsession.commit() 

673 

674 proquint = server_patient.uuid_as_proquint 

675 

676 # For type checker 

677 assert proquint is not None 

678 assert self.other_device.name is not None 

679 

680 self.req.fake_request_post_from_dict( 

681 { 

682 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION, 

683 TabletParam.DEVICE: self.other_device.name, 

684 TabletParam.OPERATION: Operations.GET_TASK_SCHEDULES, 

685 TabletParam.PATIENT_PROQUINT: proquint, 

686 } 

687 ) 

688 response = client_api(self.req) 

689 reply_dict = get_reply_dict_from_response(response) 

690 

691 self.assertEqual( 

692 reply_dict[TabletParam.SUCCESS], SUCCESS_CODE, msg=reply_dict 

693 ) 

694 

695 task_schedules = json.loads(reply_dict[TabletParam.TASK_SCHEDULES]) 

696 

697 self.assertEqual(len(task_schedules), 2) 

698 

699 s = task_schedules[0] 

700 self.assertEqual(s[TabletParam.TASK_SCHEDULE_NAME], "Test 1") 

701 

702 schedule_items = s[TabletParam.TASK_SCHEDULE_ITEMS] 

703 self.assertEqual(len(schedule_items), 4) 

704 

705 phq9_1_sched = schedule_items[0] 

706 self.assertEqual(phq9_1_sched[TabletParam.TABLE], "phq9") 

707 self.assertEqual( 

708 phq9_1_sched[TabletParam.SETTINGS], {"phq9_key": "phq9_value"} 

709 ) 

710 self.assertEqual( 

711 parse(phq9_1_sched[TabletParam.DUE_FROM]), local(2020, 7, 31) 

712 ) 

713 self.assertEqual( 

714 parse(phq9_1_sched[TabletParam.DUE_BY]), local(2020, 8, 7) 

715 ) 

716 self.assertFalse(phq9_1_sched[TabletParam.COMPLETE]) 

717 self.assertFalse(phq9_1_sched[TabletParam.ANONYMOUS]) 

718 

719 bmi_sched = schedule_items[1] 

720 self.assertEqual(bmi_sched[TabletParam.TABLE], "bmi") 

721 self.assertEqual( 

722 bmi_sched[TabletParam.SETTINGS], {"bmi_key": "bmi_value"} 

723 ) 

724 self.assertEqual( 

725 parse(bmi_sched[TabletParam.DUE_FROM]), local(2020, 7, 31) 

726 ) 

727 self.assertEqual( 

728 parse(bmi_sched[TabletParam.DUE_BY]), local(2020, 8, 8) 

729 ) 

730 self.assertTrue(bmi_sched[TabletParam.COMPLETE]) 

731 self.assertFalse(bmi_sched[TabletParam.ANONYMOUS]) 

732 

733 phq9_2_sched = schedule_items[2] 

734 self.assertEqual(phq9_2_sched[TabletParam.TABLE], "phq9") 

735 self.assertEqual( 

736 phq9_2_sched[TabletParam.SETTINGS], {"phq9_key": "phq9_value"} 

737 ) 

738 self.assertEqual( 

739 parse(phq9_2_sched[TabletParam.DUE_FROM]), local(2020, 8, 30) 

740 ) 

741 self.assertEqual( 

742 parse(phq9_2_sched[TabletParam.DUE_BY]), local(2020, 9, 6) 

743 ) 

744 self.assertFalse(phq9_2_sched[TabletParam.COMPLETE]) 

745 self.assertFalse(phq9_2_sched[TabletParam.ANONYMOUS]) 

746 

747 # GMCPQ 

748 gmcpq_sched = schedule_items[3] 

749 self.assertTrue(gmcpq_sched[TabletParam.ANONYMOUS])