Implement regular expression support for key searching
[ric-plt/sdlpy.git] / ricsdl-package / tests / test_syncstorage.py
1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2019 Nokia.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain 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,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 #
17 # This source code is part of the near-RT RIC (RAN Intelligent Controller)
18 # platform project (RICP).
19 #
20
21
22 from unittest.mock import patch, Mock
23 import pytest
24 from ricsdl.syncstorage import SyncStorage
25 from ricsdl.syncstorage import SyncLock
26 from ricsdl.syncstorage import func_arg_checker
27 from ricsdl.exceptions import SdlTypeError
28
29
30 @pytest.fixture()
31 def sync_storage_fixture(request):
32     request.cls.ns = 'some-ns'
33     request.cls.key = 'a'
34     request.cls.keys = {'a', 'b'}
35     request.cls.dm = {'b': b'2', 'a': b'1'}
36     request.cls.old_data = b'1'
37     request.cls.new_data = b'3'
38     request.cls.keyprefix = 'x'
39     request.cls.matchedkeys = ['x1', 'x2', 'x3', 'x4', 'x5']
40     request.cls.group = 'some-group'
41     request.cls.groupmembers = set([b'm1', b'm2'])
42     request.cls.groupmember = b'm1'
43     request.cls.lock_name = 'some-lock-name'
44     request.cls.lock_int_expiration = 10
45     request.cls.lock_float_expiration = 1.1
46
47     with patch('ricsdl.backend.get_backend_instance') as mock_db_backend:
48         storage = SyncStorage()
49         request.cls.mock_db_backend = mock_db_backend.return_value
50     request.cls.storage = storage
51     yield
52
53
54 @pytest.mark.usefixtures('sync_storage_fixture')
55 class TestSyncStorage:
56     def test_set_function_success(self):
57         self.storage.set(self.ns, self.dm)
58         self.mock_db_backend.set.assert_called_once_with(self.ns, self.dm)
59
60     def test_set_function_can_raise_exception_for_wrong_argument(self):
61         with pytest.raises(SdlTypeError):
62             self.storage.set(123, {'a': b'v1'})
63         with pytest.raises(SdlTypeError):
64             self.storage.set('ns', [1, 2])
65
66     def test_set_if_function_success(self):
67         self.mock_db_backend.set_if.return_value = True
68         ret = self.storage.set_if(self.ns, self.key, self.old_data, self.new_data)
69         self.mock_db_backend.set_if.assert_called_once_with(self.ns, self.key, self.old_data,
70                                                             self.new_data)
71         assert ret is True
72
73     def test_set_if_function_can_return_false_if_same_data_already_exists(self):
74         self.mock_db_backend.set_if.return_value = False
75         ret = self.storage.set_if(self.ns, self.key, self.old_data, self.new_data)
76         self.mock_db_backend.set_if.assert_called_once_with(self.ns, self.key, self.old_data,
77                                                             self.new_data)
78         assert ret is False
79
80     def test_set_if_function_can_raise_exception_for_wrong_argument(self):
81         with pytest.raises(SdlTypeError):
82             self.storage.set_if(0xbad, 'key', b'v1', b'v2')
83         with pytest.raises(SdlTypeError):
84             self.storage.set_if('ns', 0xbad, b'v1', b'v2')
85         with pytest.raises(SdlTypeError):
86             self.storage.set_if('ns', 'key', 0xbad, b'v2')
87         with pytest.raises(SdlTypeError):
88             self.storage.set_if('ns', 'key', b'v1', 0xbad)
89
90     def test_set_if_not_exists_function_success(self):
91         self.mock_db_backend.set_if_not_exists.return_value = True
92         ret = self.storage.set_if_not_exists(self.ns, self.key, self.new_data)
93         self.mock_db_backend.set_if_not_exists.assert_called_once_with(self.ns, self.key,
94                                                                        self.new_data)
95         assert ret is True
96
97     def test_set_if_not_exists_function_can_return_false_if_key_already_exists(self):
98         self.mock_db_backend.set_if_not_exists.return_value = False
99         ret = self.storage.set_if_not_exists(self.ns, self.key, self.new_data)
100         self.mock_db_backend.set_if_not_exists.assert_called_once_with(self.ns, self.key,
101                                                                        self.new_data)
102         assert ret is False
103
104     def test_set_if_not_exists_function_can_raise_exception_for_wrong_argument(self):
105         with pytest.raises(SdlTypeError):
106             self.storage.set_if_not_exists(0xbad, 'key', b'v1')
107         with pytest.raises(SdlTypeError):
108             self.storage.set_if_not_exists('ns', 0xbad, b'v1')
109         with pytest.raises(SdlTypeError):
110             self.storage.set_if_not_exists('ns', 'key', 0xbad)
111
112     def test_get_function_success(self):
113         self.mock_db_backend.get.return_value = self.dm
114         ret = self.storage.get(self.ns, self.keys)
115         self.mock_db_backend.get.assert_called_once()
116         call_args, _ = self.mock_db_backend.get.call_args
117         assert call_args[0] == self.ns
118         assert len(call_args[1]) == len(self.keys)
119         assert all(k in call_args[1] for k in self.keys)
120         assert ret == self.dm
121         # Validate that SDL returns a dictionary with keys in alphabetical order
122         assert sorted(self.dm)[0] == list(ret.keys())[0]
123
124     def test_get_function_can_return_empty_dict_when_no_key_values_exist(self):
125         self.mock_db_backend.get.return_value = dict()
126         ret = self.storage.get(self.ns, self.keys)
127         self.mock_db_backend.get.assert_called_once()
128         call_args, _ = self.mock_db_backend.get.call_args
129         assert call_args[0] == self.ns
130         assert len(call_args[1]) == len(self.keys)
131         assert all(k in call_args[1] for k in self.keys)
132         assert ret == dict()
133
134     def test_get_function_can_raise_exception_for_wrong_argument(self):
135         with pytest.raises(SdlTypeError):
136             self.storage.get(0xbad, self.key)
137         with pytest.raises(SdlTypeError):
138             self.storage.get(self.ns, 0xbad)
139
140     def test_find_keys_function_success(self):
141         self.mock_db_backend.find_keys.return_value = self.matchedkeys
142         ret = self.storage.find_keys(self.ns, self.keyprefix)
143         self.mock_db_backend.find_keys.assert_called_once_with(self.ns, self.keyprefix)
144         assert ret == self.matchedkeys
145
146     def test_find_keys_function_can_return_empty_list_when_no_keys_exist(self):
147         self.mock_db_backend.find_keys.return_value = list()
148         ret = self.storage.find_keys(self.ns, self.keyprefix)
149         self.mock_db_backend.find_keys.assert_called_once_with(self.ns, self.keyprefix)
150         assert ret == list()
151
152     def test_find_keys_function_can_raise_exception_for_wrong_argument(self):
153         with pytest.raises(SdlTypeError):
154             self.storage.find_keys(0xbad, self.keyprefix)
155         with pytest.raises(SdlTypeError):
156             self.storage.find_keys(self.ns, 0xbad)
157
158     def test_find_and_get_function_success(self):
159         self.mock_db_backend.find_and_get.return_value = self.dm
160         ret = self.storage.find_and_get(self.ns, self.keyprefix)
161         self.mock_db_backend.find_and_get.assert_called_once_with(self.ns, self.keyprefix)
162         assert ret == self.dm
163         # Validate that SDL returns a dictionary with keys in alphabetical order
164         assert sorted(self.dm)[0] == list(ret.keys())[0]
165
166     def test_find_and_get_function_can_return_empty_dict_when_no_keys_exist(self):
167         self.mock_db_backend.find_and_get.return_value = dict()
168         ret = self.storage.find_and_get(self.ns, self.keyprefix)
169         self.mock_db_backend.find_and_get.assert_called_once_with(self.ns, self.keyprefix)
170         assert ret == dict()
171
172     def test_find_and_get_function_can_raise_exception_for_wrong_argument(self):
173         with pytest.raises(SdlTypeError):
174             self.storage.find_and_get(0xbad, self.keyprefix)
175         with pytest.raises(SdlTypeError):
176             self.storage.find_and_get(self.ns, 0xbad)
177
178     def test_remove_function_success(self):
179         self.storage.remove(self.ns, self.keys)
180         self.mock_db_backend.remove.assert_called_once()
181         call_args, _ = self.mock_db_backend.remove.call_args
182         assert call_args[0] == self.ns
183         assert isinstance(call_args[1], list)
184         assert len(call_args[1]) == len(self.keys)
185         assert all(k in call_args[1] for k in self.keys)
186
187     def test_remove_function_can_raise_exception_for_wrong_argument(self):
188         with pytest.raises(SdlTypeError):
189             self.storage.remove(0xbad, self.keys)
190         with pytest.raises(SdlTypeError):
191             self.storage.remove(self.ns, 0xbad)
192
193     def test_remove_if_function_success(self):
194         self.mock_db_backend.remove_if.return_value = True
195         ret = self.storage.remove_if(self.ns, self.key, self.new_data)
196         self.mock_db_backend.remove_if.assert_called_once_with(self.ns, self.key, self.new_data)
197         assert ret is True
198
199     def test_remove_if_function_can_return_false_if_data_does_not_match(self):
200         self.mock_db_backend.remove_if.return_value = False
201         ret = self.storage.remove_if(self.ns, self.key, self.old_data)
202         self.mock_db_backend.remove_if.assert_called_once_with(self.ns, self.key, self.old_data)
203         assert ret is False
204
205     def test_remove_if_function_can_raise_exception_for_wrong_argument(self):
206         with pytest.raises(SdlTypeError):
207             self.storage.remove_if(0xbad, self.keys, self.old_data)
208         with pytest.raises(SdlTypeError):
209             self.storage.remove_if(self.ns, 0xbad, self.old_data)
210         with pytest.raises(SdlTypeError):
211             self.storage.remove_if(self.ns, self.keys, 0xbad)
212
213     def test_remove_all_function_success(self):
214         self.mock_db_backend.find_keys.return_value = ['a1']
215         self.storage.remove_all(self.ns)
216         self.mock_db_backend.find_keys.assert_called_once_with(self.ns, '*')
217         self.mock_db_backend.remove.assert_called_once_with(self.ns,
218                                                             self.mock_db_backend.find_keys.return_value)
219
220     def test_remove_all_function_can_raise_exception_for_wrong_argument(self):
221         with pytest.raises(SdlTypeError):
222             self.storage.remove_all(0xbad)
223
224     def test_add_member_function_success(self):
225         self.storage.add_member(self.ns, self.group, self.groupmembers)
226         self.mock_db_backend.add_member.assert_called_once_with(self.ns,
227                                                                 self.group, self.groupmembers)
228
229     def test_add_member_function_can_raise_exception_for_wrong_argument(self):
230         with pytest.raises(SdlTypeError):
231             self.storage.add_member(0xbad, self.group, self.groupmembers)
232         with pytest.raises(SdlTypeError):
233             self.storage.add_member(self.ns, 0xbad, self.groupmembers)
234         with pytest.raises(SdlTypeError):
235             self.storage.add_member(self.ns, self.group, 0xbad)
236
237     def test_remove_member_function_success(self):
238         self.storage.remove_member(self.ns, self.group, self.groupmembers)
239         self.mock_db_backend.remove_member.assert_called_once_with(self.ns, self.group,
240                                                                    self.groupmembers)
241
242     def test_remove_member_function_can_raise_exception_for_wrong_argument(self):
243         with pytest.raises(SdlTypeError):
244             self.storage.remove_member(0xbad, self.group, self.groupmembers)
245         with pytest.raises(SdlTypeError):
246             self.storage.remove_member(self.ns, 0xbad, self.groupmembers)
247         with pytest.raises(SdlTypeError):
248             self.storage.remove_member(self.ns, self.group, 0xbad)
249
250     def test_remove_group_function_success(self):
251         self.storage.remove_group(self.ns, self.group)
252         self.mock_db_backend.remove_group.assert_called_once_with(self.ns, self.group)
253
254     def test_remove_group_function_can_raise_exception_for_wrong_argument(self):
255         with pytest.raises(SdlTypeError):
256             self.storage.remove_group(0xbad, self.group)
257         with pytest.raises(SdlTypeError):
258             self.storage.remove_group(self.ns, 0xbad)
259
260     def test_get_members_function_success(self):
261         self.mock_db_backend.get_members.return_value = self.groupmembers
262         ret = self.storage.get_members(self.ns, self.group)
263         self.mock_db_backend.get_members.assert_called_once_with(self.ns, self.group)
264         assert ret == self.groupmembers
265
266     def test_get_members_function_can_raise_exception_for_wrong_argument(self):
267         with pytest.raises(SdlTypeError):
268             self.storage.get_members(0xbad, self.group)
269         with pytest.raises(SdlTypeError):
270             self.storage.get_members(self.ns, 0xbad)
271
272     def test_is_member_function_success(self):
273         self.mock_db_backend.is_member.return_value = True
274         ret = self.storage.is_member(self.ns, self.group, self.groupmember)
275         self.mock_db_backend.is_member.assert_called_once_with(self.ns, self.group,
276                                                                self.groupmember)
277         assert ret is True
278
279     def test_is_member_function_can_raise_exception_for_wrong_argument(self):
280         with pytest.raises(SdlTypeError):
281             self.storage.is_member(0xbad, self.group, self.groupmember)
282         with pytest.raises(SdlTypeError):
283             self.storage.is_member(self.ns, 0xbad, self.groupmember)
284         with pytest.raises(SdlTypeError):
285             self.storage.is_member(self.ns, self.group, 0xbad)
286
287     def test_group_size_function_success(self):
288         self.mock_db_backend.group_size.return_value = 100
289         ret = self.storage.group_size(self.ns, self.group)
290         self.mock_db_backend.group_size.assert_called_once_with(self.ns, self.group)
291         assert ret == 100
292
293     def test_group_size_function_can_raise_exception_for_wrong_argument(self):
294         with pytest.raises(SdlTypeError):
295             self.storage.group_size(0xbad, self.group)
296         with pytest.raises(SdlTypeError):
297             self.storage.group_size(self.ns, 0xbad)
298
299     @patch('ricsdl.syncstorage.SyncLock')
300     def test_get_lock_resource_function_success_when_expiration_time_is_integer(self, mock_db_lock):
301         ret = self.storage.get_lock_resource(self.ns, self.lock_name, self.lock_int_expiration)
302         mock_db_lock.assert_called_once_with(self.ns, self.lock_name, self.lock_int_expiration,
303                                              self.storage)
304         assert ret == mock_db_lock.return_value
305
306     @patch('ricsdl.syncstorage.SyncLock')
307     def test_get_lock_resource_function_success_when_expiration_time_is_float_number(self,
308                                                                                      mock_db_lock):
309         ret = self.storage.get_lock_resource(self.ns, self.lock_name, self.lock_float_expiration)
310         mock_db_lock.assert_called_once_with(self.ns, self.lock_name, self.lock_float_expiration,
311                                              self.storage)
312         assert ret == mock_db_lock.return_value
313
314     def test_get_lock_resource_function_can_raise_exception_for_wrong_argument(self):
315         with pytest.raises(SdlTypeError):
316             self.storage.get_lock_resource(0xbad, self.lock_name, self.lock_int_expiration)
317         with pytest.raises(SdlTypeError):
318             self.storage.get_lock_resource(self.ns, 0xbad, self.lock_int_expiration)
319         with pytest.raises(SdlTypeError):
320             self.storage.get_lock_resource(self.ns, self.lock_name, 'bad')
321
322     def test_get_backend_function_success(self):
323         ret = self.storage.get_backend()
324         assert ret == self.mock_db_backend
325
326     def test_storage_object_string_representation(self):
327         str_out = str(self.storage)
328         assert str_out is not None
329
330
331 @pytest.fixture()
332 def lock_fixture(request):
333     request.cls.ns = 'some-ns'
334     request.cls.lockname = 'some-lock-name'
335     request.cls.expiration = 10
336     request.cls.retry_interval = 0.1
337     request.cls.retry_timeout = 1
338
339     with patch('ricsdl.backend.get_backend_lock_instance') as mock_db_backend_lock:
340         lock = SyncLock('test-ns', 'test-lock-name', request.cls.expiration, Mock())
341         request.cls.mock_db_backend_lock = mock_db_backend_lock.return_value
342     request.cls.lock = lock
343     yield
344
345
346 @pytest.mark.usefixtures('lock_fixture')
347 class TestSyncLock:
348     def test_acquire_function_success_when_timeout_and_interval_are_integers(self):
349         self.lock.acquire(self.retry_interval, self.retry_timeout)
350         self.mock_db_backend_lock.acquire.assert_called_once_with(self.retry_interval,
351                                                                   self.retry_timeout)
352
353     def test_acquire_function_success_when_timeout_and_interval_are_float_numbers(self):
354         self.lock.acquire(float(self.retry_interval), float(self.retry_timeout))
355         self.mock_db_backend_lock.acquire.assert_called_once_with(float(self.retry_interval),
356                                                                   float(self.retry_timeout))
357
358     def test_acquire_function_can_raise_exception_for_wrong_argument(self):
359         with pytest.raises(SdlTypeError):
360             self.lock.acquire('bad', self.retry_timeout)
361         with pytest.raises(SdlTypeError):
362             self.lock.acquire(self.retry_interval, 'bad')
363
364     def test_release_function_success(self):
365         self.lock.release()
366         self.mock_db_backend_lock.release.assert_called_once()
367
368     def test_refresh_function_success(self):
369         self.lock.refresh()
370         self.mock_db_backend_lock.refresh.assert_called_once()
371
372     def test_get_validity_time_function_success(self):
373         self.mock_db_backend_lock.get_validity_time.return_value = self.expiration
374         ret = self.lock.get_validity_time()
375         self.mock_db_backend_lock.get_validity_time.assert_called_once()
376         assert ret == self.expiration
377
378     def test_get_validity_time_function_success_when_returned_time_is_float(self):
379         self.mock_db_backend_lock.get_validity_time.return_value = float(self.expiration)
380         ret = self.lock.get_validity_time()
381         self.mock_db_backend_lock.get_validity_time.assert_called_once()
382         assert ret == float(self.expiration)
383
384     def test_lock_object_string_representation(self):
385         str_out = str(self.lock)
386         assert str_out is not None
387
388
389 def test_function_arg_validator():
390     @func_arg_checker(SdlTypeError, 0, a=str, b=(int, float), c=set, d=(dict, type(None)))
391     def _my_func(a='abc', b=1, c={'x', 'y'}, d={'x': b'1'}):
392         pass
393     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'a'=<class 'NoneType'>. "
394                                            r"Must be: <class 'str'>"):
395         _my_func(None)
396
397     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'b'=<class 'str'>. "):
398         _my_func('abc', 'wrong type')
399
400     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'c'=<class 'str'>. "
401                                            r"Must be: <class 'set'>"):
402         _my_func('abc', 1.0, 'wrong type')
403
404     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'd'=<class 'str'>. "):
405         _my_func('abc', 1.0, {'x', 'y'}, 'wrong type')
406
407
408 def test_function_kwarg_validator():
409     @func_arg_checker(SdlTypeError, 0, a=str, b=(int, float), c=set, d=(dict, type(None)))
410     def _my_func(a='abc', b=1, c={'x', 'y'}, d={'x': b'1'}):
411         pass
412     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'a'=<class 'NoneType'>. "
413                                            r"Must be: <class 'str'>"):
414         _my_func(a=None)
415
416     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'b'=<class 'str'>. "):
417         _my_func(b='wrong type')
418
419     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'c'=<class 'str'>. "
420                                            r"Must be: <class 'set'>"):
421         _my_func(c='wrong type')
422
423     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'd'=<class 'str'>. "):
424         _my_func(d='wrong type')