blob: 133cb058d8b81dc2233034fe6eb3b34d36858cb4 [file] [log] [blame]
Eduardo Sousaa0117812019-02-05 15:57:09 +00001# Copyright 2018 Whitestack, LLC
2# Copyright 2018 Telefonica S.A.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16# For those usages not covered by the Apache License, Version 2.0 please
17# contact: esousa@whitestack.com or alfonso.tiernosepulveda@telefonica.com
18##
19
Eduardo Sousa4d611d32018-05-09 19:20:37 +010020import http
21import pytest
tiernob3e750b2018-09-05 11:25:23 +020022import unittest
23from osm_common.dbbase import DbBase, DbException, deep_update
tierno136f2952018-10-19 13:01:03 +020024from os import urandom
tiernobd5a4022019-01-30 09:48:38 +000025from http import HTTPStatus
Eduardo Sousa4d611d32018-05-09 19:20:37 +010026
tiernob20a9022018-05-22 12:07:05 +020027
Eduardo Sousa4d611d32018-05-09 19:20:37 +010028def exception_message(message):
29 return "database exception " + message
30
tiernob20a9022018-05-22 12:07:05 +020031
Eduardo Sousa4d611d32018-05-09 19:20:37 +010032@pytest.fixture
33def db_base():
34 return DbBase()
35
tiernob20a9022018-05-22 12:07:05 +020036
Eduardo Sousa4d611d32018-05-09 19:20:37 +010037def test_constructor():
38 db_base = DbBase()
tiernob20a9022018-05-22 12:07:05 +020039 assert db_base is not None
Eduardo Sousa4d611d32018-05-09 19:20:37 +010040 assert isinstance(db_base, DbBase)
41
tiernob20a9022018-05-22 12:07:05 +020042
Eduardo Sousa4d611d32018-05-09 19:20:37 +010043def test_db_connect(db_base):
tierno136f2952018-10-19 13:01:03 +020044 with pytest.raises(DbException) as excinfo:
45 db_base.db_connect(None)
garciadeblas2644b762021-03-24 09:21:01 +010046 assert str(excinfo.value).startswith(
47 exception_message("Method 'db_connect' not implemented")
48 )
Eduardo Sousa4d611d32018-05-09 19:20:37 +010049
tiernob20a9022018-05-22 12:07:05 +020050
Eduardo Sousa4d611d32018-05-09 19:20:37 +010051def test_db_disconnect(db_base):
52 db_base.db_disconnect()
53
tiernob20a9022018-05-22 12:07:05 +020054
Eduardo Sousa4d611d32018-05-09 19:20:37 +010055def test_get_list(db_base):
56 with pytest.raises(DbException) as excinfo:
57 db_base.get_list(None, None)
garciadeblas2644b762021-03-24 09:21:01 +010058 assert str(excinfo.value).startswith(
59 exception_message("Method 'get_list' not implemented")
60 )
Eduardo Sousa4d611d32018-05-09 19:20:37 +010061 assert excinfo.value.http_code == http.HTTPStatus.NOT_FOUND
62
tiernob20a9022018-05-22 12:07:05 +020063
Eduardo Sousa4d611d32018-05-09 19:20:37 +010064def test_get_one(db_base):
65 with pytest.raises(DbException) as excinfo:
66 db_base.get_one(None, None, None, None)
garciadeblas2644b762021-03-24 09:21:01 +010067 assert str(excinfo.value).startswith(
68 exception_message("Method 'get_one' not implemented")
69 )
Eduardo Sousa4d611d32018-05-09 19:20:37 +010070 assert excinfo.value.http_code == http.HTTPStatus.NOT_FOUND
71
tiernob20a9022018-05-22 12:07:05 +020072
Eduardo Sousa4d611d32018-05-09 19:20:37 +010073def test_create(db_base):
74 with pytest.raises(DbException) as excinfo:
75 db_base.create(None, None)
garciadeblas2644b762021-03-24 09:21:01 +010076 assert str(excinfo.value).startswith(
77 exception_message("Method 'create' not implemented")
78 )
Eduardo Sousa4d611d32018-05-09 19:20:37 +010079 assert excinfo.value.http_code == http.HTTPStatus.NOT_FOUND
80
tiernob20a9022018-05-22 12:07:05 +020081
tierno2c9794c2020-04-29 10:24:28 +000082def test_create_list(db_base):
83 with pytest.raises(DbException) as excinfo:
84 db_base.create_list(None, None)
garciadeblas2644b762021-03-24 09:21:01 +010085 assert str(excinfo.value).startswith(
86 exception_message("Method 'create_list' not implemented")
87 )
tierno2c9794c2020-04-29 10:24:28 +000088 assert excinfo.value.http_code == http.HTTPStatus.NOT_FOUND
89
90
Eduardo Sousa4d611d32018-05-09 19:20:37 +010091def test_del_list(db_base):
92 with pytest.raises(DbException) as excinfo:
93 db_base.del_list(None, None)
garciadeblas2644b762021-03-24 09:21:01 +010094 assert str(excinfo.value).startswith(
95 exception_message("Method 'del_list' not implemented")
96 )
Eduardo Sousa4d611d32018-05-09 19:20:37 +010097 assert excinfo.value.http_code == http.HTTPStatus.NOT_FOUND
98
tiernob20a9022018-05-22 12:07:05 +020099
Eduardo Sousa4d611d32018-05-09 19:20:37 +0100100def test_del_one(db_base):
101 with pytest.raises(DbException) as excinfo:
102 db_base.del_one(None, None, None)
garciadeblas2644b762021-03-24 09:21:01 +0100103 assert str(excinfo.value).startswith(
104 exception_message("Method 'del_one' not implemented")
105 )
Eduardo Sousa4d611d32018-05-09 19:20:37 +0100106 assert excinfo.value.http_code == http.HTTPStatus.NOT_FOUND
tiernob3e750b2018-09-05 11:25:23 +0200107
108
tierno136f2952018-10-19 13:01:03 +0200109class TestEncryption(unittest.TestCase):
110 def setUp(self):
tiernoeef7cb72018-11-12 11:51:49 +0100111 master_key = "Setting a long master key with numbers 123 and capitals AGHBNHD and symbols %&8)!'"
tiernocfc52722018-10-23 11:41:49 +0200112 db_base1 = DbBase()
tierno136f2952018-10-19 13:01:03 +0200113 db_base2 = DbBase()
tiernoeef7cb72018-11-12 11:51:49 +0100114 db_base3 = DbBase()
tierno136f2952018-10-19 13:01:03 +0200115 # set self.secret_key obtained when connect
tiernoeef7cb72018-11-12 11:51:49 +0100116 db_base1.set_secret_key(master_key, replace=True)
117 db_base1.set_secret_key(urandom(32))
118 db_base2.set_secret_key(None, replace=True)
119 db_base2.set_secret_key(urandom(30))
120 db_base3.set_secret_key(master_key)
121 self.db_bases = [db_base1, db_base2, db_base3]
tierno136f2952018-10-19 13:01:03 +0200122
123 def test_encrypt_decrypt(self):
124 TEST = (
125 ("plain text 1 ! ", None),
126 ("plain text 2 with salt ! ", "1afd5d1a-4a7e-4d9c-8c65-251290183106"),
tierno136f2952018-10-19 13:01:03 +0200127 )
tiernoeef7cb72018-11-12 11:51:49 +0100128 for db_base in self.db_bases:
tierno136f2952018-10-19 13:01:03 +0200129 for value, salt in TEST:
130 # no encryption
garciadeblas2644b762021-03-24 09:21:01 +0100131 encrypted = db_base.encrypt(value, schema_version="1.0", salt=salt)
132 self.assertEqual(
133 encrypted, value, "value '{}' has been encrypted".format(value)
134 )
135 decrypted = db_base.decrypt(encrypted, schema_version="1.0", salt=salt)
136 self.assertEqual(
137 decrypted, value, "value '{}' has been decrypted".format(value)
138 )
tierno136f2952018-10-19 13:01:03 +0200139
140 # encrypt/decrypt
garciadeblas2644b762021-03-24 09:21:01 +0100141 encrypted = db_base.encrypt(value, schema_version="1.1", salt=salt)
142 self.assertNotEqual(
143 encrypted, value, "value '{}' has not been encrypted".format(value)
144 )
tierno136f2952018-10-19 13:01:03 +0200145 self.assertIsInstance(encrypted, str, "Encrypted is not ascii text")
garciadeblas2644b762021-03-24 09:21:01 +0100146 decrypted = db_base.decrypt(encrypted, schema_version="1.1", salt=salt)
147 self.assertEqual(
148 decrypted, value, "value is not equal after encryption/decryption"
149 )
tierno136f2952018-10-19 13:01:03 +0200150
151 def test_encrypt_decrypt_salt(self):
152 value = "value to be encrypted!"
153 encrypted = []
tiernoeef7cb72018-11-12 11:51:49 +0100154 for db_base in self.db_bases:
tierno136f2952018-10-19 13:01:03 +0200155 for salt in (None, "salt 1", "1afd5d1a-4a7e-4d9c-8c65-251290183106"):
156 # encrypt/decrypt
garciadeblas2644b762021-03-24 09:21:01 +0100157 encrypted.append(
158 db_base.encrypt(value, schema_version="1.1", salt=salt)
159 )
160 self.assertNotEqual(
161 encrypted[-1],
162 value,
163 "value '{}' has not been encrypted".format(value),
164 )
tierno136f2952018-10-19 13:01:03 +0200165 self.assertIsInstance(encrypted[-1], str, "Encrypted is not ascii text")
garciadeblas2644b762021-03-24 09:21:01 +0100166 decrypted = db_base.decrypt(
167 encrypted[-1], schema_version="1.1", salt=salt
168 )
169 self.assertEqual(
170 decrypted, value, "value is not equal after encryption/decryption"
171 )
tierno136f2952018-10-19 13:01:03 +0200172 for i in range(0, len(encrypted)):
garciadeblas2644b762021-03-24 09:21:01 +0100173 for j in range(i + 1, len(encrypted)):
174 self.assertNotEqual(
175 encrypted[i],
176 encrypted[j],
177 "encryption with different salt must contain different result",
178 )
tiernobd5a4022019-01-30 09:48:38 +0000179 # decrypt with a different master key
180 try:
garciadeblas2644b762021-03-24 09:21:01 +0100181 decrypted = self.db_bases[-1].decrypt(
182 encrypted[0], schema_version="1.1", salt=None
183 )
184 self.assertNotEqual(
185 encrypted[0],
186 decrypted,
187 "Decryption with different KEY must generate different result",
188 )
tiernobd5a4022019-01-30 09:48:38 +0000189 except DbException as e:
garciadeblas2644b762021-03-24 09:21:01 +0100190 self.assertEqual(
191 e.http_code,
192 HTTPStatus.INTERNAL_SERVER_ERROR,
193 "Decryption with different KEY does not provide expected http_code",
194 )
tierno136f2952018-10-19 13:01:03 +0200195
196
tiernob3e750b2018-09-05 11:25:23 +0200197class TestDeepUpdate(unittest.TestCase):
198 def test_update_dict(self):
199 # Original, patch, expected result
200 TEST = (
201 ({"a": "b"}, {"a": "c"}, {"a": "c"}),
202 ({"a": "b"}, {"b": "c"}, {"a": "b", "b": "c"}),
203 ({"a": "b"}, {"a": None}, {}),
204 ({"a": "b", "b": "c"}, {"a": None}, {"b": "c"}),
205 ({"a": ["b"]}, {"a": "c"}, {"a": "c"}),
206 ({"a": "c"}, {"a": ["b"]}, {"a": ["b"]}),
207 ({"a": {"b": "c"}}, {"a": {"b": "d", "c": None}}, {"a": {"b": "d"}}),
208 ({"a": [{"b": "c"}]}, {"a": [1]}, {"a": [1]}),
209 ({1: ["a", "b"]}, {1: ["c", "d"]}, {1: ["c", "d"]}),
210 ({1: {"a": "b"}}, {1: ["c"]}, {1: ["c"]}),
211 ({1: {"a": "foo"}}, {1: None}, {}),
212 ({1: {"a": "foo"}}, {1: "bar"}, {1: "bar"}),
213 ({"e": None}, {"a": 1}, {"e": None, "a": 1}),
214 ({1: [1, 2]}, {1: {"a": "b", "c": None}}, {1: {"a": "b"}}),
215 ({}, {"a": {"bb": {"ccc": None}}}, {"a": {"bb": {}}}),
216 )
217 for t in TEST:
218 deep_update(t[0], t[1])
219 self.assertEqual(t[0], t[2])
220 # test deepcopy is done. So that original dictionary does not reference the pach
221 test_original = {1: {"a": "b"}}
222 test_patch = {1: {"c": {"d": "e"}}}
223 test_result = {1: {"a": "b", "c": {"d": "e"}}}
224 deep_update(test_original, test_patch)
225 self.assertEqual(test_original, test_result)
226 test_patch[1]["c"]["f"] = "edition of patch, must not modify original"
227 self.assertEqual(test_original, test_result)
228
229 def test_update_array(self):
230 # This TEST contains a list with the the Original, patch, and expected result
231 TEST = (
232 # delete all instances of "a"/"d"
233 ({"A": ["a", "b", "a"]}, {"A": {"$a": None}}, {"A": ["b"]}),
234 ({"A": ["a", "b", "a"]}, {"A": {"$d": None}}, {"A": ["a", "b", "a"]}),
235 # delete and insert at 0
garciadeblas2644b762021-03-24 09:21:01 +0100236 (
237 {"A": ["a", "b", "c"]},
238 {"A": {"$b": None, "$+[0]": "b"}},
239 {"A": ["b", "a", "c"]},
240 ),
tiernob3e750b2018-09-05 11:25:23 +0200241 # delete and edit
garciadeblas2644b762021-03-24 09:21:01 +0100242 (
243 {"A": ["a", "b", "a"]},
244 {"A": {"$a": None, "$[1]": {"c": "d"}}},
245 {"A": [{"c": "d"}]},
246 ),
tiernob3e750b2018-09-05 11:25:23 +0200247 # insert if not exist
248 ({"A": ["a", "b", "c"]}, {"A": {"$+b": "b"}}, {"A": ["a", "b", "c"]}),
249 ({"A": ["a", "b", "c"]}, {"A": {"$+d": "f"}}, {"A": ["a", "b", "c", "f"]}),
250 # edit by filter
garciadeblas2644b762021-03-24 09:21:01 +0100251 (
252 {"A": ["a", "b", "a"]},
253 {"A": {"$b": {"c": "d"}}},
254 {"A": ["a", {"c": "d"}, "a"]},
255 ),
256 (
257 {"A": ["a", "b", "a"]},
258 {"A": {"$b": None, "$+[0]": "b", "$+": "c"}},
259 {"A": ["b", "a", "a", "c"]},
260 ),
tiernob3e750b2018-09-05 11:25:23 +0200261 ({"A": ["a", "b", "a"]}, {"A": {"$c": None}}, {"A": ["a", "b", "a"]}),
262 # index deletion out of range
263 ({"A": ["a", "b", "a"]}, {"A": {"$[5]": None}}, {"A": ["a", "b", "a"]}),
264 # nested array->dict
garciadeblas2644b762021-03-24 09:21:01 +0100265 (
266 {"A": ["a", "b", {"id": "1", "c": {"d": 2}}]},
267 {"A": {"$id: '1'": {"h": None, "c": {"d": "e", "f": "g"}}}},
268 {"A": ["a", "b", {"id": "1", "c": {"d": "e", "f": "g"}}]},
269 ),
270 (
271 {"A": [{"id": 1, "c": {"d": 2}}, {"id": 1, "c": {"f": []}}]},
272 {"A": {"$id: 1": {"h": None, "c": {"d": "e", "f": "g"}}}},
273 {
274 "A": [
275 {"id": 1, "c": {"d": "e", "f": "g"}},
276 {"id": 1, "c": {"d": "e", "f": "g"}},
277 ]
278 },
279 ),
tiernob3e750b2018-09-05 11:25:23 +0200280 # nested array->array
garciadeblas2644b762021-03-24 09:21:01 +0100281 (
282 {"A": ["a", "b", ["a", "b"]]},
283 {"A": {"$b": None, "$[2]": {"$b": {}, "$+": "c"}}},
284 {"A": ["a", ["a", {}, "c"]]},
285 ),
tiernob3e750b2018-09-05 11:25:23 +0200286 # types str and int different, so not found
garciadeblas2644b762021-03-24 09:21:01 +0100287 (
288 {"A": ["a", {"id": "1", "c": "d"}]},
289 {"A": {"$id: 1": {"c": "e"}}},
290 {"A": ["a", {"id": "1", "c": "d"}]},
291 ),
tiernob3e750b2018-09-05 11:25:23 +0200292 )
293 for t in TEST:
294 print(t)
295 deep_update(t[0], t[1])
296 self.assertEqual(t[0], t[2])
297
298 def test_update_badformat(self):
299 # This TEST contains original, incorrect patch and #TODO text that must be present
300 TEST = (
301 # conflict, index 0 is edited twice
302 ({"A": ["a", "b", "a"]}, {"A": {"$a": None, "$[0]": {"c": "d"}}}),
303 # conflict, two insertions at same index
304 ({"A": ["a", "b", "a"]}, {"A": {"$[1]": "c", "$[-2]": "d"}}),
305 ({"A": ["a", "b", "a"]}, {"A": {"$[1]": "c", "$[+1]": "d"}}),
306 # bad format keys with and without $
307 ({"A": ["a", "b", "a"]}, {"A": {"$b": {"c": "d"}, "c": 3}}),
308 # bad format empty $ and yaml incorrect
309 ({"A": ["a", "b", "a"]}, {"A": {"$": 3}}),
310 ({"A": ["a", "b", "a"]}, {"A": {"$a: b: c": 3}}),
311 ({"A": ["a", "b", "a"]}, {"A": {"$a: b, c: d": 3}}),
312 # insertion of None
313 ({"A": ["a", "b", "a"]}, {"A": {"$+": None}}),
314 # Not found, insertion of None
315 ({"A": ["a", "b", "a"]}, {"A": {"$+c": None}}),
316 # index edition out of range
317 ({"A": ["a", "b", "a"]}, {"A": {"$[5]": 6}}),
318 # conflict, two editions on index 2
garciadeblas2644b762021-03-24 09:21:01 +0100319 (
320 {"A": ["a", {"id": "1", "c": "d"}]},
321 {"A": {"$id: '1'": {"c": "e"}, "$c: d": {"c": "f"}}},
322 ),
tiernob3e750b2018-09-05 11:25:23 +0200323 )
324 for t in TEST:
325 print(t)
326 self.assertRaises(DbException, deep_update, t[0], t[1])
327 try:
328 deep_update(t[0], t[1])
329 except DbException as e:
330 print(e)
tierno136f2952018-10-19 13:01:03 +0200331
332
garciadeblas2644b762021-03-24 09:21:01 +0100333if __name__ == "__main__":
tierno136f2952018-10-19 13:01:03 +0200334 unittest.main()