Make CnfVar lists comparable
[pyi2ncommon] / test / cnfvar / test_model.py
CommitLineData
d31714a0
SA
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"""
22test_model.py: unit tests for cnfvar/model.py.
23
24Tests classes and functions in cnfvar/model.py
25
26For help see :py:mod:`unittest`
27"""
28
29import unittest
30import json
31from textwrap import dedent
32from tempfile import NamedTemporaryFile
b621d44e 33from copy import deepcopy
d31714a0 34
b621d44e 35from src.cnfvar import CnfList, CnfDiff
d31714a0
SA
36
37CNF_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
72EXPECTED_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
108class 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"
c89cc126 171 user_cnf.add_children(("USER_GROUP_MEMBER_REF", 5, -1))
d31714a0
SA
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
b621d44e
CH
184class 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
d31714a0
SA
297if __name__ == '__main__':
298 unittest.main()