Improve SDL API function argument validation
[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, NotConnected)
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_is_active_function_success(self):
57         self.mock_db_backend.is_connected.return_value = True
58         ret = self.storage.is_active()
59         self.mock_db_backend.is_connected.assert_called_once()
60         assert ret is True
61
62     def test_is_active_function_can_catch_backend_exception_and_return_false(self):
63         self.mock_db_backend.is_connected.side_effect = NotConnected
64         ret = self.storage.is_active()
65         self.mock_db_backend.is_connected.assert_called_once()
66         assert ret is False
67
68     def test_set_function_success(self):
69         self.storage.set(self.ns, self.dm)
70         self.mock_db_backend.set.assert_called_once_with(self.ns, self.dm)
71
72     def test_set_function_can_raise_exception_for_wrong_argument(self):
73         with pytest.raises(SdlTypeError):
74             self.storage.set(123, {'a': b'v1'})
75         with pytest.raises(SdlTypeError):
76             self.storage.set('ns', [1, 2])
77         with pytest.raises(SdlTypeError):
78             self.storage.set('ns', {0xbad: b'v1'})
79         with pytest.raises(SdlTypeError):
80             self.storage.set('ns', {'a': 0xbad})
81
82     def test_set_if_function_success(self):
83         self.mock_db_backend.set_if.return_value = True
84         ret = self.storage.set_if(self.ns, self.key, self.old_data, self.new_data)
85         self.mock_db_backend.set_if.assert_called_once_with(self.ns, self.key, self.old_data,
86                                                             self.new_data)
87         assert ret is True
88
89     def test_set_if_function_can_return_false_if_same_data_already_exists(self):
90         self.mock_db_backend.set_if.return_value = False
91         ret = self.storage.set_if(self.ns, self.key, self.old_data, self.new_data)
92         self.mock_db_backend.set_if.assert_called_once_with(self.ns, self.key, self.old_data,
93                                                             self.new_data)
94         assert ret is False
95
96     def test_set_if_function_can_raise_exception_for_wrong_argument(self):
97         with pytest.raises(SdlTypeError):
98             self.storage.set_if(0xbad, 'key', b'v1', b'v2')
99         with pytest.raises(SdlTypeError):
100             self.storage.set_if('ns', 0xbad, b'v1', b'v2')
101         with pytest.raises(SdlTypeError):
102             self.storage.set_if('ns', 'key', 0xbad, b'v2')
103         with pytest.raises(SdlTypeError):
104             self.storage.set_if('ns', 'key', b'v1', 0xbad)
105
106     def test_set_if_not_exists_function_success(self):
107         self.mock_db_backend.set_if_not_exists.return_value = True
108         ret = self.storage.set_if_not_exists(self.ns, self.key, self.new_data)
109         self.mock_db_backend.set_if_not_exists.assert_called_once_with(self.ns, self.key,
110                                                                        self.new_data)
111         assert ret is True
112
113     def test_set_if_not_exists_function_can_return_false_if_key_already_exists(self):
114         self.mock_db_backend.set_if_not_exists.return_value = False
115         ret = self.storage.set_if_not_exists(self.ns, self.key, self.new_data)
116         self.mock_db_backend.set_if_not_exists.assert_called_once_with(self.ns, self.key,
117                                                                        self.new_data)
118         assert ret is False
119
120     def test_set_if_not_exists_function_can_raise_exception_for_wrong_argument(self):
121         with pytest.raises(SdlTypeError):
122             self.storage.set_if_not_exists(0xbad, 'key', b'v1')
123         with pytest.raises(SdlTypeError):
124             self.storage.set_if_not_exists('ns', 0xbad, b'v1')
125         with pytest.raises(SdlTypeError):
126             self.storage.set_if_not_exists('ns', 'key', 0xbad)
127
128     def test_get_function_success(self):
129         self.mock_db_backend.get.return_value = self.dm
130         ret = self.storage.get(self.ns, self.keys)
131         self.mock_db_backend.get.assert_called_once()
132         call_args, _ = self.mock_db_backend.get.call_args
133         assert call_args[0] == self.ns
134         assert len(call_args[1]) == len(self.keys)
135         assert all(k in call_args[1] for k in self.keys)
136         assert ret == self.dm
137         # Validate that SDL returns a dictionary with keys in alphabetical order
138         assert sorted(self.dm)[0] == list(ret.keys())[0]
139
140     def test_get_function_can_return_empty_dict_when_no_key_values_exist(self):
141         self.mock_db_backend.get.return_value = dict()
142         ret = self.storage.get(self.ns, self.keys)
143         self.mock_db_backend.get.assert_called_once()
144         call_args, _ = self.mock_db_backend.get.call_args
145         assert call_args[0] == self.ns
146         assert len(call_args[1]) == len(self.keys)
147         assert all(k in call_args[1] for k in self.keys)
148         assert ret == dict()
149
150     def test_get_function_can_raise_exception_for_wrong_argument(self):
151         with pytest.raises(SdlTypeError):
152             self.storage.get(0xbad, self.key)
153         with pytest.raises(SdlTypeError):
154             self.storage.get(self.ns, 0xbad)
155
156     def test_find_keys_function_success(self):
157         self.mock_db_backend.find_keys.return_value = self.matchedkeys
158         ret = self.storage.find_keys(self.ns, self.keyprefix)
159         self.mock_db_backend.find_keys.assert_called_once_with(self.ns, self.keyprefix)
160         assert ret == self.matchedkeys
161
162     def test_find_keys_function_can_return_empty_list_when_no_keys_exist(self):
163         self.mock_db_backend.find_keys.return_value = list()
164         ret = self.storage.find_keys(self.ns, self.keyprefix)
165         self.mock_db_backend.find_keys.assert_called_once_with(self.ns, self.keyprefix)
166         assert ret == list()
167
168     def test_find_keys_function_can_raise_exception_for_wrong_argument(self):
169         with pytest.raises(SdlTypeError):
170             self.storage.find_keys(0xbad, self.keyprefix)
171         with pytest.raises(SdlTypeError):
172             self.storage.find_keys(self.ns, 0xbad)
173
174     def test_find_and_get_function_success(self):
175         self.mock_db_backend.find_and_get.return_value = self.dm
176         ret = self.storage.find_and_get(self.ns, self.keyprefix)
177         self.mock_db_backend.find_and_get.assert_called_once_with(self.ns, self.keyprefix)
178         assert ret == self.dm
179         # Validate that SDL returns a dictionary with keys in alphabetical order
180         assert sorted(self.dm)[0] == list(ret.keys())[0]
181
182     def test_find_and_get_function_can_return_empty_dict_when_no_keys_exist(self):
183         self.mock_db_backend.find_and_get.return_value = dict()
184         ret = self.storage.find_and_get(self.ns, self.keyprefix)
185         self.mock_db_backend.find_and_get.assert_called_once_with(self.ns, self.keyprefix)
186         assert ret == dict()
187
188     def test_find_and_get_function_can_raise_exception_for_wrong_argument(self):
189         with pytest.raises(SdlTypeError):
190             self.storage.find_and_get(0xbad, self.keyprefix)
191         with pytest.raises(SdlTypeError):
192             self.storage.find_and_get(self.ns, 0xbad)
193
194     def test_remove_function_success(self):
195         self.storage.remove(self.ns, self.keys)
196         self.mock_db_backend.remove.assert_called_once()
197         call_args, _ = self.mock_db_backend.remove.call_args
198         assert call_args[0] == self.ns
199         assert isinstance(call_args[1], list)
200         assert len(call_args[1]) == len(self.keys)
201         assert all(k in call_args[1] for k in self.keys)
202
203     def test_remove_function_can_raise_exception_for_wrong_argument(self):
204         with pytest.raises(SdlTypeError):
205             self.storage.remove(0xbad, self.keys)
206         with pytest.raises(SdlTypeError):
207             self.storage.remove(self.ns, 0xbad)
208
209     def test_remove_if_function_success(self):
210         self.mock_db_backend.remove_if.return_value = True
211         ret = self.storage.remove_if(self.ns, self.key, self.new_data)
212         self.mock_db_backend.remove_if.assert_called_once_with(self.ns, self.key, self.new_data)
213         assert ret is True
214
215     def test_remove_if_function_can_return_false_if_data_does_not_match(self):
216         self.mock_db_backend.remove_if.return_value = False
217         ret = self.storage.remove_if(self.ns, self.key, self.old_data)
218         self.mock_db_backend.remove_if.assert_called_once_with(self.ns, self.key, self.old_data)
219         assert ret is False
220
221     def test_remove_if_function_can_raise_exception_for_wrong_argument(self):
222         with pytest.raises(SdlTypeError):
223             self.storage.remove_if(0xbad, self.keys, self.old_data)
224         with pytest.raises(SdlTypeError):
225             self.storage.remove_if(self.ns, 0xbad, self.old_data)
226         with pytest.raises(SdlTypeError):
227             self.storage.remove_if(self.ns, self.keys, 0xbad)
228
229     def test_remove_all_function_success(self):
230         self.mock_db_backend.find_keys.return_value = ['a1']
231         self.storage.remove_all(self.ns)
232         self.mock_db_backend.find_keys.assert_called_once_with(self.ns, '*')
233         self.mock_db_backend.remove.assert_called_once_with(self.ns,
234                                                             self.mock_db_backend.find_keys.return_value)
235
236     def test_remove_all_function_can_raise_exception_for_wrong_argument(self):
237         with pytest.raises(SdlTypeError):
238             self.storage.remove_all(0xbad)
239
240     def test_add_member_function_success(self):
241         self.storage.add_member(self.ns, self.group, self.groupmembers)
242         self.mock_db_backend.add_member.assert_called_once_with(self.ns,
243                                                                 self.group, self.groupmembers)
244
245     def test_add_member_function_can_raise_exception_for_wrong_argument(self):
246         with pytest.raises(SdlTypeError):
247             self.storage.add_member(0xbad, self.group, self.groupmembers)
248         with pytest.raises(SdlTypeError):
249             self.storage.add_member(self.ns, 0xbad, self.groupmembers)
250         with pytest.raises(SdlTypeError):
251             self.storage.add_member(self.ns, self.group, 0xbad)
252
253     def test_remove_member_function_success(self):
254         self.storage.remove_member(self.ns, self.group, self.groupmembers)
255         self.mock_db_backend.remove_member.assert_called_once_with(self.ns, self.group,
256                                                                    self.groupmembers)
257
258     def test_remove_member_function_can_raise_exception_for_wrong_argument(self):
259         with pytest.raises(SdlTypeError):
260             self.storage.remove_member(0xbad, self.group, self.groupmembers)
261         with pytest.raises(SdlTypeError):
262             self.storage.remove_member(self.ns, 0xbad, self.groupmembers)
263         with pytest.raises(SdlTypeError):
264             self.storage.remove_member(self.ns, self.group, 0xbad)
265
266     def test_remove_group_function_success(self):
267         self.storage.remove_group(self.ns, self.group)
268         self.mock_db_backend.remove_group.assert_called_once_with(self.ns, self.group)
269
270     def test_remove_group_function_can_raise_exception_for_wrong_argument(self):
271         with pytest.raises(SdlTypeError):
272             self.storage.remove_group(0xbad, self.group)
273         with pytest.raises(SdlTypeError):
274             self.storage.remove_group(self.ns, 0xbad)
275
276     def test_get_members_function_success(self):
277         self.mock_db_backend.get_members.return_value = self.groupmembers
278         ret = self.storage.get_members(self.ns, self.group)
279         self.mock_db_backend.get_members.assert_called_once_with(self.ns, self.group)
280         assert ret == self.groupmembers
281
282     def test_get_members_function_can_raise_exception_for_wrong_argument(self):
283         with pytest.raises(SdlTypeError):
284             self.storage.get_members(0xbad, self.group)
285         with pytest.raises(SdlTypeError):
286             self.storage.get_members(self.ns, 0xbad)
287
288     def test_is_member_function_success(self):
289         self.mock_db_backend.is_member.return_value = True
290         ret = self.storage.is_member(self.ns, self.group, self.groupmember)
291         self.mock_db_backend.is_member.assert_called_once_with(self.ns, self.group,
292                                                                self.groupmember)
293         assert ret is True
294
295     def test_is_member_function_can_raise_exception_for_wrong_argument(self):
296         with pytest.raises(SdlTypeError):
297             self.storage.is_member(0xbad, self.group, self.groupmember)
298         with pytest.raises(SdlTypeError):
299             self.storage.is_member(self.ns, 0xbad, self.groupmember)
300         with pytest.raises(SdlTypeError):
301             self.storage.is_member(self.ns, self.group, 0xbad)
302
303     def test_group_size_function_success(self):
304         self.mock_db_backend.group_size.return_value = 100
305         ret = self.storage.group_size(self.ns, self.group)
306         self.mock_db_backend.group_size.assert_called_once_with(self.ns, self.group)
307         assert ret == 100
308
309     def test_group_size_function_can_raise_exception_for_wrong_argument(self):
310         with pytest.raises(SdlTypeError):
311             self.storage.group_size(0xbad, self.group)
312         with pytest.raises(SdlTypeError):
313             self.storage.group_size(self.ns, 0xbad)
314
315     @patch('ricsdl.syncstorage.SyncLock')
316     def test_get_lock_resource_function_success_when_expiration_time_is_integer(self, mock_db_lock):
317         ret = self.storage.get_lock_resource(self.ns, self.lock_name, self.lock_int_expiration)
318         mock_db_lock.assert_called_once_with(self.ns, self.lock_name, self.lock_int_expiration,
319                                              self.storage)
320         assert ret == mock_db_lock.return_value
321
322     @patch('ricsdl.syncstorage.SyncLock')
323     def test_get_lock_resource_function_success_when_expiration_time_is_float_number(self,
324                                                                                      mock_db_lock):
325         ret = self.storage.get_lock_resource(self.ns, self.lock_name, self.lock_float_expiration)
326         mock_db_lock.assert_called_once_with(self.ns, self.lock_name, self.lock_float_expiration,
327                                              self.storage)
328         assert ret == mock_db_lock.return_value
329
330     def test_get_lock_resource_function_can_raise_exception_for_wrong_argument(self):
331         with pytest.raises(SdlTypeError):
332             self.storage.get_lock_resource(0xbad, self.lock_name, self.lock_int_expiration)
333         with pytest.raises(SdlTypeError):
334             self.storage.get_lock_resource(self.ns, 0xbad, self.lock_int_expiration)
335         with pytest.raises(SdlTypeError):
336             self.storage.get_lock_resource(self.ns, self.lock_name, 'bad')
337
338     def test_get_backend_function_success(self):
339         ret = self.storage.get_backend()
340         assert ret == self.mock_db_backend
341
342     def test_storage_object_string_representation(self):
343         str_out = str(self.storage)
344         assert str_out is not None
345
346
347 @pytest.fixture()
348 def lock_fixture(request):
349     request.cls.ns = 'some-ns'
350     request.cls.lockname = 'some-lock-name'
351     request.cls.expiration = 10
352     request.cls.retry_interval = 0.1
353     request.cls.retry_timeout = 1
354
355     with patch('ricsdl.backend.get_backend_lock_instance') as mock_db_backend_lock:
356         lock = SyncLock('test-ns', 'test-lock-name', request.cls.expiration, Mock())
357         request.cls.mock_db_backend_lock = mock_db_backend_lock.return_value
358     request.cls.lock = lock
359     yield
360
361
362 @pytest.mark.usefixtures('lock_fixture')
363 class TestSyncLock:
364     def test_acquire_function_success_when_timeout_and_interval_are_integers(self):
365         self.lock.acquire(self.retry_interval, self.retry_timeout)
366         self.mock_db_backend_lock.acquire.assert_called_once_with(self.retry_interval,
367                                                                   self.retry_timeout)
368
369     def test_acquire_function_success_when_timeout_and_interval_are_float_numbers(self):
370         self.lock.acquire(float(self.retry_interval), float(self.retry_timeout))
371         self.mock_db_backend_lock.acquire.assert_called_once_with(float(self.retry_interval),
372                                                                   float(self.retry_timeout))
373
374     def test_acquire_function_can_raise_exception_for_wrong_argument(self):
375         with pytest.raises(SdlTypeError):
376             self.lock.acquire('bad', self.retry_timeout)
377         with pytest.raises(SdlTypeError):
378             self.lock.acquire(self.retry_interval, 'bad')
379
380     def test_release_function_success(self):
381         self.lock.release()
382         self.mock_db_backend_lock.release.assert_called_once()
383
384     def test_refresh_function_success(self):
385         self.lock.refresh()
386         self.mock_db_backend_lock.refresh.assert_called_once()
387
388     def test_get_validity_time_function_success(self):
389         self.mock_db_backend_lock.get_validity_time.return_value = self.expiration
390         ret = self.lock.get_validity_time()
391         self.mock_db_backend_lock.get_validity_time.assert_called_once()
392         assert ret == self.expiration
393
394     def test_get_validity_time_function_success_when_returned_time_is_float(self):
395         self.mock_db_backend_lock.get_validity_time.return_value = float(self.expiration)
396         ret = self.lock.get_validity_time()
397         self.mock_db_backend_lock.get_validity_time.assert_called_once()
398         assert ret == float(self.expiration)
399
400     def test_lock_object_string_representation(self):
401         str_out = str(self.lock)
402         assert str_out is not None
403
404
405 def test_function_arg_validator():
406     @func_arg_checker(SdlTypeError, 0, a=str, b=(int, float), c=set, d=(dict, type(None)))
407     def _my_func(a='abc', b=1, c={'x', 'y'}, d={'x': b'1'}):
408         pass
409     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'a'=<class 'NoneType'>. "
410                                            r"Must be: <class 'str'>"):
411         _my_func(None)
412
413     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'b'=<class 'str'>. "):
414         _my_func('abc', 'wrong type')
415
416     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'c'=<class 'str'>. "
417                                            r"Must be: <class 'set'>"):
418         _my_func('abc', 1.0, 'wrong type')
419
420     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'd'=<class 'str'>. "):
421         _my_func('abc', 1.0, {'x', 'y'}, 'wrong type')
422
423
424 def test_function_kwarg_validator():
425     @func_arg_checker(SdlTypeError, 0, a=str, b=(int, float), c=set, d=(dict, type(None)))
426     def _my_func(a='abc', b=1, c={'x', 'y'}, d={'x': b'1'}):
427         pass
428     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'a'=<class 'NoneType'>. "
429                                            r"Must be: <class 'str'>"):
430         _my_func(a=None)
431
432     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'b'=<class 'str'>. "):
433         _my_func(b='wrong type')
434
435     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'c'=<class 'str'>. "
436                                            r"Must be: <class 'set'>"):
437         _my_func(c='wrong type')
438
439     with pytest.raises(SdlTypeError, match=r"Wrong argument type: 'd'=<class 'str'>. "):
440         _my_func(d='wrong type')