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

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
3"""
4camcops_server/cc_modules/tests/client_api_tests.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27"""
29import json
30# from pprint import pformat
31import string
33from cardinal_pythonlib.convert import (
34 base64_64format_encode,
35 hex_xformat_encode,
36)
37from cardinal_pythonlib.sql.literals import sql_quote_string
38from cardinal_pythonlib.text import escape_newlines, unescape_newlines
40from camcops_server.cc_modules.cc_client_api_core import (
41 fail_server_error,
42 fail_unsupported_operation,
43 fail_user_error,
44 ServerErrorException,
45 TabletParam,
46 UserErrorException,
47)
48from camcops_server.cc_modules.cc_convert import (
49 decode_values,
50)
51from camcops_server.cc_modules.cc_ipuse import IpUse
52from camcops_server.cc_modules.cc_proquint import (
53 uuid_from_proquint,
54)
55from camcops_server.cc_modules.cc_taskindex import update_indexes_and_push_exports # noqa
56from camcops_server.cc_modules.cc_testhelpers import class_attribute_names
57from camcops_server.cc_modules.cc_unittest import DemoDatabaseTestCase
58from camcops_server.cc_modules.cc_user import User
59from camcops_server.cc_modules.cc_version import (
60 MINIMUM_TABLET_VERSION,
61)
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 get_reply_dict_from_response,
70 make_single_user_mode_username,
71 Operations,
72 SUCCESS_CODE,
73 TEST_NHS_NUMBER,
74)
77class ClientApiTests(DemoDatabaseTestCase):
78 """
79 Unit tests.
80 """
81 def test_client_api_basics(self) -> None:
82 self.announce("test_client_api_basics")
84 with self.assertRaises(UserErrorException):
85 fail_user_error("testmsg")
86 with self.assertRaises(ServerErrorException):
87 fail_server_error("testmsg")
88 with self.assertRaises(UserErrorException):
89 fail_unsupported_operation("duffop")
91 # Encoding/decoding tests
92 # data = bytearray("hello")
93 data = b"hello"
94 enc_b64data = base64_64format_encode(data)
95 enc_hexdata = hex_xformat_encode(data)
96 not_enc_1 = "X'012345'"
97 not_enc_2 = "64'aGVsbG8='"
98 teststring = """one, two, 3, 4.5, NULL, 'hello "hi
99 with linebreak"', 'NULL', 'quote''s here', {b}, {h}, {s1}, {s2}"""
100 sql_csv_testdict = {
101 teststring.format(
102 b=enc_b64data,
103 h=enc_hexdata,
104 s1=sql_quote_string(not_enc_1),
105 s2=sql_quote_string(not_enc_2),
106 ): [
107 "one",
108 "two",
109 3,
110 4.5,
111 None,
112 'hello "hi\n with linebreak"',
113 "NULL",
114 "quote's here",
115 data,
116 data,
117 not_enc_1,
118 not_enc_2,
119 ],
120 "": [],
121 }
122 for k, v in sql_csv_testdict.items():
123 r = decode_values(k)
124 self.assertEqual(r, v, "Mismatch! Result: {r!s}\n"
125 "Should have been: {v!s}\n"
126 "Key was: {k!s}".format(r=r, v=v, k=k))
128 # Newline encoding/decodine
129 ts2 = "slash \\ newline \n ctrl_r \r special \\n other special \\r " \
130 "quote ' doublequote \" "
131 self.assertEqual(unescape_newlines(escape_newlines(ts2)), ts2,
132 "Bug in escape_newlines() or unescape_newlines()")
134 # TODO: client_api.ClientApiTests: more tests here... ?
136 def test_client_api_antique_support_1(self) -> None:
137 self.announce("test_client_api_antique_support_1")
138 self.req.fake_request_post_from_dict({
139 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
140 TabletParam.DEVICE: self.other_device.name,
141 TabletParam.OPERATION: Operations.WHICH_KEYS_TO_SEND,
142 TabletParam.TABLE: DEVICE_STORED_VAR_TABLENAME_DEFUNCT,
143 })
144 response = client_api(self.req)
145 d = get_reply_dict_from_response(response)
146 self.assertEqual(d[TabletParam.SUCCESS], SUCCESS_CODE)
148 def test_client_api_antique_support_2(self) -> None:
149 self.announce("test_client_api_antique_support_2")
150 self.req.fake_request_post_from_dict({
151 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
152 TabletParam.DEVICE: self.other_device.name,
153 TabletParam.OPERATION: Operations.WHICH_KEYS_TO_SEND,
154 TabletParam.TABLE: "nonexistent_table",
155 })
156 response = client_api(self.req)
157 d = get_reply_dict_from_response(response)
158 self.assertEqual(d[TabletParam.SUCCESS], FAILURE_CODE)
160 def test_client_api_antique_support_3(self) -> None:
161 self.announce("test_client_api_antique_support_3")
162 self.req.fake_request_post_from_dict({
163 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
164 TabletParam.DEVICE: self.other_device.name,
165 TabletParam.OPERATION: Operations.UPLOAD_TABLE,
166 TabletParam.TABLE: DEVICE_STORED_VAR_TABLENAME_DEFUNCT,
167 })
168 response = client_api(self.req)
169 d = get_reply_dict_from_response(response)
170 self.assertEqual(d[TabletParam.SUCCESS], SUCCESS_CODE)
172 def test_client_api_validators(self) -> None:
173 self.announce("test_client_api_validators")
174 for x in class_attribute_names(Operations):
175 try:
176 validate_alphanum_underscore(x, self.req)
177 except ValueError:
178 self.fail(f"Operations.{x} fails validate_alphanum_underscore")
181class PatientRegistrationTests(DemoDatabaseTestCase):
182 def create_tasks(self) -> None:
183 # Speed things up a bit
184 pass
186 def test_returns_patient_info(self) -> None:
187 import datetime
188 patient = self.create_patient(
189 forename="JO", surname="PATIENT", dob=datetime.date(1958, 4, 19),
190 sex="F", address="Address", gp="GP", other="Other",
191 as_server_patient=True
192 )
194 self.create_patient_idnum(
195 patient_id=patient.id,
196 which_idnum=self.nhs_iddef.which_idnum,
197 idnum_value=TEST_NHS_NUMBER,
198 as_server_patient=True
199 )
201 proquint = patient.uuid_as_proquint
203 # For type checker
204 assert proquint is not None
205 assert self.other_device.name is not None
207 self.req.fake_request_post_from_dict({
208 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
209 TabletParam.DEVICE: self.other_device.name,
210 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
211 TabletParam.PATIENT_PROQUINT: proquint,
212 })
213 response = client_api(self.req)
214 reply_dict = get_reply_dict_from_response(response)
216 self.assertEqual(reply_dict[TabletParam.SUCCESS], SUCCESS_CODE,
217 msg=reply_dict)
219 patient_dict = json.loads(reply_dict[TabletParam.PATIENT_INFO])[0]
221 self.assertEqual(patient_dict[TabletParam.SURNAME], "PATIENT")
222 self.assertEqual(patient_dict[TabletParam.FORENAME], "JO")
223 self.assertEqual(patient_dict[TabletParam.SEX], "F")
224 self.assertEqual(patient_dict[TabletParam.DOB], "1958-04-19")
225 self.assertEqual(patient_dict[TabletParam.ADDRESS], "Address")
226 self.assertEqual(patient_dict[TabletParam.GP], "GP")
227 self.assertEqual(patient_dict[TabletParam.OTHER], "Other")
228 self.assertEqual(patient_dict[f"idnum{self.nhs_iddef.which_idnum}"],
229 TEST_NHS_NUMBER)
231 def test_creates_user(self) -> None:
232 from camcops_server.cc_modules.cc_taskindex import (
233 PatientIdNumIndexEntry,
234 )
235 patient = self.create_patient(_group_id=self.group.id,
236 as_server_patient=True)
237 idnum = self.create_patient_idnum(
238 patient_id=patient.id,
239 which_idnum=self.nhs_iddef.which_idnum,
240 idnum_value=TEST_NHS_NUMBER,
241 as_server_patient=True
242 )
243 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession)
245 proquint = patient.uuid_as_proquint
247 # For type checker
248 assert proquint is not None
249 assert self.other_device.name is not None
251 self.req.fake_request_post_from_dict({
252 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
253 TabletParam.DEVICE: self.other_device.name,
254 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
255 TabletParam.PATIENT_PROQUINT: proquint,
256 })
257 response = client_api(self.req)
258 reply_dict = get_reply_dict_from_response(response)
260 self.assertEqual(reply_dict[TabletParam.SUCCESS], SUCCESS_CODE,
261 msg=reply_dict)
263 username = reply_dict[TabletParam.USER]
264 self.assertEqual(
265 username,
266 make_single_user_mode_username(self.other_device.name, patient._pk)
267 )
268 password = reply_dict[TabletParam.PASSWORD]
269 self.assertEqual(len(password), 32)
271 valid_chars = string.ascii_letters + string.digits + string.punctuation
272 self.assertTrue(all(c in valid_chars for c in password))
274 user = self.req.dbsession.query(User).filter(
275 User.username == username).one_or_none()
276 self.assertIsNotNone(user)
277 self.assertEqual(user.upload_group, patient.group)
278 self.assertTrue(user.auto_generated)
279 self.assertTrue(user.may_register_devices)
280 self.assertTrue(user.may_upload)
282 def test_does_not_create_user_when_name_exists(self) -> None:
283 from camcops_server.cc_modules.cc_taskindex import (
284 PatientIdNumIndexEntry,
285 )
286 patient = self.create_patient(_group_id=self.group.id,
287 as_server_patient=True)
288 idnum = self.create_patient_idnum(
289 patient_id=patient.id,
290 which_idnum=self.nhs_iddef.which_idnum,
291 idnum_value=TEST_NHS_NUMBER,
292 as_server_patient=True
293 )
294 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession)
296 proquint = patient.uuid_as_proquint
298 user = User(
299 username=make_single_user_mode_username(
300 self.other_device.name, patient._pk
301 )
302 )
303 user.set_password(self.req, "old password")
304 self.dbsession.add(user)
305 self.dbsession.commit()
307 self.req.fake_request_post_from_dict({
308 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
309 TabletParam.DEVICE: self.other_device.name,
310 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
311 TabletParam.PATIENT_PROQUINT: proquint,
312 })
313 response = client_api(self.req)
314 reply_dict = get_reply_dict_from_response(response)
316 self.assertEqual(reply_dict[TabletParam.SUCCESS], SUCCESS_CODE,
317 msg=reply_dict)
319 username = reply_dict[TabletParam.USER]
320 self.assertEqual(
321 username,
322 make_single_user_mode_username(self.other_device.name, patient._pk)
323 )
324 password = reply_dict[TabletParam.PASSWORD]
325 self.assertEqual(len(password), 32)
327 valid_chars = string.ascii_letters + string.digits + string.punctuation
328 self.assertTrue(all(c in valid_chars for c in password))
330 user = self.req.dbsession.query(User).filter(
331 User.username == username).one_or_none()
332 self.assertIsNotNone(user)
333 self.assertEqual(user.upload_group, patient.group)
334 self.assertTrue(user.auto_generated)
335 self.assertTrue(user.may_register_devices)
336 self.assertTrue(user.may_upload)
338 def test_raises_for_invalid_proquint(self) -> None:
339 # For type checker
340 assert self.other_device.name is not None
342 self.req.fake_request_post_from_dict({
343 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
344 TabletParam.DEVICE: self.other_device.name,
345 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
346 TabletParam.PATIENT_PROQUINT: "invalid",
347 })
348 response = client_api(self.req)
349 reply_dict = get_reply_dict_from_response(response)
351 self.assertEqual(reply_dict[TabletParam.SUCCESS], FAILURE_CODE,
352 msg=reply_dict)
353 self.assertIn("no patient with access key 'invalid'",
354 reply_dict[TabletParam.ERROR])
356 def test_raises_for_missing_valid_proquint(self) -> None:
357 valid_proquint = "sazom-diliv-navol-hubot-mufur-mamuv-kojus-loluv-v"
359 # Error message is same as for invalid proquint so make sure our
360 # test proquint really is valid (should not raise)
361 uuid_from_proquint(valid_proquint)
363 assert self.other_device.name is not None
365 self.req.fake_request_post_from_dict({
366 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
367 TabletParam.DEVICE: self.other_device.name,
368 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
369 TabletParam.PATIENT_PROQUINT: valid_proquint,
370 })
371 response = client_api(self.req)
372 reply_dict = get_reply_dict_from_response(response)
374 self.assertEqual(reply_dict[TabletParam.SUCCESS], FAILURE_CODE,
375 msg=reply_dict)
376 self.assertIn(f"no patient with access key '{valid_proquint}'",
377 reply_dict[TabletParam.ERROR])
379 def test_raises_when_no_patient_idnums(self) -> None:
380 # In theory this shouldn't be possible in normal operation as the
381 # patient cannot be created without any idnums
382 patient = self.create_patient(as_server_patient=True)
384 proquint = patient.uuid_as_proquint
385 self.req.fake_request_post_from_dict({
386 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
387 TabletParam.DEVICE: self.other_device.name,
388 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
389 TabletParam.PATIENT_PROQUINT: proquint,
390 })
392 response = client_api(self.req)
393 reply_dict = get_reply_dict_from_response(response)
394 self.assertEqual(reply_dict[TabletParam.SUCCESS], FAILURE_CODE,
395 msg=reply_dict)
396 self.assertIn("Patient has no ID numbers",
397 reply_dict[TabletParam.ERROR])
399 def test_raises_when_patient_not_created_on_server(self) -> None:
400 patient = self.create_patient(_device_id=self.other_device.id,
401 as_server_patient=True)
403 proquint = patient.uuid_as_proquint
404 self.req.fake_request_post_from_dict({
405 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
406 TabletParam.DEVICE: self.other_device.name,
407 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
408 TabletParam.PATIENT_PROQUINT: proquint,
409 })
411 response = client_api(self.req)
412 reply_dict = get_reply_dict_from_response(response)
413 self.assertEqual(reply_dict[TabletParam.SUCCESS], FAILURE_CODE,
414 msg=reply_dict)
415 self.assertIn(f"no patient with access key '{proquint}'",
416 reply_dict[TabletParam.ERROR])
418 def test_returns_ip_use_flags(self) -> None:
419 import datetime
420 from camcops_server.cc_modules.cc_taskindex import (
421 PatientIdNumIndexEntry,
422 )
424 patient = self.create_patient(
425 forename="JO", surname="PATIENT", dob=datetime.date(1958, 4, 19),
426 sex="F", address="Address", gp="GP", other="Other",
427 as_server_patient=True
428 )
429 idnum = self.create_patient_idnum(
430 patient_id=patient.id,
431 which_idnum=self.nhs_iddef.which_idnum,
432 idnum_value=TEST_NHS_NUMBER,
433 as_server_patient=True
434 )
435 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession)
437 patient.group.ip_use = IpUse()
439 patient.group.ip_use.commercial = True
440 patient.group.ip_use.clinical = True
441 patient.group.ip_use.educational = False
442 patient.group.ip_use.research = False
444 self.dbsession.add(patient.group)
445 self.dbsession.commit()
447 proquint = patient.uuid_as_proquint
449 # For type checker
450 assert proquint is not None
451 assert self.other_device.name is not None
453 self.req.fake_request_post_from_dict({
454 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
455 TabletParam.DEVICE: self.other_device.name,
456 TabletParam.OPERATION: Operations.REGISTER_PATIENT,
457 TabletParam.PATIENT_PROQUINT: proquint,
458 })
459 response = client_api(self.req)
460 reply_dict = get_reply_dict_from_response(response)
462 self.assertEqual(reply_dict[TabletParam.SUCCESS], SUCCESS_CODE,
463 msg=reply_dict)
465 ip_use_info = json.loads(reply_dict[TabletParam.IP_USE_INFO])
467 self.assertEqual(ip_use_info[TabletParam.IP_USE_COMMERCIAL], 1)
468 self.assertEqual(ip_use_info[TabletParam.IP_USE_CLINICAL], 1)
469 self.assertEqual(ip_use_info[TabletParam.IP_USE_EDUCATIONAL], 0)
470 self.assertEqual(ip_use_info[TabletParam.IP_USE_RESEARCH], 0)
473class GetTaskSchedulesTests(DemoDatabaseTestCase):
474 def create_tasks(self) -> None:
475 # Speed things up a bit
476 pass
478 def test_returns_task_schedules(self) -> None:
479 from pendulum import DateTime as Pendulum, Duration, local, parse
481 from camcops_server.cc_modules.cc_taskindex import (
482 PatientIdNumIndexEntry,
483 TaskIndexEntry,
484 )
485 from camcops_server.cc_modules.cc_taskschedule import (
486 PatientTaskSchedule,
487 TaskSchedule,
488 TaskScheduleItem
489 )
490 from camcops_server.tasks.bmi import Bmi
492 schedule1 = TaskSchedule()
493 schedule1.group_id = self.group.id
494 schedule1.name = "Test 1"
495 self.dbsession.add(schedule1)
497 schedule2 = TaskSchedule()
498 schedule2.group_id = self.group.id
499 self.dbsession.add(schedule2)
500 self.dbsession.commit()
502 item1 = TaskScheduleItem()
503 item1.schedule_id = schedule1.id
504 item1.task_table_name = "phq9"
505 item1.due_from = Duration(days=0)
506 item1.due_by = Duration(days=7)
507 self.dbsession.add(item1)
509 item2 = TaskScheduleItem()
510 item2.schedule_id = schedule1.id
511 item2.task_table_name = "bmi"
512 item2.due_from = Duration(days=0)
513 item2.due_by = Duration(days=8)
514 self.dbsession.add(item2)
516 item3 = TaskScheduleItem()
517 item3.schedule_id = schedule1.id
518 item3.task_table_name = "phq9"
519 item3.due_from = Duration(days=30)
520 item3.due_by = Duration(days=37)
521 self.dbsession.add(item3)
523 item4 = TaskScheduleItem()
524 item4.schedule_id = schedule1.id
525 item4.task_table_name = "gmcpq"
526 item4.due_from = Duration(days=30)
527 item4.due_by = Duration(days=38)
528 self.dbsession.add(item4)
529 self.dbsession.commit()
531 patient = self.create_patient()
532 idnum = self.create_patient_idnum(
533 patient_id=patient.id,
534 which_idnum=self.nhs_iddef.which_idnum,
535 idnum_value=TEST_NHS_NUMBER
536 )
537 PatientIdNumIndexEntry.index_idnum(idnum, self.dbsession)
539 server_patient = self.create_patient(as_server_patient=True)
540 _ = self.create_patient_idnum(
541 patient_id=server_patient.id,
542 which_idnum=self.nhs_iddef.which_idnum,
543 idnum_value=TEST_NHS_NUMBER,
544 as_server_patient=True
545 )
547 schedule_1 = PatientTaskSchedule()
548 schedule_1.patient_pk = server_patient.pk
549 schedule_1.schedule_id = schedule1.id
550 schedule_1.settings = {
551 "bmi": {
552 "bmi_key": "bmi_value",
553 },
554 "phq9": {
555 "phq9_key": "phq9_value",
556 }
557 }
558 schedule_1.start_datetime = local(2020, 7, 31)
559 self.dbsession.add(schedule_1)
561 schedule_2 = PatientTaskSchedule()
562 schedule_2.patient_pk = server_patient.pk
563 schedule_2.schedule_id = schedule2.id
564 self.dbsession.add(schedule_2)
566 bmi = Bmi()
567 self.apply_standard_task_fields(bmi)
568 bmi.id = 1
569 bmi.height_m = 1.83
570 bmi.mass_kg = 67.57
571 bmi.patient_id = patient.id
572 bmi.when_created = local(2020, 8, 1)
573 self.dbsession.add(bmi)
574 self.dbsession.commit()
575 self.assertTrue(bmi.is_complete())
577 TaskIndexEntry.index_task(
578 bmi,
579 self.dbsession,
580 indexed_at_utc=Pendulum.utcnow()
581 )
582 self.dbsession.commit()
584 proquint = server_patient.uuid_as_proquint
586 # For type checker
587 assert proquint is not None
588 assert self.other_device.name is not None
590 self.req.fake_request_post_from_dict({
591 TabletParam.CAMCOPS_VERSION: MINIMUM_TABLET_VERSION,
592 TabletParam.DEVICE: self.other_device.name,
593 TabletParam.OPERATION: Operations.GET_TASK_SCHEDULES,
594 TabletParam.PATIENT_PROQUINT: proquint,
595 })
596 response = client_api(self.req)
597 reply_dict = get_reply_dict_from_response(response)
599 self.assertEqual(reply_dict[TabletParam.SUCCESS], SUCCESS_CODE,
600 msg=reply_dict)
602 task_schedules = json.loads(reply_dict[TabletParam.TASK_SCHEDULES])
604 self.assertEqual(len(task_schedules), 2)
606 s = task_schedules[0]
607 self.assertEqual(s[TabletParam.TASK_SCHEDULE_NAME], "Test 1")
609 schedule_items = s[TabletParam.TASK_SCHEDULE_ITEMS]
610 self.assertEqual(len(schedule_items), 4)
612 phq9_1_sched = schedule_items[0]
613 self.assertEqual(phq9_1_sched[TabletParam.TABLE], "phq9")
614 self.assertEqual(phq9_1_sched[TabletParam.SETTINGS], {
615 "phq9_key": "phq9_value"
616 })
617 self.assertEqual(parse(phq9_1_sched[TabletParam.DUE_FROM]),
618 local(2020, 7, 31))
619 self.assertEqual(parse(phq9_1_sched[TabletParam.DUE_BY]),
620 local(2020, 8, 7))
621 self.assertFalse(phq9_1_sched[TabletParam.COMPLETE])
622 self.assertFalse(phq9_1_sched[TabletParam.ANONYMOUS])
624 bmi_sched = schedule_items[1]
625 self.assertEqual(bmi_sched[TabletParam.TABLE], "bmi")
626 self.assertEqual(bmi_sched[TabletParam.SETTINGS], {
627 "bmi_key": "bmi_value",
628 })
629 self.assertEqual(parse(bmi_sched[TabletParam.DUE_FROM]),
630 local(2020, 7, 31))
631 self.assertEqual(parse(bmi_sched[TabletParam.DUE_BY]),
632 local(2020, 8, 8))
633 self.assertTrue(bmi_sched[TabletParam.COMPLETE])
634 self.assertFalse(bmi_sched[TabletParam.ANONYMOUS])
636 phq9_2_sched = schedule_items[2]
637 self.assertEqual(phq9_2_sched[TabletParam.TABLE], "phq9")
638 self.assertEqual(phq9_2_sched[TabletParam.SETTINGS], {
639 "phq9_key": "phq9_value"
640 })
641 self.assertEqual(parse(phq9_2_sched[TabletParam.DUE_FROM]),
642 local(2020, 8, 30))
643 self.assertEqual(parse(phq9_2_sched[TabletParam.DUE_BY]),
644 local(2020, 9, 6))
645 self.assertFalse(phq9_2_sched[TabletParam.COMPLETE])
646 self.assertFalse(phq9_2_sched[TabletParam.ANONYMOUS])
648 # GMCPQ
649 gmcpq_sched = schedule_items[3]
650 self.assertTrue(gmcpq_sched[TabletParam.ANONYMOUS])