localstack-snapshot 0.2.0__tar.gz → 0.3.0__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.2.0 → localstack_snapshot-0.3.0}/PKG-INFO +13 -3
- localstack_snapshot-0.3.0/README.md +35 -0
- localstack_snapshot-0.3.0/localstack_snapshot/__init__.py +1 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/prototype.py +2 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/transformer.py +100 -8
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/transformer_utility.py +55 -1
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/PKG-INFO +13 -3
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/pyproject.toml +1 -1
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/tests/test_transformer.py +127 -0
- localstack_snapshot-0.2.0/README.md +0 -26
- localstack_snapshot-0.2.0/localstack_snapshot/__init__.py +0 -1
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/LICENSE +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/__init__.py +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/snapshot.py +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/__init__.py +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/report.py +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/util/__init__.py +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/util/encoding.py +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/SOURCES.txt +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/dependency_links.txt +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/requires.txt +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/top_level.txt +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/setup.cfg +0 -0
- {localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/tests/test_snapshots.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: localstack-snapshot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Extracted snapshot testing lib for LocalStack
|
|
5
5
|
Author-email: LocalStack Contributors <info@localstack.cloud>
|
|
6
6
|
License:
|
|
@@ -222,6 +222,7 @@ Requires-Dist: black==23.10.0; extra == "dev"
|
|
|
222
222
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
223
223
|
Requires-Dist: coverage[toml]>=5.0.0; extra == "dev"
|
|
224
224
|
Requires-Dist: ruff==0.1.0; extra == "dev"
|
|
225
|
+
Dynamic: license-file
|
|
225
226
|
|
|
226
227
|
Snapshot testing for pytest
|
|
227
228
|
===============================
|
|
@@ -233,10 +234,19 @@ This project is in a very early stage and will be both restructured and renamed.
|
|
|
233
234
|
|
|
234
235
|
## Quickstart
|
|
235
236
|
|
|
236
|
-
|
|
237
|
+
To install the python and other developer requirements into a venv run:
|
|
237
238
|
|
|
238
239
|
make install
|
|
239
240
|
|
|
241
|
+
### Configuration options
|
|
242
|
+
|
|
243
|
+
There's a few env vars that can be used with this project:
|
|
244
|
+
|
|
245
|
+
* `TEST_TARGET`: Set to `AWS_CLOUD` to use an externally-deployed instance when running tests.
|
|
246
|
+
* `SNAPSHOT_LEGACY_REPORT`: By default set to `0`. Can be set to `1`. This enables the legacy reporting output style.
|
|
247
|
+
* `SNAPSHOT_UDPATE`: By default set to `0`. Can be set to `1`. This enables updating the snapshot file rather than comparing with its contents.
|
|
248
|
+
* `SNAPSHOT_RAW`: By default set to `0`. Can be set to `1`. This outputs an additional snapshot file which contains the untransformed values.
|
|
249
|
+
|
|
240
250
|
## Format code
|
|
241
251
|
|
|
242
252
|
We use black and isort as code style tools.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Snapshot testing for pytest
|
|
2
|
+
===============================
|
|
3
|
+
|
|
4
|
+
Extracted snapshot testing lib for LocalStack.
|
|
5
|
+
|
|
6
|
+
This project is in a very early stage and will be both restructured and renamed.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
To install the python and other developer requirements into a venv run:
|
|
12
|
+
|
|
13
|
+
make install
|
|
14
|
+
|
|
15
|
+
### Configuration options
|
|
16
|
+
|
|
17
|
+
There's a few env vars that can be used with this project:
|
|
18
|
+
|
|
19
|
+
* `TEST_TARGET`: Set to `AWS_CLOUD` to use an externally-deployed instance when running tests.
|
|
20
|
+
* `SNAPSHOT_LEGACY_REPORT`: By default set to `0`. Can be set to `1`. This enables the legacy reporting output style.
|
|
21
|
+
* `SNAPSHOT_UDPATE`: By default set to `0`. Can be set to `1`. This enables updating the snapshot file rather than comparing with its contents.
|
|
22
|
+
* `SNAPSHOT_RAW`: By default set to `0`. Can be set to `1`. This outputs an additional snapshot file which contains the untransformed values.
|
|
23
|
+
|
|
24
|
+
## Format code
|
|
25
|
+
|
|
26
|
+
We use black and isort as code style tools.
|
|
27
|
+
To execute them, run:
|
|
28
|
+
|
|
29
|
+
make format
|
|
30
|
+
|
|
31
|
+
## Build distribution
|
|
32
|
+
|
|
33
|
+
To build a wheel and source distribution, simply run
|
|
34
|
+
|
|
35
|
+
make dist
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/prototype.py
RENAMED
|
@@ -272,6 +272,8 @@ class SnapshotSession:
|
|
|
272
272
|
self._transform_dict_to_parseable_values(v)
|
|
273
273
|
|
|
274
274
|
if isinstance(v, str) and v.startswith("{"):
|
|
275
|
+
# Doesn't handle JSON arrays and nested JSON strings. See JsonStringTransformer.
|
|
276
|
+
# TODO for the major release consider having JSON parsing in one place only: either here or in JsonStringTransformer
|
|
275
277
|
try:
|
|
276
278
|
json_value = json.loads(v)
|
|
277
279
|
original[k] = json_value
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/transformer.py
RENAMED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import copy
|
|
2
|
+
import json
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
5
6
|
from datetime import datetime
|
|
7
|
+
from json import JSONDecodeError
|
|
6
8
|
from re import Pattern
|
|
7
9
|
from typing import Any, Callable, Optional, Protocol
|
|
8
10
|
|
|
@@ -184,35 +186,36 @@ class RegexTransformer:
|
|
|
184
186
|
return input_data
|
|
185
187
|
|
|
186
188
|
|
|
187
|
-
class
|
|
189
|
+
class KeyValueBasedTransformerFunctionReplacement:
|
|
188
190
|
def __init__(
|
|
189
191
|
self,
|
|
190
192
|
match_fn: Callable[[str, Any], Optional[str]],
|
|
191
|
-
|
|
193
|
+
replacement_function: [Callable[[str, Any], str]],
|
|
192
194
|
replace_reference: bool = True,
|
|
193
195
|
):
|
|
194
196
|
self.match_fn = match_fn
|
|
195
|
-
self.
|
|
197
|
+
self.replacement_function = replacement_function
|
|
196
198
|
self.replace_reference = replace_reference
|
|
197
199
|
|
|
198
200
|
def transform(self, input_data: dict, *, ctx: TransformContext) -> dict:
|
|
199
201
|
for k, v in input_data.items():
|
|
200
202
|
if (match_result := self.match_fn(k, v)) is not None:
|
|
203
|
+
replacement = self.replacement_function(k, v)
|
|
201
204
|
if self.replace_reference:
|
|
202
205
|
_register_serialized_reference_replacement(
|
|
203
|
-
ctx, reference_value=match_result, replacement=
|
|
206
|
+
ctx, reference_value=match_result, replacement=replacement
|
|
204
207
|
)
|
|
205
208
|
else:
|
|
206
209
|
if isinstance(v, str):
|
|
207
210
|
SNAPSHOT_LOGGER.debug(
|
|
208
|
-
f"Replacing value for key '{k}': Match result '{match_result:.200s}' with '{
|
|
211
|
+
f"Replacing value for key '{k}': Match result '{match_result:.200s}' with '{replacement}'. (Original value: {str(v)})"
|
|
209
212
|
)
|
|
210
|
-
input_data[k] = v.replace(match_result,
|
|
213
|
+
input_data[k] = v.replace(match_result, replacement)
|
|
211
214
|
else:
|
|
212
215
|
SNAPSHOT_LOGGER.debug(
|
|
213
|
-
f"Replacing value for key '{k}' with '{
|
|
216
|
+
f"Replacing value for key '{k}' with '{replacement}'. (Original value: {str(v)})"
|
|
214
217
|
)
|
|
215
|
-
input_data[k] =
|
|
218
|
+
input_data[k] = replacement
|
|
216
219
|
elif isinstance(v, list) and len(v) > 0:
|
|
217
220
|
for i in range(0, len(v)):
|
|
218
221
|
if isinstance(v[i], dict):
|
|
@@ -223,6 +226,20 @@ class KeyValueBasedTransformer:
|
|
|
223
226
|
return input_data
|
|
224
227
|
|
|
225
228
|
|
|
229
|
+
class KeyValueBasedTransformer(KeyValueBasedTransformerFunctionReplacement):
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
match_fn: Callable[[str, Any], Optional[str]],
|
|
233
|
+
replacement: str,
|
|
234
|
+
replace_reference: bool = True,
|
|
235
|
+
):
|
|
236
|
+
super().__init__(
|
|
237
|
+
match_fn=match_fn,
|
|
238
|
+
replacement_function=lambda k, v: replacement,
|
|
239
|
+
replace_reference=replace_reference,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
226
243
|
class GenericTransformer:
|
|
227
244
|
def __init__(self, fn: Callable[[dict, TransformContext], dict]):
|
|
228
245
|
self.fn = fn
|
|
@@ -360,3 +377,78 @@ class TextTransformer:
|
|
|
360
377
|
f"Registering text pattern '{self.text}' in snapshot with '{self.replacement}'"
|
|
361
378
|
)
|
|
362
379
|
return input_data
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class JsonStringTransformer:
|
|
383
|
+
"""
|
|
384
|
+
Parses JSON string at the specified key.
|
|
385
|
+
Additionally, attempts to parse any JSON strings inside the parsed JSON
|
|
386
|
+
|
|
387
|
+
This transformer complements the default parsing of JSON strings in
|
|
388
|
+
localstack_snapshot.snapshots.prototype.SnapshotSession._transform_dict_to_parseable_values
|
|
389
|
+
|
|
390
|
+
Shortcomings of the default parser that this transformer addresses:
|
|
391
|
+
- parsing of nested JSON strings '{"a": "{\\"b\\":42}"}'
|
|
392
|
+
- parsing of JSON arrays at the specified key, e.g. '["a", "b"]'
|
|
393
|
+
|
|
394
|
+
Such parsing allows applying transformations further to the elements of the parsed JSON - timestamps, ARNs, etc.
|
|
395
|
+
|
|
396
|
+
Such parsing is not done by default because it's not a common use case.
|
|
397
|
+
Whether to parse a JSON string or not should be decided by the user on a case by case basis.
|
|
398
|
+
Limited general parsing that we already have is preserved for backwards compatibility.
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
key: str
|
|
402
|
+
|
|
403
|
+
def __init__(self, key: str):
|
|
404
|
+
self.key = key
|
|
405
|
+
|
|
406
|
+
def transform(self, input_data: dict, *, ctx: TransformContext = None) -> dict:
|
|
407
|
+
return self._transform_dict(input_data, ctx=ctx)
|
|
408
|
+
|
|
409
|
+
def _transform(self, input_data: Any, ctx: TransformContext = None) -> Any:
|
|
410
|
+
if isinstance(input_data, dict):
|
|
411
|
+
return self._transform_dict(input_data, ctx=ctx)
|
|
412
|
+
elif isinstance(input_data, list):
|
|
413
|
+
return self._transform_list(input_data, ctx=ctx)
|
|
414
|
+
return input_data
|
|
415
|
+
|
|
416
|
+
def _transform_dict(self, input_data: dict, ctx: TransformContext = None) -> dict:
|
|
417
|
+
for k, v in input_data.items():
|
|
418
|
+
if k == self.key and isinstance(v, str) and v.strip().startswith(("{", "[")):
|
|
419
|
+
try:
|
|
420
|
+
SNAPSHOT_LOGGER.debug(f"Replacing string value of {k} with parsed JSON")
|
|
421
|
+
json_value = json.loads(v)
|
|
422
|
+
input_data[k] = self._transform_nested(json_value)
|
|
423
|
+
except JSONDecodeError:
|
|
424
|
+
SNAPSHOT_LOGGER.exception(
|
|
425
|
+
f'Value mapped to "{k}" key is not a valid JSON string and won\'t be transformed. Value: {v}'
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
input_data[k] = self._transform(v, ctx=ctx)
|
|
429
|
+
return input_data
|
|
430
|
+
|
|
431
|
+
def _transform_list(self, input_data: list, ctx: TransformContext = None) -> list:
|
|
432
|
+
return [self._transform(item, ctx=ctx) for item in input_data]
|
|
433
|
+
|
|
434
|
+
def _transform_nested(self, input_data: Any) -> Any:
|
|
435
|
+
"""
|
|
436
|
+
Separate method from the main `_transform_dict` one because
|
|
437
|
+
it checks every string while the main one attempts to load at specified key only.
|
|
438
|
+
This one is implicit, best-effort attempt,
|
|
439
|
+
while the main one is explicit about at which key transform should happen
|
|
440
|
+
"""
|
|
441
|
+
if isinstance(input_data, list):
|
|
442
|
+
input_data = [self._transform_nested(item) for item in input_data]
|
|
443
|
+
if isinstance(input_data, dict):
|
|
444
|
+
for k, v in input_data.items():
|
|
445
|
+
input_data[k] = self._transform_nested(v)
|
|
446
|
+
if isinstance(input_data, str) and input_data.strip().startswith(("{", "[")):
|
|
447
|
+
try:
|
|
448
|
+
json_value = json.loads(input_data)
|
|
449
|
+
input_data = self._transform_nested(json_value)
|
|
450
|
+
except JSONDecodeError:
|
|
451
|
+
SNAPSHOT_LOGGER.debug(
|
|
452
|
+
f"The value is not a valid JSON string and won't be transformed. The value: {input_data}"
|
|
453
|
+
)
|
|
454
|
+
return input_data
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from re import Pattern
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Any, Callable, Optional
|
|
3
3
|
|
|
4
4
|
from localstack_snapshot.snapshots.transformer import (
|
|
5
5
|
JsonpathTransformer,
|
|
6
|
+
JsonStringTransformer,
|
|
6
7
|
KeyValueBasedTransformer,
|
|
8
|
+
KeyValueBasedTransformerFunctionReplacement,
|
|
7
9
|
RegexTransformer,
|
|
10
|
+
SortingTransformer,
|
|
8
11
|
TextTransformer,
|
|
9
12
|
)
|
|
10
13
|
|
|
@@ -38,6 +41,33 @@ class TransformerUtility:
|
|
|
38
41
|
replace_reference=reference_replacement,
|
|
39
42
|
)
|
|
40
43
|
|
|
44
|
+
@staticmethod
|
|
45
|
+
def key_value_replacement_function(
|
|
46
|
+
key: str,
|
|
47
|
+
replacement_function: Callable[[str, Any], str] = None,
|
|
48
|
+
reference_replacement: bool = True,
|
|
49
|
+
):
|
|
50
|
+
"""Creates a new KeyValueBasedTransformer. If the key matches, the value will be replaced.
|
|
51
|
+
|
|
52
|
+
:param key: the name of the key which should be replaced
|
|
53
|
+
:param replacement_function: The function calculating the replacement. Will be passed the key and value of the replaced pair.
|
|
54
|
+
By default it is the key-name in lowercase, separated with hyphen
|
|
55
|
+
:param reference_replacement: if False, only the original value for this key will be replaced.
|
|
56
|
+
If True all references of this value will be replaced (using a regex pattern), for the entire test case.
|
|
57
|
+
In this case, the replaced value will be nummerated as well.
|
|
58
|
+
Default: True
|
|
59
|
+
|
|
60
|
+
:return: KeyValueBasedTransformer
|
|
61
|
+
"""
|
|
62
|
+
replacement_function = replacement_function or (
|
|
63
|
+
lambda x, y: _replace_camel_string_with_hyphen(key)
|
|
64
|
+
)
|
|
65
|
+
return KeyValueBasedTransformerFunctionReplacement(
|
|
66
|
+
lambda k, v: v if k == key and (v is not None and v != "") else None,
|
|
67
|
+
replacement_function=replacement_function,
|
|
68
|
+
replace_reference=reference_replacement,
|
|
69
|
+
)
|
|
70
|
+
|
|
41
71
|
@staticmethod
|
|
42
72
|
def jsonpath(jsonpath: str, value_replacement: str, reference_replacement: bool = True):
|
|
43
73
|
"""Creates a new JsonpathTransformer. If the jsonpath matches, the value will be replaced.
|
|
@@ -81,3 +111,27 @@ class TransformerUtility:
|
|
|
81
111
|
:return: TextTransformer
|
|
82
112
|
"""
|
|
83
113
|
return TextTransformer(text, replacement)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def json_string(key: str) -> JsonStringTransformer:
|
|
117
|
+
"""Creates a new JsonStringTransformer. If there is a valid JSON text string at specified key
|
|
118
|
+
it will be loaded as a regular object or array.
|
|
119
|
+
|
|
120
|
+
:param key: key at which JSON string is expected
|
|
121
|
+
|
|
122
|
+
:return: JsonStringTransformer
|
|
123
|
+
"""
|
|
124
|
+
return JsonStringTransformer(key)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def sorting(key: str, sorting_fn: Optional[Callable[[...], Any]]) -> SortingTransformer:
|
|
128
|
+
"""Creates a new SortingTransformer.
|
|
129
|
+
|
|
130
|
+
Sorts a list at `key` with the given `sorting_fn` (argument for `sorted(list, key=sorting_fn)`)
|
|
131
|
+
|
|
132
|
+
:param key: key at which the list to sort is expected
|
|
133
|
+
:param sorting_fn: sorting function
|
|
134
|
+
|
|
135
|
+
:return: SortingTransformer
|
|
136
|
+
"""
|
|
137
|
+
return SortingTransformer(key, sorting_fn)
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: localstack-snapshot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Extracted snapshot testing lib for LocalStack
|
|
5
5
|
Author-email: LocalStack Contributors <info@localstack.cloud>
|
|
6
6
|
License:
|
|
@@ -222,6 +222,7 @@ Requires-Dist: black==23.10.0; extra == "dev"
|
|
|
222
222
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
223
223
|
Requires-Dist: coverage[toml]>=5.0.0; extra == "dev"
|
|
224
224
|
Requires-Dist: ruff==0.1.0; extra == "dev"
|
|
225
|
+
Dynamic: license-file
|
|
225
226
|
|
|
226
227
|
Snapshot testing for pytest
|
|
227
228
|
===============================
|
|
@@ -233,10 +234,19 @@ This project is in a very early stage and will be both restructured and renamed.
|
|
|
233
234
|
|
|
234
235
|
## Quickstart
|
|
235
236
|
|
|
236
|
-
|
|
237
|
+
To install the python and other developer requirements into a venv run:
|
|
237
238
|
|
|
238
239
|
make install
|
|
239
240
|
|
|
241
|
+
### Configuration options
|
|
242
|
+
|
|
243
|
+
There's a few env vars that can be used with this project:
|
|
244
|
+
|
|
245
|
+
* `TEST_TARGET`: Set to `AWS_CLOUD` to use an externally-deployed instance when running tests.
|
|
246
|
+
* `SNAPSHOT_LEGACY_REPORT`: By default set to `0`. Can be set to `1`. This enables the legacy reporting output style.
|
|
247
|
+
* `SNAPSHOT_UDPATE`: By default set to `0`. Can be set to `1`. This enables updating the snapshot file rather than comparing with its contents.
|
|
248
|
+
* `SNAPSHOT_RAW`: By default set to `0`. Can be set to `1`. This outputs an additional snapshot file which contains the untransformed values.
|
|
249
|
+
|
|
240
250
|
## Format code
|
|
241
251
|
|
|
242
252
|
We use black and isort as code style tools.
|
|
@@ -7,7 +7,7 @@ name = "localstack-snapshot"
|
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "LocalStack Contributors", email = "info@localstack.cloud" }
|
|
9
9
|
]
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.3.0"
|
|
11
11
|
description = "Extracted snapshot testing lib for LocalStack"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"jsonpath-ng>1.6",
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from localstack_snapshot.snapshots.transformer import (
|
|
7
|
+
JsonStringTransformer,
|
|
7
8
|
SortingTransformer,
|
|
8
9
|
TimestampTransformer,
|
|
9
10
|
TransformContext,
|
|
@@ -50,6 +51,81 @@ class TestTransformer:
|
|
|
50
51
|
|
|
51
52
|
assert json.loads(tmp) == expected_key_value_reference
|
|
52
53
|
|
|
54
|
+
def test_key_value_replacement_custom_function(self):
|
|
55
|
+
input = {
|
|
56
|
+
"hello": "12characters",
|
|
57
|
+
"hello2": "again",
|
|
58
|
+
"path": {
|
|
59
|
+
"to": {
|
|
60
|
+
"anotherkey": "hi",
|
|
61
|
+
"twelvesymbol": {"hello": "twelvesymbol"},
|
|
62
|
+
"fifteen_symbols": {"hello": "fifteen_symbols"},
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
key_value = TransformerUtility.key_value_replacement_function(
|
|
68
|
+
"hello",
|
|
69
|
+
replacement_function=lambda k, v: f"placeholder({len(v)})",
|
|
70
|
+
reference_replacement=False,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
expected_key_value = {
|
|
74
|
+
"hello": "placeholder(12)",
|
|
75
|
+
"hello2": "again",
|
|
76
|
+
"path": {
|
|
77
|
+
"to": {
|
|
78
|
+
"anotherkey": "hi",
|
|
79
|
+
"twelvesymbol": {"hello": "placeholder(12)"},
|
|
80
|
+
"fifteen_symbols": {"hello": "placeholder(15)"},
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ctx = TransformContext()
|
|
86
|
+
assert key_value.transform(input, ctx=ctx) == expected_key_value
|
|
87
|
+
assert ctx.serialized_replacements == []
|
|
88
|
+
|
|
89
|
+
def test_key_value_replacement_custom_function_reference_replacement(self):
|
|
90
|
+
input = {
|
|
91
|
+
"hello": "12characters",
|
|
92
|
+
"hello2": "again",
|
|
93
|
+
"path": {
|
|
94
|
+
"to": {
|
|
95
|
+
"anotherkey": "hi",
|
|
96
|
+
"twelvesymbol": {"hello": "twelvesymbol"},
|
|
97
|
+
"fifteen_symbols": {"hello": "fifteen_symbols"},
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
key_value = TransformerUtility.key_value_replacement_function(
|
|
103
|
+
"hello",
|
|
104
|
+
replacement_function=lambda k, v: f"placeholder({len(v)})",
|
|
105
|
+
reference_replacement=True,
|
|
106
|
+
)
|
|
107
|
+
# replacement counters are per replacement key, so it will start from 1 again.
|
|
108
|
+
expected_key_value_reference = {
|
|
109
|
+
"hello": "<placeholder(12):1>",
|
|
110
|
+
"hello2": "again",
|
|
111
|
+
"path": {
|
|
112
|
+
"to": {
|
|
113
|
+
"anotherkey": "hi",
|
|
114
|
+
"<placeholder(12):2>": {"hello": "<placeholder(12):2>"},
|
|
115
|
+
"<placeholder(15):1>": {"hello": "<placeholder(15):1>"},
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
ctx = TransformContext()
|
|
120
|
+
assert key_value.transform(input, ctx=ctx) == input
|
|
121
|
+
assert len(ctx.serialized_replacements) == 3
|
|
122
|
+
|
|
123
|
+
tmp = json.dumps(input, default=str)
|
|
124
|
+
for sr in ctx.serialized_replacements:
|
|
125
|
+
tmp = sr(tmp)
|
|
126
|
+
|
|
127
|
+
assert json.loads(tmp) == expected_key_value_reference
|
|
128
|
+
|
|
53
129
|
def test_key_value_replacement_with_falsy_value(self):
|
|
54
130
|
input = {
|
|
55
131
|
"hello": "world",
|
|
@@ -236,6 +312,57 @@ class TestTransformer:
|
|
|
236
312
|
output = sr(output)
|
|
237
313
|
assert json.loads(output) == expected
|
|
238
314
|
|
|
315
|
+
@pytest.mark.parametrize(
|
|
316
|
+
"input_value,transformed_value",
|
|
317
|
+
[
|
|
318
|
+
pytest.param('{"a": "b"}', {"a": "b"}, id="simple_json_object"),
|
|
319
|
+
pytest.param('{\n "a": "b"\n}', {"a": "b"}, id="formatted_json_object"),
|
|
320
|
+
pytest.param('\n {"a": "b"}', {"a": "b"}, id="json_with_whitespaces"),
|
|
321
|
+
pytest.param('{"a": 42}malformed', '{"a": 42}malformed', id="malformed_json"),
|
|
322
|
+
pytest.param('["a", "b"]', ["a", "b"], id="simple_json_list"),
|
|
323
|
+
pytest.param('{"a": "{\\"b\\":42}"}', {"a": {"b": 42}}, id="nested_json_object"),
|
|
324
|
+
pytest.param(
|
|
325
|
+
'{"a": "\\n {\\n \\"b\\":42}"}',
|
|
326
|
+
{"a": {"b": 42}},
|
|
327
|
+
id="nested_formatted_json_object_with_whitespaces",
|
|
328
|
+
),
|
|
329
|
+
pytest.param(
|
|
330
|
+
'{"a": "[{\\"b\\":\\"c\\"}]"}', {"a": [{"b": "c"}]}, id="nested_json_list"
|
|
331
|
+
),
|
|
332
|
+
pytest.param(
|
|
333
|
+
'{"a": "{\\"b\\":42malformed}"}',
|
|
334
|
+
{"a": '{"b":42malformed}'},
|
|
335
|
+
id="malformed_nested_json",
|
|
336
|
+
),
|
|
337
|
+
pytest.param("[]", [], id="empty_list"),
|
|
338
|
+
pytest.param("{}", {}, id="empty_object"),
|
|
339
|
+
pytest.param("", "", id="empty_string"),
|
|
340
|
+
],
|
|
341
|
+
)
|
|
342
|
+
def test_json_string(self, input_value, transformed_value):
|
|
343
|
+
key = "key"
|
|
344
|
+
input_data = {key: input_value}
|
|
345
|
+
expected = {key: transformed_value}
|
|
346
|
+
|
|
347
|
+
transformer = JsonStringTransformer(key)
|
|
348
|
+
|
|
349
|
+
ctx = TransformContext()
|
|
350
|
+
output = transformer.transform(input_data, ctx=ctx)
|
|
351
|
+
|
|
352
|
+
assert output == expected
|
|
353
|
+
|
|
354
|
+
def test_json_string_in_a_nested_key(self):
|
|
355
|
+
key = "nested-key-in-an-object-hidden-inside-a-list"
|
|
356
|
+
input_data = {"top-level-key": [{key: '{"a": "b"}'}]}
|
|
357
|
+
expected = {"top-level-key": [{key: {"a": "b"}}]}
|
|
358
|
+
|
|
359
|
+
transformer = JsonStringTransformer(key)
|
|
360
|
+
|
|
361
|
+
ctx = TransformContext()
|
|
362
|
+
output = transformer.transform(input_data, ctx=ctx)
|
|
363
|
+
|
|
364
|
+
assert output == expected
|
|
365
|
+
|
|
239
366
|
|
|
240
367
|
class TestTimestampTransformer:
|
|
241
368
|
def test_generic_timestamp_transformer(self):
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
Snapshot testing for pytest
|
|
2
|
-
===============================
|
|
3
|
-
|
|
4
|
-
Extracted snapshot testing lib for LocalStack.
|
|
5
|
-
|
|
6
|
-
This project is in a very early stage and will be both restructured and renamed.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Quickstart
|
|
10
|
-
|
|
11
|
-
to install the python and other developer requirements into a venv run:
|
|
12
|
-
|
|
13
|
-
make install
|
|
14
|
-
|
|
15
|
-
## Format code
|
|
16
|
-
|
|
17
|
-
We use black and isort as code style tools.
|
|
18
|
-
To execute them, run:
|
|
19
|
-
|
|
20
|
-
make format
|
|
21
|
-
|
|
22
|
-
## Build distribution
|
|
23
|
-
|
|
24
|
-
To build a wheel and source distribution, simply run
|
|
25
|
-
|
|
26
|
-
make dist
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.0"
|
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/__init__.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/pytest/snapshot.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/__init__.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/snapshots/report.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/util/__init__.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot/util/encoding.py
RENAMED
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/requires.txt
RENAMED
|
File without changes
|
{localstack_snapshot-0.2.0 → localstack_snapshot-0.3.0}/localstack_snapshot.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|