assertman2 0.2.29__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. assertman2-0.2.29/PKG-INFO +13 -0
  2. assertman2-0.2.29/assertman/__init__.py +2 -0
  3. assertman2-0.2.29/assertman/assert_that.py +10 -0
  4. assertman2-0.2.29/assertman/assertable_mixin.py +36 -0
  5. assertman2-0.2.29/assertman/cerberus_wrapper/__init__.py +1 -0
  6. assertman2-0.2.29/assertman/cerberus_wrapper/assert_that.py +97 -0
  7. assertman2-0.2.29/assertman/cerberus_wrapper/mapping.py +420 -0
  8. assertman2-0.2.29/assertman/cerberus_wrapper/pretty_error.py +38 -0
  9. assertman2-0.2.29/assertman/cerberus_wrapper/validators.py +413 -0
  10. assertman2-0.2.29/assertman/helpers/__init__.py +0 -0
  11. assertman2-0.2.29/assertman/helpers/datetime_helper.py +274 -0
  12. assertman2-0.2.29/assertman/helpers/jsonpath_helper.py +29 -0
  13. assertman2-0.2.29/assertman/helpers/waiting_helper.py +92 -0
  14. assertman2-0.2.29/assertman/matchers/__init__.py +7 -0
  15. assertman2-0.2.29/assertman/matchers/datetimes.py +300 -0
  16. assertman2-0.2.29/assertman/matchers/dictionary.py +5 -0
  17. assertman2-0.2.29/assertman/matchers/logical.py +6 -0
  18. assertman2-0.2.29/assertman/matchers/numbers.py +8 -0
  19. assertman2-0.2.29/assertman/matchers/object.py +11 -0
  20. assertman2-0.2.29/assertman/matchers/sequence.py +194 -0
  21. assertman2-0.2.29/assertman/matchers/text.py +10 -0
  22. assertman2-0.2.29/assertman/objects/__init__.py +32 -0
  23. assertman2-0.2.29/assertman/objects/assertable_api_response.py +65 -0
  24. assertman2-0.2.29/assertman/objects/assertable_bool.py +21 -0
  25. assertman2-0.2.29/assertman/objects/assertable_datetime.py +8 -0
  26. assertman2-0.2.29/assertman/objects/assertable_dict.py +43 -0
  27. assertman2-0.2.29/assertman/objects/assertable_float.py +10 -0
  28. assertman2-0.2.29/assertman/objects/assertable_int.py +10 -0
  29. assertman2-0.2.29/assertman/objects/assertable_list.py +88 -0
  30. assertman2-0.2.29/assertman/objects/assertable_none.py +21 -0
  31. assertman2-0.2.29/assertman/objects/assertable_string.py +9 -0
  32. assertman2-0.2.29/assertman2.egg-info/PKG-INFO +13 -0
  33. assertman2-0.2.29/assertman2.egg-info/SOURCES.txt +65 -0
  34. assertman2-0.2.29/assertman2.egg-info/dependency_links.txt +1 -0
  35. assertman2-0.2.29/assertman2.egg-info/requires.txt +5 -0
  36. assertman2-0.2.29/assertman2.egg-info/top_level.txt +3 -0
  37. assertman2-0.2.29/docs/__init__.py +0 -0
  38. assertman2-0.2.29/setup.cfg +4 -0
  39. assertman2-0.2.29/setup.py +25 -0
  40. assertman2-0.2.29/tests/__init__.py +0 -0
  41. assertman2-0.2.29/tests/conftest.py +61 -0
  42. assertman2-0.2.29/tests/extra/__init__.py +0 -0
  43. assertman2-0.2.29/tests/extra/dict/__init__.py +0 -0
  44. assertman2-0.2.29/tests/extra/dict/test_dict_extract.py +41 -0
  45. assertman2-0.2.29/tests/extra/dict/test_jsonpatch.py +105 -0
  46. assertman2-0.2.29/tests/extra/list/__init__.py +0 -0
  47. assertman2-0.2.29/tests/extra/list/test_list_filter.py +129 -0
  48. assertman2-0.2.29/tests/extra/list/test_list_find.py +107 -0
  49. assertman2-0.2.29/tests/extra/list/test_list_get_by_index.py +21 -0
  50. assertman2-0.2.29/tests/extra/response/__init__.py +0 -0
  51. assertman2-0.2.29/tests/extra/response/test_request.py +86 -0
  52. assertman2-0.2.29/tests/new/__init__.py +0 -0
  53. assertman2-0.2.29/tests/new/pretty.py +22 -0
  54. assertman2-0.2.29/tests/new/prettylist.py +9 -0
  55. assertman2-0.2.29/tests/objects/__init__.py +0 -0
  56. assertman2-0.2.29/tests/objects/test_1_integer.py +27 -0
  57. assertman2-0.2.29/tests/objects/test_2_string.py +25 -0
  58. assertman2-0.2.29/tests/objects/test_3_1_list.py +107 -0
  59. assertman2-0.2.29/tests/objects/test_3_2_list_with_dicts.py +94 -0
  60. assertman2-0.2.29/tests/objects/test_4_1_dict.py +50 -0
  61. assertman2-0.2.29/tests/objects/test_4_2_dict_with_lists_has_length.py +33 -0
  62. assertman2-0.2.29/tests/objects/test_4_3_dict_with_lists_has_item.py +217 -0
  63. assertman2-0.2.29/tests/objects/test_4_4_dict_has_key.py +47 -0
  64. assertman2-0.2.29/tests/objects/test_4_5_dict_with_lists_every_item.py +0 -0
  65. assertman2-0.2.29/tests/objects/test_5_datetime.py +340 -0
  66. assertman2-0.2.29/tests/temp_test_validate_schema.py +47 -0
  67. assertman2-0.2.29/tests/utils.py +4 -0
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 1.1
2
+ Name: assertman2
3
+ Version: 0.2.29
4
+ Summary: UNKNOWN
5
+ Home-page: UNKNOWN
6
+ Author: UNKNOWN
7
+ Author-email: UNKNOWN
8
+ License: UNKNOWN
9
+ Description: UNKNOWN
10
+ Platform: UNKNOWN
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
@@ -0,0 +1,2 @@
1
+ from assertman.matchers import *
2
+ from assertman.assert_that import that
@@ -0,0 +1,10 @@
1
+ from assertman import objects
2
+
3
+
4
+
5
+ def assert_that(arg):
6
+ return objects.make_assertable_object(arg)
7
+
8
+
9
+ def that(arg):
10
+ return objects.make_assertable_object(arg)
@@ -0,0 +1,36 @@
1
+ import hamcrest
2
+ from hamcrest.core.base_matcher import BaseMatcher
3
+ from assertman import cerberus_wrapper
4
+
5
+
6
+ __tracebackhide__ = True
7
+
8
+
9
+ class AssertableMixin:
10
+
11
+ _assertion_processing = None
12
+
13
+ @property
14
+ def _assertable_data(self):
15
+ raise NotImplementedError(
16
+ 'Что бы добавить объекту свойство `assertable`, определите в нем метод `_assertable_data`')
17
+
18
+ def should(self, matcher):
19
+ if not isinstance(matcher, BaseMatcher):
20
+ raise TypeError(f'Переданный матчер имеет неподдерживаемый тип {type(matcher)}')
21
+
22
+ if type(matcher).__name__ in ['IsDictContainingEntries']:
23
+ cerberus_wrapper.assert_that(self._assertable_data, matcher)
24
+ else:
25
+ hamcrest.assert_that(self._assertable_data, matcher)
26
+ return True
27
+
28
+ def filter(self, *args, **kwargs):
29
+ raise NotImplementedError("Фильтрация доступна только для списков")
30
+
31
+ def find(self, *args, **kwargs):
32
+ raise NotImplementedError("Поиск элемента доступен только для списков")
33
+
34
+ def extract(self, query):
35
+ raise NotImplementedError("Извлечение по ключу доступно только для словарей")
36
+
@@ -0,0 +1 @@
1
+ from assertman.cerberus_wrapper.assert_that import assert_that
@@ -0,0 +1,97 @@
1
+ from assertman.cerberus_wrapper import mapping
2
+ from assertman.cerberus_wrapper import validators
3
+ from assertman.cerberus_wrapper import pretty_error
4
+ from rich.console import Console
5
+ import io
6
+ from rich.traceback import install
7
+
8
+ install()
9
+
10
+ def assert_that(document, matcher):
11
+ __tracebackhide__ = True
12
+
13
+ # Проверяем, что валидируемый документ - это словарь
14
+ if not isinstance(document, (dict, list)):
15
+ raise TypeError('Валидируемый документ должен быть словарем (json) или list')
16
+
17
+ # Базовая схема
18
+ _base_schema = {}
19
+
20
+ # Конвертируем hamcrest-матчеры в формат cerberus-схемы
21
+ _matchers_schema = mapping.extract(matcher)
22
+
23
+ if _matchers_schema.get('schema') is None:
24
+ _matchers_schema = {'schema': _matchers_schema, 'type': 'dict'}
25
+
26
+ # TODO: (Кузнецов М.) Написать тесты на то что схема дополняется, а не заменяется
27
+ for k, v in _matchers_schema['schema'].items():
28
+ if k not in _base_schema:
29
+ _base_schema.update({k: v})
30
+ else:
31
+ _base_schema[k].update(v)
32
+
33
+ # Выполняем валидацию ответа по схеме
34
+ # (не менять тут схему)
35
+ validator = validators.SkyrimValidator(
36
+ _base_schema,
37
+ allow_unknown=True,
38
+ require_all=True
39
+ )
40
+
41
+ result = validator.validate(document)
42
+
43
+ # Если при проведени валидации по схеме найдены ошибки, выкидываем AssertionError со сформировнным текстом ошибок
44
+ if result is False:
45
+ # Если в результате проверок нашлись ошибки, то выкидываем AssertionError c перечнем ошибок
46
+ error_text = pretty_error.make_assertion_text(validator.errors)
47
+ raise AssertionError("".join(error_text))
48
+ return True
49
+
50
+
51
+
52
+ def assert_that2(document, matcher):
53
+ __tracebackhide__ = True
54
+
55
+ # Проверяем, что валидируемый документ - это словарь
56
+ if not isinstance(document, dict):
57
+ raise TypeError('Валидируемый документ должен быть словарем (json)')
58
+
59
+ # Базовая схема
60
+ _base_schema = {}
61
+
62
+ # Конвертируем hamcrest-матчеры в формат cerberus-схемы
63
+ _matchers_schema = mapping.extract(matcher)
64
+
65
+ # TODO: (Кузнецов М.) Написать тесты на то что схема дополняется, а не заменяется
66
+ for k, v in _matchers_schema['schema'].items():
67
+ if k not in _base_schema:
68
+ _base_schema.update({k: v})
69
+ else:
70
+ _base_schema[k].update(v)
71
+
72
+ # Выполняем валидацию ответа по схеме
73
+ # (нее менять тут схему)
74
+ validator = validators.SkyrimValidator(
75
+ _base_schema,
76
+ allow_unknown=True,
77
+ require_all=True
78
+ )
79
+
80
+ result = validator.validate(document)
81
+
82
+ # Если при проведени валидации по схеме найдены ошибки, выкидываем AssertionError со сформировнным текстом ошибок
83
+ if result is False:
84
+ install()
85
+ # Если в результате проверок нашлись ошибки, то выкидываем AssertionError c перечнем ошибок
86
+ error_text = pretty_error.make_assertion_text(validator.errors)
87
+
88
+
89
+ console = Console(color_system="windows", file=io.StringIO(), width=120)
90
+
91
+ console.print("".join(error_text))
92
+ output = console.file.getvalue()
93
+
94
+
95
+ raise AssertionError(output)
96
+
97
+ return True
@@ -0,0 +1,420 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ # ------ Общие матчеры (Object) ------------------------------------------------------
5
+
6
+ def equal_to(obj):
7
+ """ Matches if object is equal to a given object """
8
+ schema = {"equal_to": obj}
9
+ # по-умолчанию cerberus схема не допускает наличие None значений. При проверки на equal_to(None), отключаем
10
+ # это ограничение, только для конкретного поля
11
+ if obj is None:
12
+ schema.update({"nullable": True})
13
+ return schema
14
+
15
+
16
+ def has_length(match):
17
+ """ Matches if ``len(item)`` satisfies a given matcher
18
+ * матчер проверяет длинну любого sequence-типа данных: list, str, dict, tuple и кастомнный тип данных с
19
+ переопределенным методом `__len__`
20
+ """
21
+ # TODO (Кузнецов М.): Дописать проверкау то что items должен быть числом или матчером
22
+
23
+ # В случае, если используется простая проверка длинны списка (без вложенных матчеров сравнения больше или меньше)
24
+ if isinstance(match, int):
25
+ return {'length': match}
26
+
27
+ # В случае, если используется проверка длинны списка с вложенными матчерами типа 'greater_than' и 'less_than'.
28
+ # Не стал тут реализовывать обработку матчеров 'greater_than_or_equal_to' и 'less_than_or_equal_to',
29
+ # так как при проверке длинны списков они всегда могут быть заменены
30
+ # на более лаконичные 'greater_than' и 'less_than')
31
+ # Еще одна деталь, в том что 'minlength' / 'maxlength', больше сответствуют 'greater_than_or_equal_to' /
32
+ # 'less_than_or_equal_to', чем 'greater_than' / 'less_than'. Поэтому приходится прибавлять / удалять еденницу
33
+ elif isinstance(match, dict):
34
+ if match.get('greater_than'):
35
+ return {'minlength': match.get('greater_than') + 1}
36
+ elif match.get('less_than'):
37
+ return {'maxlength': match.get('less_than') - 1}
38
+ elif match.get('greater_than_or_equal_to'):
39
+ return {'minlength': match.get('greater_than_or_equal_to')}
40
+ elif match.get('less_than_or_equal_to'):
41
+ return {'maxlength': match.get('less_than_or_equal_to')}
42
+ else:
43
+ raise Exception(f"Нельзя использовать матчер `{match}` внутри матчера `has_length`")
44
+ else:
45
+ raise Exception(f"Для `has_item` получен матчер, для которого не задано "
46
+ f"правило преобразования в схему: {type(match)}")
47
+
48
+
49
+ def is_instance_of(atype):
50
+ """ Matches if object is an instance of, or inherits from, a given type """
51
+ value = atype.__name__
52
+ if value == 'str':
53
+ value = "string"
54
+ elif value == 'int':
55
+ value = "integer"
56
+ elif value == 'bool':
57
+ value = "boolean"
58
+
59
+ return {'type': value}
60
+
61
+
62
+ def empty():
63
+ raise NotImplementedError
64
+
65
+
66
+ def none():
67
+ """ Matches if object is `None` """
68
+ return equal_to(None)
69
+
70
+
71
+ def not_none():
72
+ """ Matches if object is not `None` """
73
+ return not_(None)
74
+
75
+
76
+ # ------ Numbers ------------------------------------------------------
77
+
78
+ def greater_than(value):
79
+ """ Matches if object is greater than a given value """
80
+ _check_isinstance(value, (int, float))
81
+ return {'greater_than': value}
82
+
83
+
84
+ def greater_than_or_equal_to(value):
85
+ """ Matches if object is greater than or equal to a given value """
86
+ _check_isinstance(value, (int, float))
87
+ return {'greater_than_or_equal_to': value}
88
+
89
+
90
+ def less_than(value):
91
+ """ Matches if object is less_than a given value """
92
+ _check_isinstance(value, (int, float))
93
+ return {'less_than': value}
94
+
95
+
96
+ def less_than_or_equal_to(value):
97
+ """ Matches if object is less_than_or_equal_to a given value """
98
+ _check_isinstance(value, (int, float))
99
+ return {'less_than_or_equal_to': value}
100
+
101
+
102
+ def close_to(value, delta):
103
+ """ Matches if object is a number close to a given value, within a given delta """
104
+ _check_isinstance(value, (int, float))
105
+ _check_isinstance(delta, (int, float))
106
+ return {'close_to': (value, delta), 'type': 'integer'}
107
+
108
+
109
+ # ------ Text ------------------------------------------------------
110
+
111
+ def starts_with(substring):
112
+ """ Matches if object is a string starting with a given string """
113
+ _check_isinstance(substring, str)
114
+ return {"starts_with": substring}
115
+
116
+
117
+ def contains_string(substring):
118
+ """ Matches if object is a string containing a given string. """
119
+ _check_isinstance(substring, str)
120
+ return {"contains_string": substring}
121
+
122
+
123
+ def string_contains_in_order(substrings):
124
+ """ Matches if object is a string containing a given list of substrings in relative order. """
125
+ _check_isinstance(substrings, tuple)
126
+ for i in substrings:
127
+ _check_isinstance(i, str)
128
+ return {"string_contains_in_order": substrings}
129
+
130
+
131
+ def ends_with(substring):
132
+ """ Matches if object is a string ending with a given string """
133
+ _check_isinstance(substring, str)
134
+ return {"ends_with": substring}
135
+
136
+
137
+ def equal_to_ignoring_case(substring):
138
+ """ Matches if object is a string equal to a given string, ignoring case differences """
139
+ _check_isinstance(substring, str)
140
+ return {"equal_to_ignoring_case": substring}
141
+
142
+
143
+ def matches_regexp(pattern):
144
+ """ Matches if object is a string containing a match for a given regular expression """
145
+ _check_isinstance(pattern, str)
146
+ return {'regex': pattern}
147
+
148
+
149
+ # ------ Sequence ------------------------------------------------------
150
+
151
+ def has_item(matcher):
152
+ """ Matches if any element of sequence satisfies a given matcher """
153
+ # TODO (Кузнецов М.): Добавить проверку matcher
154
+ return {'type': 'list', 'required': True, 'empty': False, "has_item": matcher}
155
+
156
+
157
+ def _not_has_item(matcher):
158
+ """ Matches if any element of sequence NOT satisfies a given matcher """
159
+ return {'type': 'list', 'required': True, 'empty': False, "not_has_item": matcher}
160
+
161
+
162
+ def every_item(matcher):
163
+ """ Matches if every element of sequence satisfies a given matcher """
164
+ # TODO (Кузнецов М.): Добавить проверку matcher
165
+ return {'type': 'list', 'required': True, 'empty': False, "every_item": matcher}
166
+
167
+
168
+ def has_item_at_index(index, matcher):
169
+ """Проверить, что в списке элемент с переданным индексом удовлетворяет переданному матчеру."""
170
+ return {'type': 'list', 'required': True, 'empty': False, "has_item_at_index": (index, matcher)}
171
+
172
+
173
+ def has_first_item(matcher):
174
+ """Проверить, что в списке первый элемент удовлетворяет переданному матчеру."""
175
+ return has_item_at_index(0, matcher)
176
+
177
+
178
+ def has_last_item(matcher):
179
+ """Проверить, что в списке последний элемент удовлетворяет переданному матчеру."""
180
+ return has_item_at_index(-1, matcher)
181
+
182
+
183
+ def has_items(matchers):
184
+ """ Matches if any element of sequence satisfies a given matchers """
185
+ matchers_to_list_has_item = [extract(i) for i in matchers]
186
+ return {'type': 'list', 'required': True, 'empty': False, "has_items": [i['has_item'] for i in matchers_to_list_has_item]}
187
+
188
+ # ------ Dictionary ------------------------------------------------------
189
+
190
+ def has_entries(*keys_valuematchers, **kv_args):
191
+ schema = {}
192
+
193
+ def add_to_scheme(_dict):
194
+ for k, v in _dict.items():
195
+ if k not in schema:
196
+ schema.update({k: extract(v)})
197
+ else:
198
+ schema[k].update(extract(v))
199
+
200
+ if keys_valuematchers:
201
+ if len(keys_valuematchers) > 1 and not isinstance(keys_valuematchers[0], dict):
202
+ raise ValueError('has_entries requires key-value pairs or dict with matchers')
203
+ add_to_scheme(keys_valuematchers[0])
204
+
205
+ add_to_scheme(kv_args)
206
+
207
+ return {'schema': schema, 'type': 'dict'}
208
+
209
+
210
+ def has_key(key_match):
211
+ return {'has_key': key_match}
212
+
213
+
214
+
215
+ # ------ Logical ------------------------------------------------------
216
+
217
+ def all_of(*items):
218
+ """ Matches if all of the given matchers evaluate to ``True`` """
219
+ # TODO (Кузнецов М.): Дописать проверку на то, что items должен быть список матчеров
220
+ return {'allof': items}
221
+
222
+
223
+ def any_of(*items):
224
+ """ Matches if any of the given matchers evaluate to ``True`` """
225
+ # TODO (Кузнецов М.): Дописать проверку то что items должен быть список матчеров
226
+ return {'anyof': items}
227
+
228
+
229
+ # ------ Datetime ------------------------------------------------------
230
+ def is_today():
231
+ """ Матчер проверки, что дата является сегодняшней (без учета времени) """
232
+ return is_today_with_shift()
233
+
234
+
235
+ def is_tomorrow():
236
+ """ Матчер проверки, что дата является завтрашней (без учета времени) """
237
+ return is_today_with_shift(hours=24)
238
+
239
+
240
+ def is_yesterday():
241
+ """ Матчер проверки, что дата является вчерашней (без учета времени) """
242
+ return is_today_with_shift(hours=-24)
243
+
244
+
245
+ def is_today_with_shift(weeks=None, days=None, hours=None, message=None):
246
+ """ Матчер проверки, что дата является сегодняшней с заданным сдвигом """
247
+ return {"is_today_with_shift": {"weeks": weeks, "days": days, "hours": hours, "message": message}}
248
+
249
+
250
+ def is_greater_than_datetime(value):
251
+ """ Проверка, что дата более поздняя, чем ожидаемая (с учетом времени) """
252
+ return {"is_greater_than_datetime": value}
253
+
254
+
255
+ def is_less_than_datetime(value):
256
+ """ Проверка, что дата более ранняя, чем ожидаемая (с учетом времени) """
257
+ return {"is_less_than_datetime": value}
258
+
259
+
260
+ def is_equal_to_datetime(value):
261
+ """ Проверка, что дата соответствует ожидаемой с учетом времени """
262
+ return {"is_equal_to_datetime": value}
263
+
264
+
265
+ def is_greater_than_date(value):
266
+ """ Проверка, что дата более поздняя (без учета времени) """
267
+ return {"is_greater_than_date": value}
268
+
269
+
270
+ def is_less_than_date(value):
271
+ """ Проверка, что дата более ранняя (без учета времени) """
272
+ return {"is_less_than_date": value}
273
+
274
+
275
+ def is_equal_to_date(value):
276
+ """ Проверка, что дата соответствует ожидаемой (без учета времени) """
277
+ return {"is_equal_to_date": value}
278
+
279
+
280
+ def is_equal_to_date_ignoring_seconds(value, seconds=5):
281
+ """ Проверка, что дата соответствует ожидаемой с учетом допустимой погрешности с 5 секунд (по умолчанию)
282
+ В seconds можно задать допустимую погрешность
283
+ """
284
+ return {"is_equal_to_date_ignoring_seconds": {"value": value, "seconds": seconds}}
285
+
286
+
287
+ # ----------------- Original Cerberus matchers ---------------------------
288
+ def is_in(values):
289
+ return {'allowed': values}
290
+
291
+
292
+ # ------ Negative ------------------------------------------------------
293
+
294
+ class CerberusNot:
295
+ def __init__(self, value):
296
+ self.value = value
297
+
298
+ def __repr__(self):
299
+ return f'not({self.value})'
300
+
301
+
302
+ not_ = CerberusNot
303
+
304
+
305
+ def negative(matcher):
306
+ def matcher_type(_type):
307
+ return _type == type(matcher).__name__
308
+
309
+ if matcher_type('IsSequenceContaining'):
310
+ return _not_has_item(extract(matcher.element_matcher))
311
+
312
+ result = {}
313
+ for k, v in extract(matcher).items():
314
+ # Есть встроеенные в cerberus матчеры, которые почти всегда автоматически применяются
315
+ # (типа `nullable`, 'required' и так как они булевые, то при их инверсии достаточно сделать отрицание)
316
+ if k in ['nullable']:
317
+ result.update({k: not v})
318
+ else:
319
+ result.update({k: not_(v)})
320
+ return result
321
+
322
+
323
+ def _check_isinstance(value, *types):
324
+ for _type in types:
325
+ if not isinstance(value, _type):
326
+ raise TypeError(f"Матчер работает только с {_type}")
327
+
328
+
329
+ def extract(v, equal=True):
330
+ """ Функция маппинга матчеров Hamcrest в матчеры Cerberus
331
+ :param v:
332
+ :param equal:
333
+ :return: Cerberus marcher
334
+ """
335
+
336
+ def matcher_type(_type):
337
+ return _type == type(v).__name__
338
+
339
+ if isinstance(v, str) or isinstance(v, int) or isinstance(v, float) or isinstance(v, list) or isinstance(v, dict):
340
+ return equal_to(v)
341
+ elif matcher_type('IsEqual') or matcher_type('IsEqualWithDiff'):
342
+ return equal_to(v.object) if equal == True else v.object
343
+ elif matcher_type('StringContains'):
344
+ return contains_string(v.substring)
345
+ elif matcher_type('StringStartsWith'):
346
+ return starts_with(v.substring)
347
+ elif matcher_type('StringEndsWith'):
348
+ return ends_with(v.substring)
349
+ elif matcher_type('StringContainsInOrder'):
350
+ return string_contains_in_order(v.substrings)
351
+ elif matcher_type('IsEqualIgnoringCase'):
352
+ return equal_to_ignoring_case(v.original_string)
353
+ elif matcher_type('IsCloseTo'):
354
+ return close_to(v.value, v.delta)
355
+ elif matcher_type('HasLength'):
356
+ return has_length(extract(v.len_matcher, equal=False))
357
+ elif matcher_type('OrderingComparison'):
358
+ if v.comparison_description == 'greater than':
359
+ return greater_than(v.value)
360
+ elif v.comparison_description == 'less than':
361
+ return less_than(v.value)
362
+ elif v.comparison_description == 'greater than or equal to':
363
+ return greater_than_or_equal_to(v.value)
364
+ elif v.comparison_description == 'less than or equal to':
365
+ return less_than_or_equal_to(v.value)
366
+ elif matcher_type('IsSequenceContaining'):
367
+ return has_item(extract(v.element_matcher))
368
+ elif matcher_type('EveryItem'):
369
+ return every_item(extract(v.matcher))
370
+ elif matcher_type('IsDictContainingEntries'):
371
+ return has_entries(**{k: v for k, v in v.value_matchers})
372
+ elif matcher_type('IsDictContaining'):
373
+ raise Exception("Матчер `has_entry()` не портирован в Cerberus-формат. Это можно сделать, но в большинстве "
374
+ "случаев лучше использовать матчер `has_entries()`")
375
+ elif matcher_type('IsDictContainingValue'):
376
+ raise Exception("Матчер `has_value()` еще не портирован в Cerberus-формат")
377
+ elif matcher_type('IsEmpty'):
378
+ return has_length(0)
379
+ elif matcher_type('AllOf'):
380
+ return all_of(*[extract(i) for i in v.matchers])
381
+ elif matcher_type('AnyOf'):
382
+ return any_of(*[extract(i) for i in v.matchers])
383
+ elif matcher_type('Is'):
384
+ return extract(v.matcher)
385
+ elif matcher_type('IsInstanceOf'):
386
+ return is_instance_of(v.expected_type)
387
+ elif matcher_type('IsNone'):
388
+ return none()
389
+ elif matcher_type('IsDictContainingKey'):
390
+ return has_key(v.key_matcher.object)
391
+ elif matcher_type('StringMatchesPattern'):
392
+ return matches_regexp(v.pattern.pattern)
393
+ elif matcher_type('IsNot'):
394
+ return negative(v.matcher)
395
+ elif matcher_type('IsToday'):
396
+ return is_today_with_shift(**v.time_shift, message=v.expected_message)
397
+ elif matcher_type('DateComparison'):
398
+ if v.ignore_time is False:
399
+ if v.operation == 'greater':
400
+ return is_greater_than_datetime(v.actual_item)
401
+ elif v.operation == 'less':
402
+ return is_less_than_datetime(v.actual_item)
403
+ elif v.operation == 'equal':
404
+ return is_equal_to_datetime(v.actual_item)
405
+ elif v.ignore_time is True:
406
+ if v.operation == "greater":
407
+ return is_greater_than_date(v.actual_item)
408
+ elif v.operation == "less":
409
+ return is_less_than_date(v.actual_item)
410
+ elif v.operation == "equal":
411
+ return is_equal_to_date(v.actual_item)
412
+ if v.operation == "equal with error":
413
+ return is_equal_to_date_ignoring_seconds(v.actual_item, seconds=v.error_seconds)
414
+ elif matcher_type('HasItemAtIndex'):
415
+ return has_item_at_index(index=v.index, matcher=extract(v.matcher))
416
+ elif matcher_type('IsIn'):
417
+ return is_in(v.sequence)
418
+ elif matcher_type('IsSequenceContainingEvery'):
419
+ return has_items(v.matcher.matchers)
420
+ raise Exception(f"Не удалось найти правило маппинга Hamcrest-матчера '{type(v).__name__}' в Cerberus-матчер")
@@ -0,0 +1,38 @@
1
+
2
+
3
+ def make_assertion_text(dict_with_errors):
4
+ """ Сформировать и выдать легко-читаемое сообщение об ошибке """
5
+ # TODO: (Кузнецов М) Тут нужен рефакторинг и описание
6
+ fail_icon = '*' # \N{Cross Mark}
7
+ # fail_icon = '\N{Cross Mark}'
8
+ error_texts_list = []
9
+
10
+ def to_pretty(ddict, level=0):
11
+ if isinstance(ddict, str):
12
+ error_texts_list.append(f" \n{' ' * level}{ddict}")
13
+
14
+ elif isinstance(ddict, dict):
15
+ for k, v in ddict.items():
16
+ # otstup = '\t' * level
17
+ otstup = ' ' * level
18
+
19
+ if isinstance(v, str):
20
+ error_texts_list.append(f" \n{otstup}{fail_icon} {k}: {v}")
21
+ elif isinstance(v, dict):
22
+ error_texts_list.append(f" \n{otstup}{fail_icon} {k}:")
23
+ to_pretty(v, level=level+1)
24
+ elif isinstance(v, list):
25
+ assert len(v) < 3
26
+ if len(v) == 1:
27
+ to_pretty({k:v[0]}, level=level)
28
+ else:
29
+ error_texts_list.append(f" \n{otstup}{fail_icon} {k}: {v[0]}")
30
+ to_pretty(v[1], level=level+1)
31
+ else:
32
+ raise TypeError("Не удалось распарсить результат работы cerberus")
33
+
34
+ else:
35
+ raise TypeError("Не удалось распарсить список ошибок")
36
+
37
+ to_pretty(dict_with_errors)
38
+ return error_texts_list