Coverage for cc_modules/tests/cc_policy_tests.py: 13%
76 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-30 13:48 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-30 13:48 +0000
1"""
2camcops_server/cc_modules/tests/cc_policy_tests.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
11 CamCOPS is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CamCOPS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
25"""
27import logging
28from typing import Dict
30from cardinal_pythonlib.logs import BraceStyleAdapter
31from pendulum import Date
33from camcops_server.cc_modules.cc_policy import TokenizedPolicy
34from camcops_server.cc_modules.cc_simpleobjects import (
35 BarePatientInfo,
36 IdNumReference,
37)
38from camcops_server.cc_modules.cc_unittest import ExtendedTestCase
40log = BraceStyleAdapter(logging.getLogger(__name__))
43# =============================================================================
44# Unit tests
45# =============================================================================
48class PolicyTests(ExtendedTestCase):
49 """
50 Unit tests.
51 """
53 def test_policies(self) -> None:
54 self.announce("test_policies")
56 class TestRig(object):
57 def __init__(
58 self,
59 policy: str,
60 syntactically_valid: bool = None,
61 valid: bool = None,
62 ptinfo_satisfies_id_policy: bool = None,
63 test_critical_single_numerical_id: bool = False,
64 critical_single_numerical_id: int = None,
65 compatible_with_tablet_id_policy: bool = None,
66 is_idnum_mandatory_in_policy: Dict[int, bool] = None,
67 ) -> None:
68 self.policy = policy
69 self.syntactically_valid = syntactically_valid
70 self.valid = valid
71 self.ptinfo_satisfies_id_policy = ptinfo_satisfies_id_policy
72 self.test_critical_single_numerical_id = (
73 test_critical_single_numerical_id
74 )
75 self.critical_single_numerical_id = (
76 critical_single_numerical_id
77 )
78 self.compatible_with_tablet_id_policy = (
79 compatible_with_tablet_id_policy
80 )
81 self.is_idnum_mandatory_in_policy = (
82 is_idnum_mandatory_in_policy or {}
83 ) # type: Dict[int, bool]
85 valid_idnums = list(range(1, 10 + 1))
86 # noinspection PyTypeChecker
87 bpi = BarePatientInfo(
88 forename="forename",
89 surname="surname",
90 dob=Date.today(), # random value
91 email="patient@example.com",
92 sex="F",
93 idnum_definitions=[IdNumReference(1, 1), IdNumReference(10, 3)],
94 )
95 test_policies = [
96 TestRig("", syntactically_valid=False, valid=False),
97 TestRig(
98 "sex AND (failure", syntactically_valid=False, valid=False
99 ),
100 TestRig("sex AND NOT", syntactically_valid=False, valid=False),
101 TestRig("OR OR", syntactically_valid=False, valid=False),
102 TestRig("idnum99", syntactically_valid=True, valid=False),
103 TestRig(
104 "sex AND idnum1",
105 syntactically_valid=True,
106 valid=True,
107 ptinfo_satisfies_id_policy=True,
108 test_critical_single_numerical_id=True,
109 critical_single_numerical_id=1,
110 compatible_with_tablet_id_policy=True,
111 is_idnum_mandatory_in_policy={1: True, 3: False},
112 ),
113 TestRig(
114 "sex AND NOT idnum1",
115 syntactically_valid=True,
116 valid=False, # not compatible with tablet policy
117 ptinfo_satisfies_id_policy=False,
118 test_critical_single_numerical_id=True,
119 critical_single_numerical_id=None,
120 compatible_with_tablet_id_policy=False,
121 is_idnum_mandatory_in_policy={1: False, 3: False},
122 ),
123 TestRig(
124 "sex AND NOT idnum2",
125 syntactically_valid=True,
126 valid=False, # not compatible with tablet policy
127 test_critical_single_numerical_id=True,
128 critical_single_numerical_id=None,
129 compatible_with_tablet_id_policy=False,
130 is_idnum_mandatory_in_policy={1: False, 3: False},
131 ),
132 TestRig(
133 "sex AND NOT idnum1 AND idnum3",
134 syntactically_valid=True,
135 valid=True,
136 test_critical_single_numerical_id=True,
137 critical_single_numerical_id=3,
138 compatible_with_tablet_id_policy=True,
139 is_idnum_mandatory_in_policy={1: False, 3: True},
140 ),
141 TestRig(
142 "sex AND NOT idnum2 AND idnum10",
143 syntactically_valid=True,
144 valid=True,
145 test_critical_single_numerical_id=True,
146 critical_single_numerical_id=10,
147 compatible_with_tablet_id_policy=True,
148 is_idnum_mandatory_in_policy={1: False, 3: False},
149 ),
150 TestRig(
151 "sex AND NOT idnum1 AND NOT idnum2 AND NOT idnum3",
152 syntactically_valid=True,
153 valid=False, # not compatible with tablet policy
154 test_critical_single_numerical_id=True,
155 critical_single_numerical_id=None,
156 compatible_with_tablet_id_policy=False,
157 is_idnum_mandatory_in_policy={1: False, 3: False},
158 ),
159 TestRig(
160 "sex AND NOT (idnum1 OR idnum2 OR idnum3)", # same as previous
161 syntactically_valid=True,
162 valid=False, # not compatible with tablet policy
163 test_critical_single_numerical_id=True,
164 critical_single_numerical_id=None,
165 compatible_with_tablet_id_policy=False,
166 is_idnum_mandatory_in_policy={1: False, 3: False},
167 ),
168 TestRig(
169 "NOT (sex OR forename OR surname OR dob OR anyidnum)",
170 syntactically_valid=True,
171 valid=False, # not compatible with tablet policy
172 test_critical_single_numerical_id=True,
173 critical_single_numerical_id=None,
174 compatible_with_tablet_id_policy=False,
175 is_idnum_mandatory_in_policy={1: False, 3: False},
176 ),
177 TestRig(
178 """
179 NOT (sex OR forename OR surname OR dob OR
180 idnum1 OR idnum2 OR idnum3)
181 """,
182 syntactically_valid=True,
183 valid=False, # not compatible with tablet policy
184 test_critical_single_numerical_id=True,
185 critical_single_numerical_id=None,
186 compatible_with_tablet_id_policy=False,
187 is_idnum_mandatory_in_policy={1: False, 3: False},
188 ),
189 TestRig(
190 "sex AND idnum1 AND otheridnum AND NOT address",
191 syntactically_valid=True,
192 valid=True,
193 test_critical_single_numerical_id=True,
194 critical_single_numerical_id=None,
195 compatible_with_tablet_id_policy=True,
196 is_idnum_mandatory_in_policy={1: True, 3: False},
197 ),
198 TestRig(
199 "sex AND idnum1 AND NOT (otheridnum OR forename OR surname OR "
200 "dob OR address OR gp OR otherdetails)",
201 syntactically_valid=True,
202 valid=True,
203 test_critical_single_numerical_id=True,
204 critical_single_numerical_id=1,
205 compatible_with_tablet_id_policy=True,
206 is_idnum_mandatory_in_policy={1: True, 3: False},
207 ),
208 TestRig(
209 "forename AND surname AND dob AND sex AND anyidnum",
210 syntactically_valid=True,
211 valid=True,
212 test_critical_single_numerical_id=True,
213 critical_single_numerical_id=None,
214 compatible_with_tablet_id_policy=True,
215 is_idnum_mandatory_in_policy={1: False, 3: False},
216 ),
217 TestRig(
218 "forename AND surname AND email AND sex AND idnum1",
219 syntactically_valid=True,
220 valid=True,
221 test_critical_single_numerical_id=True,
222 critical_single_numerical_id=1,
223 compatible_with_tablet_id_policy=True,
224 is_idnum_mandatory_in_policy={1: True, 3: False},
225 ),
226 TestRig(
227 "email AND sex AND idnum1",
228 syntactically_valid=True,
229 valid=True,
230 ptinfo_satisfies_id_policy=True,
231 test_critical_single_numerical_id=True,
232 critical_single_numerical_id=1,
233 compatible_with_tablet_id_policy=True,
234 is_idnum_mandatory_in_policy={1: True, 3: False},
235 ),
236 ]
238 test_idnums = [None, -1, 1, 3]
239 correct_msg = "... correct"
241 for tp in test_policies:
242 policy_string = tp.policy
243 log.info("Testing {!r}", policy_string)
244 p = TokenizedPolicy(policy_string)
245 p.set_valid_idnums(valid_idnums)
247 log.debug("Testing is_syntactically_valid()")
248 x = p.is_syntactically_valid()
249 self.assertIsInstance(x, bool)
250 log.info("is_syntactically_valid() -> {!r}".format(x))
251 if tp.syntactically_valid is not None:
252 self.assertEqual(x, tp.syntactically_valid)
253 log.info(correct_msg)
255 log.debug("Testing is_valid()")
256 x = p.is_valid(verbose=True)
257 self.assertIsInstance(x, bool)
258 log.info(
259 "is_valid(valid_idnums={!r}) -> {!r}".format(valid_idnums, x)
260 )
261 if tp.valid is not None:
262 self.assertEqual(x, tp.valid)
263 log.info(correct_msg)
265 log.debug("Testing is_idnum_mandatory_in_policy()")
266 for which_idnum in test_idnums:
267 log.debug("... for which_idnum = {}", which_idnum)
268 x = p.is_idnum_mandatory_in_policy(
269 which_idnum=which_idnum,
270 valid_idnums=valid_idnums,
271 verbose=True,
272 )
273 self.assertIsInstance(x, bool)
274 log.info(
275 "is_idnum_mandatory_in_policy(which_idnum={!r}, "
276 "valid_idnums={!r}) -> {!r}".format(
277 which_idnum, valid_idnums, x
278 )
279 )
280 if tp.is_idnum_mandatory_in_policy:
281 if which_idnum in tp.is_idnum_mandatory_in_policy:
282 self.assertEqual(
283 x, tp.is_idnum_mandatory_in_policy[which_idnum]
284 )
285 log.info(correct_msg)
287 log.debug("Testing find_critical_single_numerical_id()")
288 x = p.find_critical_single_numerical_id(
289 valid_idnums=valid_idnums, verbose=True
290 )
291 self.assertIsInstanceOrNone(x, int)
292 log.info(
293 "find_critical_single_numerical_id(valid_idnums={!r}) "
294 "-> {!r}".format(valid_idnums, x)
295 )
296 if tp.test_critical_single_numerical_id:
297 self.assertEqual(x, tp.critical_single_numerical_id)
298 log.info(correct_msg)
300 log.debug("Testing compatible_with_tablet_id_policy()")
301 x = p.compatible_with_tablet_id_policy(
302 valid_idnums=valid_idnums, verbose=True
303 )
304 self.assertIsInstance(x, bool)
305 log.info(
306 "compatible_with_tablet_id_policy(valid_idnums={!r}) "
307 "-> {!r}".format(valid_idnums, x)
308 )
309 if tp.compatible_with_tablet_id_policy is not None:
310 self.assertEqual(x, tp.compatible_with_tablet_id_policy)
311 log.info(correct_msg)
313 log.debug("Testing satisfies_id_policy()")
314 x = p.satisfies_id_policy(bpi)
315 self.assertIsInstance(x, bool)
316 log.info("satisfies_id_policy(bpi={}) -> {!r}".format(bpi, x))
317 if tp.ptinfo_satisfies_id_policy is not None:
318 self.assertEqual(x, tp.ptinfo_satisfies_id_policy)
319 log.info(correct_msg)