1 """Test Conditions module."""
2 from tests.utils import TestCaseWithDB, TestCaseWithServer, TestCaseSansDB
3 from plomtask.conditions import Condition
4 from plomtask.processes import Process
5 from plomtask.todos import Todo
6 from plomtask.exceptions import HandledException
9 class TestsSansDB(TestCaseSansDB):
10 """Tests requiring no DB setup."""
11 checked_class = Condition
13 versioned_defaults_to_test = {'title': 'UNNAMED', 'description': ''}
16 class TestsWithDB(TestCaseWithDB):
17 """Tests requiring DB, but not server setup."""
18 checked_class = Condition
19 default_init_kwargs = {'is_active': False}
20 test_versioneds = {'title': str, 'description': str}
22 def test_remove(self) -> None:
23 """Test .remove() effects on DB and cache."""
26 proc.save(self.db_conn)
27 todo = Todo(None, proc, False, '2024-01-01')
28 for depender in (proc, todo):
29 assert hasattr(depender, 'save')
30 assert hasattr(depender, 'set_conditions')
33 depender.save(self.db_conn)
34 depender.set_conditions(self.db_conn, [c.id_], 'conditions')
35 depender.save(self.db_conn)
36 with self.assertRaises(HandledException):
37 c.remove(self.db_conn)
38 depender.set_conditions(self.db_conn, [], 'conditions')
39 depender.save(self.db_conn)
40 c.remove(self.db_conn)
43 class TestsWithServer(TestCaseWithServer):
44 """Module tests against our HTTP server/handler (and database)."""
47 def cond_as_dict(id_: int = 1,
48 is_active: bool = False,
49 titles: None | list[str] = None,
50 descriptions: None | list[str] = None
51 ) -> dict[str, object]:
52 """Return JSON of Condition to expect."""
54 'is_active': is_active,
60 titles = titles if titles else []
61 descriptions = descriptions if descriptions else []
62 assert isinstance(d['_versioned'], dict)
63 for i, title in enumerate(titles):
64 d['_versioned']['title'][i] = title
65 for i, description in enumerate(descriptions):
66 d['_versioned']['description'][i] = description
70 def proc_as_dict(id_: int = 1,
72 enables: None | list[dict[str, object]] = None,
73 disables: None | list[dict[str, object]] = None,
74 conditions: None | list[dict[str, object]] = None,
75 blockers: None | list[dict[str, object]] = None
76 ) -> dict[str, object]:
77 """Return JSON of Process to expect."""
78 # pylint: disable=too-many-arguments
81 'suppressed_steps': [],
85 'description': {0: ''},
88 'conditions': conditions if conditions else [],
89 'disables': disables if disables else [],
90 'enables': enables if enables else [],
91 'blockers': blockers if blockers else []}
94 def test_do_POST_condition(self) -> None:
95 """Test POST /condition and its effect on GET /condition[s]."""
96 # check empty POST fails
97 self.check_post({}, '/condition', 400)
98 # test valid POST's effect on …
99 post = {'title': 'foo', 'description': 'oof', 'is_active': False}
100 self.check_post(post, '/condition', 302, '/condition?id=1')
101 # … single /condition
102 cond = self.cond_as_dict(titles=['foo'], descriptions=['oof'])
103 expected_single: dict[str, object]
104 expected_single = {'is_new': False,
105 'enabled_processes': [],
106 'disabled_processes': [],
107 'enabling_processes': [],
108 'disabling_processes': [],
110 self.check_json_get('/condition?id=1', expected_single)
112 expected_all: dict[str, object]
113 expected_all = {'conditions': [cond],
114 'sort_by': 'title', 'pattern': ''}
115 self.check_json_get('/conditions', expected_all)
116 # test effect of invalid POST to existing Condition on /condition
117 self.check_post({}, '/condition?id=1', 400)
118 self.check_json_get('/condition?id=1', expected_single)
119 # test effect of POST changing title and activeness
120 post = {'title': 'bar', 'description': 'oof', 'is_active': True}
121 self.check_post(post, '/condition?id=1', 302)
122 assert isinstance(expected_single['condition'], dict)
123 expected_single['condition']['_versioned']['title'][1] = 'bar'
124 expected_single['condition']['is_active'] = True
125 self.check_json_get('/condition?id=1', expected_single)
126 # test deletion POST's effect on …
127 self.check_post({'delete': ''}, '/condition?id=1', 302, '/conditions')
128 cond = self.cond_as_dict()
129 expected_single['condition'] = cond
130 self.check_json_get('/condition?id=1', expected_single)
132 expected_all['conditions'] = []
133 self.check_json_get('/conditions', expected_all)
135 def test_do_GET_condition(self) -> None:
136 """More GET /condition testing, especially for Process relations."""
137 # check expected default status codes
138 self.check_get_defaults('/condition')
139 # check display of process relations
140 form_data = {'title': 'foo', 'description': 'oof', 'is_active': False}
141 self.check_post(form_data, '/condition', 302, '/condition?id=1')
142 proc_1_post = {'title': 'A', 'description': '', 'effort': 1.0,
143 'condition': [1], 'disables': [1]}
144 self.post_process(1, proc_1_post)
145 proc_2_post = {'title': 'B', 'description': '', 'effort': 1.0,
146 'enables': [1], 'blocker': [1]}
147 self.post_process(2, proc_2_post)
148 cond = self.cond_as_dict(titles=['foo'], descriptions=['oof'])
149 proc_1 = self.proc_as_dict(conditions=[cond], disables=[cond])
150 proc_2 = self.proc_as_dict(2, 'B', blockers=[cond], enables=[cond])
151 expected_single = {'is_new': False,
152 'enabled_processes': [proc_1],
153 'disabled_processes': [proc_2],
154 'enabling_processes': [proc_2],
155 'disabling_processes': [proc_1],
157 self.check_json_get('/condition?id=1', expected_single)
159 def test_do_GET_conditions(self) -> None:
160 """Test GET /conditions."""
161 # test empty result on empty DB, default-settings on empty params
162 expected_json: dict[str, object] = {'conditions': [],
165 self.check_json_get('/conditions', expected_json)
166 # test on meaningless non-empty params (incl. entirely un-used key)
167 expected_json = {'conditions': [],
168 'sort_by': 'title', # nonsense "foo" defaulting
169 'pattern': 'bar'} # preserved despite zero effect
170 self.check_json_get('/conditions?sort_by=foo&pattern=bar&foo=x',
172 # test non-empty result, automatic (positive) sorting by title
173 post_1 = {'title': 'foo', 'description': 'oof', 'is_active': False}
174 self.check_post(post_1, '/condition', 302, '/condition?id=1')
175 post_2 = {'title': 'bar', 'description': 'rab', 'is_active': False}
176 self.check_post(post_2, '/condition', 302, '/condition?id=2')
177 post_3 = {'title': 'baz', 'description': 'zab', 'is_active': True}
178 self.check_post(post_3, '/condition', 302, '/condition?id=3')
179 cond_1 = self.cond_as_dict(titles=['foo'], descriptions=['oof'])
180 cond_2 = self.cond_as_dict(2, titles=['bar'], descriptions=['rab'])
181 cond_3 = self.cond_as_dict(3, True, ['baz'], ['zab'])
182 cons = [cond_2, cond_3, cond_1]
183 expected_json = {'conditions': cons, 'sort_by': 'title', 'pattern': ''}
184 self.check_json_get('/conditions', expected_json)
185 # test other sortings
186 # (NB: by .is_active has two items of =False, their order currently
187 # is not explicitly made predictable, so mail fail until we do)
188 expected_json['conditions'] = [cond_1, cond_3, cond_2]
189 expected_json['sort_by'] = '-title'
190 self.check_json_get('/conditions?sort_by=-title', expected_json)
191 expected_json['conditions'] = [cond_1, cond_2, cond_3]
192 expected_json['sort_by'] = 'is_active'
193 self.check_json_get('/conditions?sort_by=is_active', expected_json)
194 expected_json['conditions'] = [cond_3, cond_1, cond_2]
195 expected_json['sort_by'] = '-is_active'
196 self.check_json_get('/conditions?sort_by=-is_active', expected_json)
197 # test pattern matching on title
198 expected_json = {'conditions': [cond_2, cond_3],
199 'sort_by': 'title', 'pattern': 'ba'}
200 self.check_json_get('/conditions?pattern=ba', expected_json)
201 # test pattern matching on description
202 expected_json['conditions'] = [cond_1]
203 expected_json['pattern'] = 'oo'
204 self.check_json_get('/conditions?pattern=oo', expected_json)