home · contact · privacy
Make use of new JSON interface for GET /conditions testing.
[plomtask] / tests / conditions.py
1 """Test Conditions module."""
2 from json import loads as json_loads
3 from tests.utils import TestCaseWithDB, TestCaseWithServer, TestCaseSansDB
4 from plomtask.conditions import Condition
5 from plomtask.processes import Process
6 from plomtask.todos import Todo
7 from plomtask.exceptions import HandledException
8
9
10 class TestsSansDB(TestCaseSansDB):
11     """Tests requiring no DB setup."""
12     checked_class = Condition
13     do_id_test = True
14     versioned_defaults_to_test = {'title': 'UNNAMED', 'description': ''}
15
16
17 class TestsWithDB(TestCaseWithDB):
18     """Tests requiring DB, but not server setup."""
19     checked_class = Condition
20     default_init_kwargs = {'is_active': False}
21     test_versioneds = {'title': str, 'description': str}
22
23     def test_Condition_from_table_row(self) -> None:
24         """Test .from_table_row() properly reads in class from DB"""
25         self.check_from_table_row()
26         self.check_versioned_from_table_row('title', str)
27         self.check_versioned_from_table_row('description', str)
28
29     def test_Condition_by_id(self) -> None:
30         """Test .by_id(), including creation."""
31         self.check_by_id()
32
33     def test_Condition_all(self) -> None:
34         """Test .all()."""
35         self.check_all()
36
37     def test_Condition_singularity(self) -> None:
38         """Test pointers made for single object keep pointing to it."""
39         self.check_singularity('is_active', True)
40
41     def test_Condition_versioned_attributes_singularity(self) -> None:
42         """Test behavior of VersionedAttributes on saving (with .title)."""
43         self.check_versioned_singularity()
44
45     def test_Condition_remove(self) -> None:
46         """Test .remove() effects on DB and cache."""
47         self.check_remove()
48         proc = Process(None)
49         proc.save(self.db_conn)
50         todo = Todo(None, proc, False, '2024-01-01')
51         for depender in (proc, todo):
52             assert hasattr(depender, 'save')
53             assert hasattr(depender, 'set_conditions')
54             c = Condition(None)
55             c.save(self.db_conn)
56             depender.save(self.db_conn)
57             depender.set_conditions(self.db_conn, [c.id_], 'conditions')
58             depender.save(self.db_conn)
59             with self.assertRaises(HandledException):
60                 c.remove(self.db_conn)
61             depender.set_conditions(self.db_conn, [], 'conditions')
62             depender.save(self.db_conn)
63             c.remove(self.db_conn)
64
65
66 class TestsWithServer(TestCaseWithServer):
67     """Module tests against our HTTP server/handler (and database)."""
68
69     def test_do_POST_condition(self) -> None:
70         """Test POST /condition and its effect on the database."""
71         form_data = {'title': 'foo', 'description': 'foo', 'is_active': False}
72         self.check_post(form_data, '/condition', 302, '/condition?id=1')
73         self.assertEqual(1, len(Condition.all(self.db_conn)))
74         form_data['delete'] = ''
75         self.check_post(form_data, '/condition?id=', 404)
76         self.check_post(form_data, '/condition?id=2', 404)
77         self.check_post(form_data, '/condition?id=1', 302, '/conditions')
78         self.assertEqual(0, len(Condition.all(self.db_conn)))
79
80     def test_do_GET_condition(self) -> None:
81         """Test GET /condition."""
82         form_data = {'title': 'foo', 'description': 'foo', 'is_active': False}
83         self.check_post(form_data, '/condition', 302, '/condition?id=1')
84         self.check_get_defaults('/condition')
85
86     def test_do_GET_conditions(self) -> None:
87         """Test GET /conditions."""
88
89         def check(params: str, expected_json: dict[str, object]) -> None:
90             self.conn.request('GET', f'/conditions{params}')
91             response = self.conn.getresponse()
92             self.assertEqual(response.status, 200)
93             retrieved_json = json_loads(response.read().decode())
94             # ignore history timestamps (too difficult too anticipate)
95             for cond in retrieved_json['conditions']:
96                 for k in [k for k in cond.keys() if isinstance(cond[k], dict)]:
97                     history = cond[k]['history']
98                     history = {'[IGNORE]': list(history.values())[0]}
99                     cond[k]['history'] = history
100             self.assertEqual(expected_json, retrieved_json)
101
102         # test empty result on empty DB, default-settings on empty params
103         expected_json: dict[str, object] = {'conditions': [],
104                                             'sort_by': 'title',
105                                             'pattern': ''}
106         check('', expected_json)
107         # test on meaningless non-empty params (incl. entirely un-used key)
108         expected_json = {'conditions': [],
109                          'sort_by': 'title',  # nonsense "foo" defaulting
110                          'pattern': 'bar'}  # preserved despite zero effect
111         check('?sort_by=foo&pattern=bar&foo=x', expected_json)
112         # test non-empty result, automatic (positive) sorting by title
113         post_1 = {'title': 'foo', 'description': 'oof', 'is_active': False}
114         self.check_post(post_1, '/condition', 302, '/condition?id=1')
115         post_2 = {'title': 'bar', 'description': 'rab', 'is_active': False}
116         self.check_post(post_2, '/condition', 302, '/condition?id=2')
117         post_3 = {'title': 'baz', 'description': 'zab', 'is_active': True}
118         self.check_post(post_3, '/condition', 302, '/condition?id=3')
119         cond_1 = {'id': 1, 'is_active': False,
120                   'title': {'history': {'[IGNORE]': 'foo'},
121                             'parent_id': 1},
122                            'description': {'history': {'[IGNORE]': 'oof'},
123                                            'parent_id': 1}}
124         cond_2 = {'id': 2, 'is_active': False,
125                   'title': {'history': {'[IGNORE]': 'bar'},
126                             'parent_id': 2},
127                   'description': {'history': {'[IGNORE]': 'rab'},
128                                   'parent_id': 2}}
129         cond_3 = {'id': 3, 'is_active': True,
130                   'title': {'history': {'[IGNORE]': 'baz'},
131                             'parent_id': 3},
132                   'description': {'history': {'[IGNORE]': 'zab'},
133                                   'parent_id': 3}}
134         cons = [cond_2, cond_3, cond_1]
135         expected_json = {'conditions': cons, 'sort_by': 'title', 'pattern': ''}
136         check('', expected_json)
137         # test other sortings
138         # (NB: by .is_active has two items of =False, their order currently
139         # is not explicitly made predictable, so mail fail until we do)
140         expected_json['conditions'] = [cond_1, cond_3, cond_2]
141         expected_json['sort_by'] = '-title'
142         check('?sort_by=-title', expected_json)
143         expected_json['conditions'] = [cond_1, cond_2, cond_3]
144         expected_json['sort_by'] = 'is_active'
145         check('?sort_by=is_active', expected_json)
146         expected_json['conditions'] = [cond_3, cond_1, cond_2]
147         expected_json['sort_by'] = '-is_active'
148         check('?sort_by=-is_active', expected_json)
149         # test pattern matching on title
150         expected_json = {'conditions': [cond_2, cond_3],
151                          'sort_by': 'title', 'pattern': 'ba'}
152         check('?pattern=ba', expected_json)
153         # test pattern matching on description
154         expected_json['conditions'] = [cond_1]
155         expected_json['pattern'] = 'oo'
156         check('?pattern=oo', expected_json)