localstack-snapshot 0.3.0__tar.gz → 0.3.2__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.
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/PKG-INFO +1 -1
- localstack_snapshot-0.3.2/localstack_snapshot/__init__.py +1 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/prototype.py +60 -11
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/report.py +8 -1
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/transformer.py +6 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/PKG-INFO +1 -1
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/pyproject.toml +1 -1
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/tests/test_snapshots.py +201 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/tests/test_transformer.py +79 -0
- localstack_snapshot-0.3.0/localstack_snapshot/__init__.py +0 -1
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/LICENSE +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/README.md +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/pytest/__init__.py +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/pytest/snapshot.py +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/__init__.py +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/transformer_utility.py +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/util/__init__.py +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/util/encoding.py +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/SOURCES.txt +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/dependency_links.txt +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/requires.txt +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/top_level.txt +0 -0
- {localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.2"
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/prototype.py
RENAMED
|
@@ -2,7 +2,9 @@ import io
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
from collections.abc import Iterator
|
|
5
6
|
from datetime import datetime, timezone
|
|
7
|
+
from enum import Enum
|
|
6
8
|
from json import JSONDecodeError
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
from re import Pattern
|
|
@@ -25,6 +27,8 @@ from .transformer_utility import TransformerUtility
|
|
|
25
27
|
SNAPSHOT_LOGGER = logging.getLogger(__name__)
|
|
26
28
|
SNAPSHOT_LOGGER.setLevel(logging.DEBUG if os.environ.get("DEBUG_SNAPSHOT") else logging.WARNING)
|
|
27
29
|
|
|
30
|
+
_SKIP_PLACEHOLDER_VALUE = "$__to_be_skipped__$"
|
|
31
|
+
|
|
28
32
|
|
|
29
33
|
class SnapshotMatchResult:
|
|
30
34
|
def __init__(self, a: dict, b: dict, key: str = ""):
|
|
@@ -167,16 +171,27 @@ class SnapshotSession:
|
|
|
167
171
|
def match_object(self, key: str, obj: object) -> None:
|
|
168
172
|
def _convert_object_to_dict(obj_):
|
|
169
173
|
if isinstance(obj_, dict):
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
# Serialize the values of the dictionary, while skipping any private keys (starting with '_')
|
|
175
|
+
return {
|
|
176
|
+
key_: _convert_object_to_dict(obj_[key_])
|
|
177
|
+
for key_ in obj_
|
|
178
|
+
if not key_.startswith("_")
|
|
179
|
+
}
|
|
180
|
+
elif isinstance(obj_, (list, Iterator)):
|
|
181
|
+
return [_convert_object_to_dict(val) for val in obj_]
|
|
182
|
+
elif isinstance(obj_, Enum):
|
|
183
|
+
return obj_.value
|
|
178
184
|
elif hasattr(obj_, "__dict__"):
|
|
179
|
-
|
|
185
|
+
# This is an object - let's try to convert it to a dictionary
|
|
186
|
+
# A naive approach would be to use the '__dict__' object directly, but that only lists the attributes
|
|
187
|
+
# In order to also serialize the properties, we use the __dir__() method
|
|
188
|
+
# Filtering by everything that is not a method gives us both attributes and properties
|
|
189
|
+
# We also (still) skip private attributes/properties, so everything that starts with an underscore
|
|
190
|
+
return {
|
|
191
|
+
k: _convert_object_to_dict(getattr(obj_, k))
|
|
192
|
+
for k in obj_.__dir__()
|
|
193
|
+
if not k.startswith("_") and type(getattr(obj_, k, "")).__name__ != "method"
|
|
194
|
+
}
|
|
180
195
|
return obj_
|
|
181
196
|
|
|
182
197
|
return self.match(key, _convert_object_to_dict(obj))
|
|
@@ -218,7 +233,7 @@ class SnapshotSession:
|
|
|
218
233
|
self.skip_verification_paths = skip_verification_paths or []
|
|
219
234
|
if skip_verification_paths:
|
|
220
235
|
SNAPSHOT_LOGGER.warning(
|
|
221
|
-
|
|
236
|
+
"Snapshot verification disabled for paths: %s", skip_verification_paths
|
|
222
237
|
)
|
|
223
238
|
|
|
224
239
|
if self.update:
|
|
@@ -306,7 +321,7 @@ class SnapshotSession:
|
|
|
306
321
|
try:
|
|
307
322
|
replaced_tmp[key] = json.loads(dumped_value)
|
|
308
323
|
except JSONDecodeError:
|
|
309
|
-
SNAPSHOT_LOGGER.error(
|
|
324
|
+
SNAPSHOT_LOGGER.error("could not decode json-string:\n%s", tmp)
|
|
310
325
|
return {}
|
|
311
326
|
|
|
312
327
|
return replaced_tmp
|
|
@@ -365,6 +380,23 @@ class SnapshotSession:
|
|
|
365
380
|
|
|
366
381
|
return full_path_nodes[::-1][1:] # reverse the list and remove Root()/$
|
|
367
382
|
|
|
383
|
+
def _remove_placeholder(_tmp):
|
|
384
|
+
"""Traverse the object and remove any values in a list that would be equal to the placeholder"""
|
|
385
|
+
if isinstance(_tmp, dict):
|
|
386
|
+
for k, v in _tmp.items():
|
|
387
|
+
if isinstance(v, dict):
|
|
388
|
+
_remove_placeholder(v)
|
|
389
|
+
elif isinstance(v, list):
|
|
390
|
+
_tmp[k] = _remove_placeholder(v)
|
|
391
|
+
elif isinstance(_tmp, list):
|
|
392
|
+
return [
|
|
393
|
+
_remove_placeholder(item) for item in _tmp if item != _SKIP_PLACEHOLDER_VALUE
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
return _tmp
|
|
397
|
+
|
|
398
|
+
has_placeholder = False
|
|
399
|
+
|
|
368
400
|
for path in self.skip_verification_paths:
|
|
369
401
|
matches = parse(path).find(tmp) or []
|
|
370
402
|
for m in matches:
|
|
@@ -378,7 +410,24 @@ class SnapshotSession:
|
|
|
378
410
|
helper = helper.get(p, None)
|
|
379
411
|
if not helper:
|
|
380
412
|
continue
|
|
413
|
+
|
|
381
414
|
if (
|
|
382
415
|
isinstance(helper, dict) and full_path[-1] in helper.keys()
|
|
383
416
|
): # might have been deleted already
|
|
384
417
|
del helper[full_path[-1]]
|
|
418
|
+
elif isinstance(helper, list):
|
|
419
|
+
try:
|
|
420
|
+
index = int(full_path[-1].lstrip("[").rstrip("]"))
|
|
421
|
+
# we need to set a placeholder value as the skips are based on index
|
|
422
|
+
# if we are to pop the values, the next skip index will have shifted and won't be correct
|
|
423
|
+
helper[index] = _SKIP_PLACEHOLDER_VALUE
|
|
424
|
+
has_placeholder = True
|
|
425
|
+
except ValueError:
|
|
426
|
+
SNAPSHOT_LOGGER.warning(
|
|
427
|
+
"Snapshot skip path '%s' was not applied as it was invalid for that snapshot",
|
|
428
|
+
path,
|
|
429
|
+
exc_info=SNAPSHOT_LOGGER.isEnabledFor(logging.DEBUG),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
if has_placeholder:
|
|
433
|
+
_remove_placeholder(tmp)
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/report.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import re
|
|
2
3
|
|
|
3
4
|
from localstack_snapshot.snapshots import SnapshotMatchResult
|
|
4
5
|
|
|
@@ -29,6 +30,8 @@ _esctable = {
|
|
|
29
30
|
"underlined": 4,
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
_regular_json_path_chars_regex = re.compile("[a-zA-Z0-9_-]+")
|
|
34
|
+
|
|
32
35
|
|
|
33
36
|
class PatchPath(str):
|
|
34
37
|
"""
|
|
@@ -52,7 +55,11 @@ def _format_json_path(path: list):
|
|
|
52
55
|
json_str = "$.."
|
|
53
56
|
for idx, elem in enumerate(path):
|
|
54
57
|
if not isinstance(elem, int):
|
|
55
|
-
|
|
58
|
+
_elem = str(elem)
|
|
59
|
+
# we want to wrap in single quotes parts with special characters so that users can copy-paste them directly
|
|
60
|
+
if not _regular_json_path_chars_regex.fullmatch(_elem):
|
|
61
|
+
_elem = f"'{_elem}'"
|
|
62
|
+
json_str += _elem
|
|
56
63
|
if idx < len(path) - 1 and not json_str.endswith(".."):
|
|
57
64
|
json_str += "."
|
|
58
65
|
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/transformer.py
RENAMED
|
@@ -102,8 +102,14 @@ class ResponseMetaDataTransformer:
|
|
|
102
102
|
if k == "ResponseMetadata":
|
|
103
103
|
metadata = v
|
|
104
104
|
http_headers = metadata.get("HTTPHeaders")
|
|
105
|
+
if not isinstance(http_headers, dict):
|
|
106
|
+
continue
|
|
107
|
+
|
|
105
108
|
# TODO "x-amz-bucket-region"
|
|
106
109
|
# TestS3.test_region_header_exists -> verifies bucket-region
|
|
110
|
+
|
|
111
|
+
# FIXME: proper value is `content-type` with no underscore in lowercase, but this will necessitate a
|
|
112
|
+
# refresh of all snapshots
|
|
107
113
|
headers_to_collect = ["content_type"]
|
|
108
114
|
simplified_headers = {}
|
|
109
115
|
for h in headers_to_collect:
|
|
@@ -7,7 +7,7 @@ name = "localstack-snapshot"
|
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "LocalStack Contributors", email = "info@localstack.cloud" }
|
|
9
9
|
]
|
|
10
|
-
version = "0.3.
|
|
10
|
+
version = "0.3.2"
|
|
11
11
|
description = "Extracted snapshot testing lib for LocalStack"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"jsonpath-ng>1.6",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import io
|
|
2
|
+
from enum import Enum
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
4
5
|
|
|
@@ -75,6 +76,67 @@ class TestSnapshotManager:
|
|
|
75
76
|
sm.match_object("key_a", CustomObject(name="myname"))
|
|
76
77
|
sm._assert_all()
|
|
77
78
|
|
|
79
|
+
def test_match_object_lists_and_iterators(self):
|
|
80
|
+
class CustomObject:
|
|
81
|
+
def __init__(self, name):
|
|
82
|
+
self.name = name
|
|
83
|
+
self.my_list = [9, 8, 7, 6, 5]
|
|
84
|
+
self.my_iterator = (x for x in range(5))
|
|
85
|
+
|
|
86
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
87
|
+
sm.recorded_state = {
|
|
88
|
+
"key_a": {"name": "myname", "my_iterator": [0, 1, 2, 3, 4], "my_list": [9, 8, 7, 6, 5]}
|
|
89
|
+
}
|
|
90
|
+
sm.match_object("key_a", CustomObject(name="myname"))
|
|
91
|
+
sm._assert_all()
|
|
92
|
+
|
|
93
|
+
def test_match_object_include_properties(self):
|
|
94
|
+
class CustomObject:
|
|
95
|
+
def __init__(self, name):
|
|
96
|
+
self.name = name
|
|
97
|
+
self._internal = "n/a"
|
|
98
|
+
|
|
99
|
+
def some_method(self):
|
|
100
|
+
# method should not be serialized
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def some_prop(self):
|
|
105
|
+
# properties should be serialized
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def some_iterator(self):
|
|
110
|
+
for i in range(3):
|
|
111
|
+
yield i
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def _private_prop(self):
|
|
115
|
+
# private properties should be ignored
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
119
|
+
sm.recorded_state = {
|
|
120
|
+
"key_a": {"name": "myname", "some_prop": True, "some_iterator": [0, 1, 2]}
|
|
121
|
+
}
|
|
122
|
+
sm.match_object("key_a", CustomObject(name="myname"))
|
|
123
|
+
sm._assert_all()
|
|
124
|
+
|
|
125
|
+
def test_match_object_enums(self):
|
|
126
|
+
class TestEnum(Enum):
|
|
127
|
+
value1 = "Value 1"
|
|
128
|
+
value2 = "Value 2"
|
|
129
|
+
|
|
130
|
+
class CustomObject:
|
|
131
|
+
def __init__(self, name):
|
|
132
|
+
self.name = name
|
|
133
|
+
self.my_enum = TestEnum.value2
|
|
134
|
+
|
|
135
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
136
|
+
sm.recorded_state = {"key_a": {"name": "myname", "my_enum": "Value 2"}}
|
|
137
|
+
sm.match_object("key_a", CustomObject(name="myname"))
|
|
138
|
+
sm._assert_all()
|
|
139
|
+
|
|
78
140
|
def test_match_object_change(self):
|
|
79
141
|
class CustomObject:
|
|
80
142
|
def __init__(self, name):
|
|
@@ -191,6 +253,137 @@ class TestSnapshotManager:
|
|
|
191
253
|
sm.match("key1", [{"key2": "value1"}, "value2", 3])
|
|
192
254
|
sm._assert_all()
|
|
193
255
|
|
|
256
|
+
def test_list_as_last_node_in_skip_verification_path(self):
|
|
257
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
258
|
+
sm.recorded_state = {"key_a": {"aaa": ["item1", "item2", "item3"]}}
|
|
259
|
+
sm.match(
|
|
260
|
+
"key_a",
|
|
261
|
+
{"aaa": ["item1", "different-value"]},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
with pytest.raises(Exception) as ctx: # asserts it fail without skipping
|
|
265
|
+
sm._assert_all()
|
|
266
|
+
ctx.match("Parity snapshot failed")
|
|
267
|
+
|
|
268
|
+
skip_path = ["$..aaa[1]", "$..aaa[2]"]
|
|
269
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
270
|
+
|
|
271
|
+
skip_path = ["$..aaa.1", "$..aaa.2"]
|
|
272
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
273
|
+
|
|
274
|
+
def test_list_as_last_node_in_skip_verification_path_complex(self):
|
|
275
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
276
|
+
sm.recorded_state = {
|
|
277
|
+
"key_a": {
|
|
278
|
+
"aaa": [
|
|
279
|
+
{"aab": ["aac", "aad"]},
|
|
280
|
+
{"aab": ["aac", "aad"]},
|
|
281
|
+
{"aab": ["aac", "aad"]},
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
sm.match(
|
|
286
|
+
"key_a",
|
|
287
|
+
{
|
|
288
|
+
"aaa": [
|
|
289
|
+
{"aab": ["aac", "bad-value"], "bbb": "value"},
|
|
290
|
+
{"aab": ["aac", "aad", "bad-value"]},
|
|
291
|
+
{"aab": ["bad-value", "aad"]},
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
with pytest.raises(Exception) as ctx: # asserts it fail without skipping
|
|
297
|
+
sm._assert_all()
|
|
298
|
+
ctx.match("Parity snapshot failed")
|
|
299
|
+
|
|
300
|
+
skip_path = [
|
|
301
|
+
"$..aaa[0].aab[1]",
|
|
302
|
+
"$..aaa[0].bbb",
|
|
303
|
+
"$..aaa[1].aab[2]",
|
|
304
|
+
"$..aaa[2].aab[0]",
|
|
305
|
+
]
|
|
306
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
307
|
+
|
|
308
|
+
skip_path = [
|
|
309
|
+
"$..aaa.0..aab.1",
|
|
310
|
+
"$..aaa.0..bbb",
|
|
311
|
+
"$..aaa.1..aab.2",
|
|
312
|
+
"$..aaa.2..aab.0",
|
|
313
|
+
]
|
|
314
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
315
|
+
|
|
316
|
+
def test_list_as_mid_node_in_skip_verification_path(self):
|
|
317
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
318
|
+
sm.recorded_state = {"key_a": {"aaa": [{"aab": "value1"}, {"aab": "value2"}]}}
|
|
319
|
+
sm.match(
|
|
320
|
+
"key_a",
|
|
321
|
+
{"aaa": [{"aab": "value1"}, {"aab": "bad-value"}]},
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
with pytest.raises(Exception) as ctx: # asserts it fail without skipping
|
|
325
|
+
sm._assert_all()
|
|
326
|
+
ctx.match("Parity snapshot failed")
|
|
327
|
+
|
|
328
|
+
skip_path = ["$..aaa[1].aab"]
|
|
329
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
330
|
+
|
|
331
|
+
skip_path = ["$..aaa.1.aab"]
|
|
332
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
333
|
+
|
|
334
|
+
def test_list_as_last_node_in_skip_verification_path_nested(self):
|
|
335
|
+
sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False)
|
|
336
|
+
sm.recorded_state = {
|
|
337
|
+
"key_a": {
|
|
338
|
+
"aaa": [
|
|
339
|
+
"bbb",
|
|
340
|
+
"ccc",
|
|
341
|
+
[
|
|
342
|
+
"ddd",
|
|
343
|
+
"eee",
|
|
344
|
+
[
|
|
345
|
+
"fff",
|
|
346
|
+
"ggg",
|
|
347
|
+
],
|
|
348
|
+
],
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
sm.match(
|
|
353
|
+
"key_a",
|
|
354
|
+
{
|
|
355
|
+
"aaa": [
|
|
356
|
+
"bbb",
|
|
357
|
+
"ccc",
|
|
358
|
+
[
|
|
359
|
+
"bad-value",
|
|
360
|
+
"eee",
|
|
361
|
+
[
|
|
362
|
+
"fff",
|
|
363
|
+
"ggg",
|
|
364
|
+
],
|
|
365
|
+
],
|
|
366
|
+
]
|
|
367
|
+
},
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
with pytest.raises(Exception) as ctx: # asserts it fail without skipping
|
|
371
|
+
sm._assert_all()
|
|
372
|
+
ctx.match("Parity snapshot failed")
|
|
373
|
+
|
|
374
|
+
skip_path = ["$..aaa[2][0]"]
|
|
375
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
376
|
+
|
|
377
|
+
skip_path = ["$..aaa.2[0]"]
|
|
378
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
379
|
+
|
|
380
|
+
# these 2 will actually skip almost everything, as they will match every first element of any list inside `aaa`
|
|
381
|
+
skip_path = ["$..aaa..[0]"]
|
|
382
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
383
|
+
|
|
384
|
+
skip_path = ["$..aaa..0"]
|
|
385
|
+
sm._assert_all(skip_verification_paths=skip_path)
|
|
386
|
+
|
|
194
387
|
|
|
195
388
|
def test_json_diff_format():
|
|
196
389
|
path = ["Records", 1]
|
|
@@ -209,6 +402,14 @@ def test_json_diff_format():
|
|
|
209
402
|
assert _format_json_path(path) == '"$.."'
|
|
210
403
|
path = [1, 1, 0, "SomeKey"]
|
|
211
404
|
assert _format_json_path(path) == '"$..SomeKey"'
|
|
405
|
+
path = ["Some:Key"]
|
|
406
|
+
assert _format_json_path(path) == "\"$..'Some:Key'\""
|
|
407
|
+
path = ["Some.Key"]
|
|
408
|
+
assert _format_json_path(path) == "\"$..'Some.Key'\""
|
|
409
|
+
path = ["Some-Key"]
|
|
410
|
+
assert _format_json_path(path) == '"$..Some-Key"'
|
|
411
|
+
path = ["Some0Key"]
|
|
412
|
+
assert _format_json_path(path) == '"$..Some0Key"'
|
|
212
413
|
|
|
213
414
|
|
|
214
415
|
def test_sorting_transformer():
|
|
@@ -5,6 +5,7 @@ import pytest
|
|
|
5
5
|
|
|
6
6
|
from localstack_snapshot.snapshots.transformer import (
|
|
7
7
|
JsonStringTransformer,
|
|
8
|
+
ResponseMetaDataTransformer,
|
|
8
9
|
SortingTransformer,
|
|
9
10
|
TimestampTransformer,
|
|
10
11
|
TransformContext,
|
|
@@ -405,3 +406,81 @@ class TestTimestampTransformer:
|
|
|
405
406
|
ctx = TransformContext()
|
|
406
407
|
output = transformer.transform(input, ctx=ctx)
|
|
407
408
|
assert output == expected
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class TestResponseMetaDataTransformer:
|
|
412
|
+
def test_with_headers(self):
|
|
413
|
+
input_data = {"ResponseMetadata": {"HTTPHeaders": {"header1": "value1"}}}
|
|
414
|
+
|
|
415
|
+
metadata_transformer = ResponseMetaDataTransformer()
|
|
416
|
+
|
|
417
|
+
expected_key_value = {"ResponseMetadata": {"HTTPHeaders": {}}}
|
|
418
|
+
|
|
419
|
+
copied = copy.deepcopy(input_data)
|
|
420
|
+
ctx = TransformContext()
|
|
421
|
+
assert metadata_transformer.transform(copied, ctx=ctx) == expected_key_value
|
|
422
|
+
assert ctx.serialized_replacements == []
|
|
423
|
+
|
|
424
|
+
def test_with_headers_and_status_code(self):
|
|
425
|
+
input_data = {
|
|
426
|
+
"ResponseMetadata": {"HTTPHeaders": {"header1": "value1"}, "HTTPStatusCode": 500}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
metadata_transformer = ResponseMetaDataTransformer()
|
|
430
|
+
|
|
431
|
+
expected_key_value = {"ResponseMetadata": {"HTTPHeaders": {}, "HTTPStatusCode": 500}}
|
|
432
|
+
|
|
433
|
+
copied = copy.deepcopy(input_data)
|
|
434
|
+
ctx = TransformContext()
|
|
435
|
+
assert metadata_transformer.transform(copied, ctx=ctx) == expected_key_value
|
|
436
|
+
assert ctx.serialized_replacements == []
|
|
437
|
+
|
|
438
|
+
def test_with_status_code_only(self):
|
|
439
|
+
input_data = {"ResponseMetadata": {"HTTPStatusCode": 500, "RandomData": "random"}}
|
|
440
|
+
|
|
441
|
+
metadata_transformer = ResponseMetaDataTransformer()
|
|
442
|
+
|
|
443
|
+
expected_key_value = {"ResponseMetadata": {"HTTPStatusCode": 500, "RandomData": "random"}}
|
|
444
|
+
|
|
445
|
+
copied = copy.deepcopy(input_data)
|
|
446
|
+
ctx = TransformContext()
|
|
447
|
+
assert metadata_transformer.transform(copied, ctx=ctx) == expected_key_value
|
|
448
|
+
assert ctx.serialized_replacements == []
|
|
449
|
+
|
|
450
|
+
def test_with_empty_response_metadata(self):
|
|
451
|
+
input_data = {"ResponseMetadata": {"NotHeaders": "data"}}
|
|
452
|
+
|
|
453
|
+
metadata_transformer = ResponseMetaDataTransformer()
|
|
454
|
+
|
|
455
|
+
expected_key_value = {"ResponseMetadata": {"NotHeaders": "data"}}
|
|
456
|
+
|
|
457
|
+
copied = copy.deepcopy(input_data)
|
|
458
|
+
ctx = TransformContext()
|
|
459
|
+
assert metadata_transformer.transform(copied, ctx=ctx) == expected_key_value
|
|
460
|
+
assert ctx.serialized_replacements == []
|
|
461
|
+
|
|
462
|
+
def test_with_headers_wrong_type(self):
|
|
463
|
+
input_data = {"ResponseMetadata": {"HTTPHeaders": "data"}}
|
|
464
|
+
|
|
465
|
+
metadata_transformer = ResponseMetaDataTransformer()
|
|
466
|
+
|
|
467
|
+
expected_key_value = {"ResponseMetadata": {"HTTPHeaders": "data"}}
|
|
468
|
+
|
|
469
|
+
copied = copy.deepcopy(input_data)
|
|
470
|
+
ctx = TransformContext()
|
|
471
|
+
assert metadata_transformer.transform(copied, ctx=ctx) == expected_key_value
|
|
472
|
+
assert ctx.serialized_replacements == []
|
|
473
|
+
|
|
474
|
+
def test_headers_filtering(self):
|
|
475
|
+
input_data = {
|
|
476
|
+
"ResponseMetadata": {"HTTPHeaders": {"content_type": "value1", "header1": "value1"}}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
metadata_transformer = ResponseMetaDataTransformer()
|
|
480
|
+
|
|
481
|
+
expected_key_value = {"ResponseMetadata": {"HTTPHeaders": {"content_type": "value1"}}}
|
|
482
|
+
|
|
483
|
+
copied = copy.deepcopy(input_data)
|
|
484
|
+
ctx = TransformContext()
|
|
485
|
+
assert metadata_transformer.transform(copied, ctx=ctx) == expected_key_value
|
|
486
|
+
assert ctx.serialized_replacements == []
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.0"
|
|
File without changes
|
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/pytest/__init__.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/pytest/snapshot.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/snapshots/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/util/__init__.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot/util/encoding.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/requires.txt
RENAMED
|
File without changes
|
{localstack_snapshot-0.3.0 → localstack_snapshot-0.3.2}/localstack_snapshot.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|