Make CnfVar lists comparable
[pyi2ncommon] / test / cnfvar / test_model.py
1 # The software in this package is distributed under the GNU General
2 # Public License version 2 (with a special exception described below).
3 #
4 # A copy of GNU General Public License (GPL) is included in this distribution,
5 # in the file COPYING.GPL.
6 #
7 # As a special exception, if other files instantiate templates or use macros
8 # or inline functions from this file, or you compile this file and link it
9 # with other works to produce a work based on this file, this file
10 # does not by itself cause the resulting work to be covered
11 # by the GNU General Public License.
12 #
13 # However the source code for this file must still be made available
14 # in accordance with section (3) of the GNU General Public License.
15 #
16 # This exception does not invalidate any other reasons why a work based
17 # on this file might be covered by the GNU General Public License.
18 #
19 # Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
20
21 """
22 test_model.py: unit tests for cnfvar/model.py.
23
24 Tests classes and functions in cnfvar/model.py
25
26 For help see :py:mod:`unittest`
27 """
28
29 import unittest
30 import json
31 from textwrap import dedent
32 from tempfile import NamedTemporaryFile
33 from copy import deepcopy
34
35 from src.cnfvar import CnfList, CnfDiff
36
37 CNF_TEST_DATA = dedent("""\
38     1 USER,1: "jake"
39     2    (1) USER_DISABLED,0: "0"
40     3    (1) USER_FULLNAME,0: "Jake"
41     4    (1) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
42     5    (1) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
43     6    (1) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
44     7    (1) USER_GROUP_MEMBER_REF,0: "100"
45     8    (1) USER_GROUP_MEMBER_REF,1: "2"
46     9    (1) USER_PASSWORD,0: "test1234"
47     11 USER,2: "jill"
48     12    (11) USER_DISABLED,0: "0"
49     13    (11) USER_FULLNAME,0: "Jill"
50     14    (11) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
51     15    (11) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
52     16    (11) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
53     17    (11) USER_GROUP_MEMBER_REF,0: "100"
54     18    (11) USER_GROUP_MEMBER_REF,1: "2"
55     19    (11) USER_PASSWORD,0: "test1234"
56     74 EMAILFILTER_BAN_FILTERLIST,0: "Vordefiniert: Alles verboten"
57     75    (74) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK"
58     76    (74) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: ""
59     77    (74) EMAILFILTER_BAN_FILTERLIST_FILTER_OFFICE_FILES,0: "BY_FILTERLIST"
60     78    (74) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: ""
61     79       (78) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "text/plain"
62     80       (78) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "text/html"
63     81    (74) EMAILFILTER_BAN_FILTERLIST_MODE,0: "ALLOW"
64     82    (74) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "1"
65     1196 UPDATE_EXPIRED,0: "0"
66     1197 UPDATE_URL_BASE,0: "https://update.intra2net.com/"
67     1198 UPDATE_VALIDATION_GROUP,0: "normal"
68     1199 UPS_LOCAL_KILLPOWER_ENABLE,0: "1"
69     """)
70
71
72 EXPECTED_CNF_DATA = dedent("""\
73     1 USER,1: "jake"
74     2   (1) USER_DISABLED,0: "0"
75     3   (1) USER_FULLNAME,0: "Jake"
76     4   (1) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
77     5   (1) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
78     6   (1) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
79     7   (1) USER_GROUP_MEMBER_REF,0: "100"
80     8   (1) USER_GROUP_MEMBER_REF,1: "2"
81     9   (1) USER_PASSWORD,0: "test1234"
82     10 USER,2: "jane"
83     11   (10) USER_DISABLED,0: "0"
84     12   (10) USER_FULLNAME,0: "Jane"
85     13   (10) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
86     14   (10) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
87     15   (10) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
88     16   (10) USER_GROUP_MEMBER_REF,0: "200"
89     17   (10) USER_GROUP_MEMBER_REF,1: "2"
90     18   (10) USER_PASSWORD,0: "test1234"
91     19   (10) USER_GROUP_MEMBER_REF,2: "5"
92     20 EMAILFILTER_BAN_FILTERLIST,0: "Vordefiniert: Alles verboten"
93     21   (20) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK"
94     22   (20) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: ""
95     23   (20) EMAILFILTER_BAN_FILTERLIST_FILTER_OFFICE_FILES,0: "BY_FILTERLIST"
96     24   (20) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: "" # hey this is a comment
97     25     (24) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "text/plain"
98     26     (24) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "text/html"
99     27   (20) EMAILFILTER_BAN_FILTERLIST_MODE,0: "ALLOW"
100     28   (20) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "1"
101     29 UPDATE_EXPIRED,0: "1"
102     30 UPDATE_URL_BASE,0: "https://update.intra2net.com/"
103     31 UPDATE_VALIDATION_GROUP,0: "normal"
104     32 UPS_LOCAL_KILLPOWER_ENABLE,0: "1"
105     """)
106
107
108 class TestModel(unittest.TestCase):
109     """Test the multiple capabilities of the CNF modules."""
110
111     def test_querying_and_serialization(self):
112         """Test deserializing, querying and serializing a CnfList."""
113         cnfs = CnfList.from_cnf_string(CNF_TEST_DATA)
114
115         self._modify(cnfs)
116
117         with NamedTemporaryFile() as tmpfile:
118             cnfs.to_cnf_file(tmpfile.name)
119
120             with open(tmpfile.name, "r", encoding="latin1") as f:
121                 contents = f.read()
122
123         self.assertEqual(contents.splitlines(), EXPECTED_CNF_DATA.splitlines())
124
125         # make sure the result can be parsed again
126         CnfList.from_cnf_string(str(contents))
127
128     def test_querying_and_serialization_json(self):
129         """Test deserializing, querying and serializing a CnfList as JSON."""
130         # first serialize to JSON without renumbering to keep the structure
131         cnfs = CnfList.from_cnf_string(CNF_TEST_DATA)
132
133         with NamedTemporaryFile() as tmpfile:
134             cnfs.to_json_file(tmpfile.name, renumber=False)
135
136             with open(tmpfile.name, "r") as f:
137                 contents = f.read()
138
139             # make sure the JSON structure is sane
140             json.loads(contents)
141
142         # now asserts that reading from JSON works
143         cnfs_2 = CnfList.from_json_string(contents)
144         self._modify(cnfs_2)
145         cnfs_2.renumber()
146
147         self.assertEqual(str(cnfs_2).splitlines(), EXPECTED_CNF_DATA.splitlines())
148
149         # make sure the result can be parsed again
150         CnfList.from_cnf_string(str(cnfs_2))
151
152     def _modify(self, cnfs):
153         """
154         Make test modifications on the list.
155
156         :param cnfs: list of cnfvars to modify
157         :type cnfs: :py:class:`CnfList`
158
159         .. note:: we unify this into a method so we can make sure that the test
160         using cnfvar strings and the test using JSON do the exactly same modifications.
161         """
162         self.assertFalse(cnfs.single_with_name("update_expired").is_enabled())
163         cnfs.single_with_name("update_expired") \
164             .enable()
165         self.assertTrue(cnfs.single_with_name("update_expired").is_enabled())
166
167         user_cnf = cnfs.first_with_value("jill")
168         user_cnf.value = "jane"
169         user_cnf.children[1].value = "Jane"
170         user_cnf.children.first_with_name("user_group_member_ref").value = "200"
171         user_cnf.add_children(("USER_GROUP_MEMBER_REF", 5, -1))
172
173         # check correct types and equality
174         self.assertEqual(user_cnf.instance, 2)
175         self.assertEqual(user_cnf.lineno, 11)
176
177         other_cnf = cnfs.single_with_value("Vordefiniert: Alles verboten")
178         other_cnf.children \
179                  .where(lambda c: c.name == "emailfilter_ban_filterlist_mimetypes") \
180                  .single() \
181                  .comment = "hey this is a comment"
182
183
184 class TestCompare(unittest.TestCase):
185     """Test compare() function of BaseCnfList."""
186
187     DATA_STR = dedent("""\
188         01 FRANCHISE,0: "Star Trek"
189         02     (01) SERIES,0: "The Original Series"
190         03         (02) SHIP,0: "Enterprise (original)"
191         04             (03) CAPTAIN,0: "Pike"
192         05             (03) CAPTAIN,1: "Kirk"
193         06     (01) SERIES,1: "The Animated Series"
194         07     (01) SERIES,1: "The Next Generation"
195         08         (07) SHIP,0: "Enterprise D"
196         09             (08) CAPTAIN,0: "Picard"
197         11             (08) CAPTAIN,1: "Riker"
198         12     (01) SERIES,2: "Deep Space Nine"
199         13     (01) SERIES,3: "Voyager"
200         14         (13) SHIP,0: "Voyager"
201         15     (01) SERIES,4: "Enterprise"
202         16         (15) SHIP,0: "Enterprise"
203         17             (16) CAPTAIN,0: "Archer"
204         18 FRANCHISE,1: "Star Wars"
205         19     (18) MOVIE,0: "A New Hope"
206         21         (19) EPISODE,0: "4"
207         22     (18) MOVIE,1: "The Empire Strikes Back"
208         23         (22) EPISODE,0: "5"
209         24     (18) MOVIE,2: "Return of the Jedi"
210         25         (24) EPISODE,0: "6"
211         26     (18) MOVIE,3: "The Phantom Menace"
212         27         (26) EPISODE,0: "1"
213         28     (18) MOVIE,4: "Attack of the Clones"
214         29         (28) EPISODE,0: "2"
215         31     (18) MOVIE,5: "Revenge of the Sith"
216         32         (31) EPISODE,0: "3"
217         33 FRANCHISE,2: "Battlestar Galactica"
218         34 FRANCHISE,3: "Firefly"
219         35 FRANCHISE,4: "Babylon 5"
220         36 FRANCHISE,5: "Star Gate"
221         """)
222
223     DATA: CnfList   # initialized in setUpClass
224
225     @classmethod
226     def setUpClass(cls) -> None:
227         """Called once before first test, converts above string to CnfList."""
228         cls.DATA = CnfList.from_cnf_string(cls.DATA_STR)
229
230     def test_equal(self):
231         """Compare data to itself."""
232         self.assertEqual(CnfDiff(), self.DATA.compare(self.DATA))
233
234     def test_remove(self):
235         """Remove entry from data, check it is found."""
236         other = deepcopy(self.DATA)
237         removed = other.single_with_value("Star Trek").children\
238             .single_with_value("The Original Series").children\
239             .single_with_value("Enterprise (original)").children \
240             .remove_where(lambda cnf: cnf.value == "Pike")
241         self.assertEqual(1, len(removed))
242         diff = self.DATA.compare(other)
243         self.assertEqual(1, len(diff))
244         diff_text = []
245         diff.print(output_func=lambda s: diff_text.append(s))
246         self.assertEqual(1, len(diff_text))
247         expect = "cnf diff: - captain (0) = 'Pike' " + \
248                  "in franchise=Star Trek > series=The Original Series > ship=Enterprise (original)"
249         self.assertEqual(expect, diff_text[0])
250
251     def test_add(self):
252         """Add entry to data, check it is found."""
253         other = deepcopy(self.DATA)
254         other.single_with_value("Star Wars").children.single_with_value("The Phantom Menace")\
255             .add_child("CHARACTER", "Jar Jar Binks")
256         diff = self.DATA.compare(other)
257         self.assertEqual(1, len(diff))
258         diff_text = []
259         diff.print(output_func=lambda s: diff_text.append(s))
260         self.assertEqual(1, len(diff_text))
261         expect = "cnf diff: + CHARACTER (0) = 'Jar Jar Binks' " + \
262                  "in franchise=Star Wars > movie=The Phantom Menace"
263         self.assertEqual(expect, diff_text[0])
264
265     def test_change(self):
266         """Change entry in data, check it is found."""
267         other = deepcopy(self.DATA)
268         other.single_with_value("Star Trek").children \
269             .single_with_value("The Next Generation").children \
270             .single_with_value("Enterprise D").children \
271             .single_with_value("Picard").value = "Jean-Luc"
272         diff = self.DATA.compare(other)
273         self.assertEqual(2, len(diff))
274         diff_text = []
275         diff.print(output_func=lambda s: diff_text.append(s))
276         self.assertEqual(2, len(diff_text))
277         expect = [
278             "cnf diff: - captain (0) = 'Picard' in franchise=Star Trek > "
279                 "series=The Next Generation > ship=Enterprise D",
280             "cnf diff: + captain (0) = 'Jean-Luc' in franchise=Star Trek > "
281                 "series=The Next Generation > ship=Enterprise D",
282         ]
283         self.assertEqual(expect, diff_text)
284
285     def test_ignore(self):
286         """Check that changes on ignore_list are ignored."""
287         other = deepcopy(self.DATA)
288         # same change as in test_change
289         other.single_with_value("Star Trek").children \
290             .single_with_value("The Next Generation").children \
291             .single_with_value("Enterprise D").children \
292             .single_with_value("Picard").value = "Jean-Luc"
293         diff = self.DATA.compare(other, ignore_list=["CAPTAIN",])
294         self.assertEqual(0, len(diff))
295
296
297 if __name__ == '__main__':
298     unittest.main()