deltafi 1.0.5__py3-none-any.whl → 1.0.7__py3-none-any.whl
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.
Potentially problematic release.
This version of deltafi might be problematic. Click here for more details.
- deltafi/domain.py +5 -1
- deltafi/plugin.py +1 -1
- deltafi/result.py +40 -10
- deltafi/test_kit/__init__.py +17 -0
- deltafi/test_kit/assertions.py +47 -0
- deltafi/test_kit/compare_helpers.py +49 -0
- deltafi/test_kit/constants.py +23 -0
- deltafi/test_kit/format.py +99 -0
- deltafi/test_kit/framework.py +326 -0
- deltafi/test_kit/load.py +121 -0
- deltafi/test_kit/transform.py +71 -0
- {deltafi-1.0.5.dist-info → deltafi-1.0.7.dist-info}/METADATA +2 -1
- deltafi-1.0.7.dist-info/RECORD +23 -0
- deltafi-1.0.5.dist-info/RECORD +0 -15
- {deltafi-1.0.5.dist-info → deltafi-1.0.7.dist-info}/WHEEL +0 -0
deltafi/domain.py
CHANGED
|
@@ -25,6 +25,7 @@ from deltafi.storage import ContentService, Segment
|
|
|
25
25
|
|
|
26
26
|
class Context(NamedTuple):
|
|
27
27
|
did: str
|
|
28
|
+
action_flow: str
|
|
28
29
|
action_name: str
|
|
29
30
|
source_filename: str
|
|
30
31
|
ingress_flow: str
|
|
@@ -37,7 +38,9 @@ class Context(NamedTuple):
|
|
|
37
38
|
@classmethod
|
|
38
39
|
def create(cls, context: dict, hostname: str, content_service: ContentService, logger: Logger):
|
|
39
40
|
did = context['did']
|
|
40
|
-
|
|
41
|
+
action_name_parts = context['name'].split(".")
|
|
42
|
+
action_flow = action_name_parts[0]
|
|
43
|
+
action_name = action_name_parts[1]
|
|
41
44
|
if 'sourceFilename' in context:
|
|
42
45
|
source_filename = context['sourceFilename']
|
|
43
46
|
else:
|
|
@@ -49,6 +52,7 @@ class Context(NamedTuple):
|
|
|
49
52
|
egress_flow = None
|
|
50
53
|
system = context['systemName']
|
|
51
54
|
return Context(did=did,
|
|
55
|
+
action_flow=action_flow,
|
|
52
56
|
action_name=action_name,
|
|
53
57
|
source_filename=source_filename,
|
|
54
58
|
ingress_flow=ingress_flow,
|
deltafi/plugin.py
CHANGED
|
@@ -270,7 +270,7 @@ class Plugin(object):
|
|
|
270
270
|
|
|
271
271
|
response = {
|
|
272
272
|
'did': event.context.did,
|
|
273
|
-
'action': event.context.action_name,
|
|
273
|
+
'action': event.context.action_flow + "." + event.context.action_name,
|
|
274
274
|
'start': start_time,
|
|
275
275
|
'stop': time.time(),
|
|
276
276
|
'type': result.result_type,
|
deltafi/result.py
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
#
|
|
18
18
|
|
|
19
19
|
import abc
|
|
20
|
-
from typing import Dict, List
|
|
21
20
|
import uuid
|
|
21
|
+
from typing import Dict, List
|
|
22
22
|
|
|
23
23
|
from deltafi.domain import Content, Context
|
|
24
24
|
from deltafi.metric import Metric
|
|
@@ -123,40 +123,66 @@ class FormatResult(Result):
|
|
|
123
123
|
def __init__(self, context: Context):
|
|
124
124
|
super().__init__('format', 'FORMAT', context)
|
|
125
125
|
self.content = None
|
|
126
|
+
self.delete_metadata_keys = []
|
|
126
127
|
self.metadata = {}
|
|
127
128
|
|
|
128
129
|
def add_metadata(self, key: str, value: str):
|
|
129
130
|
self.metadata[key] = value
|
|
130
131
|
return self
|
|
131
132
|
|
|
133
|
+
def delete_metadata_key(self, key: str):
|
|
134
|
+
self.delete_metadata_keys.append(key)
|
|
135
|
+
return self
|
|
136
|
+
|
|
132
137
|
def set_content(self, content: Content):
|
|
133
138
|
self.content = content
|
|
134
139
|
return self
|
|
135
140
|
|
|
136
141
|
def save_string_content(self, string_data: str, name: str, media_type: str):
|
|
137
142
|
segment = self.context.content_service.put_str(self.context.did, string_data)
|
|
138
|
-
self.content = Content(name=name, segments=[segment], media_type=media_type,
|
|
143
|
+
self.content = Content(name=name, segments=[segment], media_type=media_type,
|
|
144
|
+
content_service=self.context.content_service)
|
|
139
145
|
return self
|
|
140
146
|
|
|
141
147
|
def save_byte_content(self, byte_data: bytes, name: str, media_type: str):
|
|
142
148
|
segment = self.context.content_service.put_bytes(self.context.did, byte_data)
|
|
143
|
-
self.content = Content(name=name, segments=[segment], media_type=media_type,
|
|
149
|
+
self.content = Content(name=name, segments=[segment], media_type=media_type,
|
|
150
|
+
content_service=self.context.content_service)
|
|
144
151
|
return self
|
|
145
152
|
|
|
146
153
|
def response(self):
|
|
147
154
|
return {
|
|
148
155
|
'content': self.content.json(),
|
|
149
|
-
'metadata': self.metadata
|
|
156
|
+
'metadata': self.metadata,
|
|
157
|
+
'deleteMetadataKeys': self.delete_metadata_keys
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
|
|
161
|
+
class ChildFormatResult:
|
|
162
|
+
def __init__(self, format_result: FormatResult = None):
|
|
163
|
+
self._did = str(uuid.uuid4())
|
|
164
|
+
self.format_result = format_result
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def did(self):
|
|
168
|
+
return self._did
|
|
169
|
+
|
|
170
|
+
def response(self):
|
|
171
|
+
res = self.format_result.response()
|
|
172
|
+
res["did"] = self._did
|
|
173
|
+
return res
|
|
174
|
+
|
|
175
|
+
|
|
153
176
|
class FormatManyResult(Result):
|
|
154
177
|
def __init__(self, context: Context):
|
|
155
178
|
super().__init__('formatMany', 'FORMAT_MANY', context)
|
|
156
179
|
self.format_results = []
|
|
157
180
|
|
|
158
|
-
def add_format_result(self, format_result
|
|
159
|
-
|
|
181
|
+
def add_format_result(self, format_result):
|
|
182
|
+
if isinstance(format_result, ChildFormatResult):
|
|
183
|
+
self.format_results.append(format_result)
|
|
184
|
+
else:
|
|
185
|
+
self.format_results.append(ChildFormatResult(format_result))
|
|
160
186
|
return self
|
|
161
187
|
|
|
162
188
|
def response(self):
|
|
@@ -184,12 +210,14 @@ class LoadResult(Result):
|
|
|
184
210
|
|
|
185
211
|
def save_string_content(self, string_data: str, name: str, media_type: str):
|
|
186
212
|
segment = self.context.content_service.put_str(self.context.did, string_data)
|
|
187
|
-
self.content.append(
|
|
213
|
+
self.content.append(
|
|
214
|
+
Content(name=name, segments=[segment], media_type=media_type, content_service=self.context.content_service))
|
|
188
215
|
return self
|
|
189
216
|
|
|
190
217
|
def save_byte_content(self, byte_data: bytes, name: str, media_type: str):
|
|
191
218
|
segment = self.context.content_service.put_bytes(self.context.did, byte_data)
|
|
192
|
-
self.content.append(
|
|
219
|
+
self.content.append(
|
|
220
|
+
Content(name=name, segments=[segment], media_type=media_type, content_service=self.context.content_service))
|
|
193
221
|
return self
|
|
194
222
|
|
|
195
223
|
def add_metadata(self, key: str, value: str):
|
|
@@ -300,12 +328,14 @@ class TransformResult(Result):
|
|
|
300
328
|
|
|
301
329
|
def save_string_content(self, string_data: str, name: str, media_type: str):
|
|
302
330
|
segment = self.context.content_service.put_str(self.context.did, string_data)
|
|
303
|
-
self.content.append(
|
|
331
|
+
self.content.append(
|
|
332
|
+
Content(name=name, segments=[segment], media_type=media_type, content_service=self.context.content_service))
|
|
304
333
|
return self
|
|
305
334
|
|
|
306
335
|
def save_byte_content(self, byte_data: bytes, name: str, media_type: str):
|
|
307
336
|
segment = self.context.content_service.put_bytes(self.context.did, byte_data)
|
|
308
|
-
self.content.append(
|
|
337
|
+
self.content.append(
|
|
338
|
+
Content(name=name, segments=[segment], media_type=media_type, content_service=self.context.content_service))
|
|
309
339
|
return self
|
|
310
340
|
|
|
311
341
|
def add_metadata(self, key: str, value: str):
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
from typing import Dict
|
|
19
|
+
|
|
20
|
+
from .constants import IGNORE_VALUE
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def assert_equal(e, a):
|
|
24
|
+
assert e == a, f"{e} != {a}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def assert_equal_with_label(e, a, l):
|
|
28
|
+
assert e == a, f"{l}. Expected:\n<<{e}>>\nBut was:\n<<{a}>>"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def assert_equal_len(e, a):
|
|
32
|
+
assert len(e) == len(a), f"{len(e)} != {len(a)}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def assert_key_in(k, m):
|
|
36
|
+
assert k in m, f"{k} not found"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def assert_key_not_in(k, m):
|
|
40
|
+
assert k not in m, f"{k} found, but not expected"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def assert_keys_and_values(expected: Dict, actual: Dict):
|
|
44
|
+
for key in expected:
|
|
45
|
+
assert_key_in(key, actual)
|
|
46
|
+
if expected[key] != IGNORE_VALUE:
|
|
47
|
+
assert_equal(expected[key], actual[key])
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
import json
|
|
19
|
+
from abc import ABC
|
|
20
|
+
from abc import abstractmethod
|
|
21
|
+
from typing import List
|
|
22
|
+
|
|
23
|
+
from deepdiff import DeepDiff
|
|
24
|
+
|
|
25
|
+
from .assertions import *
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CompareHelper(ABC):
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def compare(self, expected: str, actual: str, label: str):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GenericCompareHelper(CompareHelper):
|
|
35
|
+
def compare(self, expected: str, actual: str, label: str):
|
|
36
|
+
assert_equal_with_label(expected, actual, label)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class JsonCompareHelper(CompareHelper):
|
|
40
|
+
def __init__(self, regex_exclusion_list: List):
|
|
41
|
+
self.excludes = regex_exclusion_list
|
|
42
|
+
|
|
43
|
+
def compare(self, expected: str, actual: str, label: str):
|
|
44
|
+
exp = json.loads(expected)
|
|
45
|
+
act = json.loads(actual)
|
|
46
|
+
diffs = DeepDiff(exp, act, exclude_regex_paths=self.excludes)
|
|
47
|
+
if len(diffs) > 0:
|
|
48
|
+
raise ValueError(f"{diffs}")
|
|
49
|
+
assert len(diffs) == 0
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
INGRESS_FLOW = "ingress-flow"
|
|
20
|
+
EGRESS_FLOW = "egress-flow"
|
|
21
|
+
HOSTNAME = "HOSTNAME"
|
|
22
|
+
SYSTEM = "SYSTEM"
|
|
23
|
+
IGNORE_VALUE = "%%IGNORE%%"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
from typing import List
|
|
19
|
+
|
|
20
|
+
from deltafi.result import FormatResult, FormatManyResult
|
|
21
|
+
|
|
22
|
+
from .assertions import *
|
|
23
|
+
from .framework import TestCaseBase, ActionTest
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FormatTestCase(TestCaseBase):
|
|
27
|
+
def __init__(self, fields: Dict):
|
|
28
|
+
super().__init__(fields)
|
|
29
|
+
self.metadata = {}
|
|
30
|
+
self.delete_metadata_keys = []
|
|
31
|
+
self.expected_format_many_result = []
|
|
32
|
+
|
|
33
|
+
def expect_format_result(self, metadata: Dict, delete_metadata_keys: List[str]):
|
|
34
|
+
self.expected_result_type = FormatResult
|
|
35
|
+
self.metadata = metadata
|
|
36
|
+
self.delete_metadata_keys = delete_metadata_keys
|
|
37
|
+
|
|
38
|
+
def add_format_many_result(self, metadata: Dict, delete_metadata_keys: List):
|
|
39
|
+
self.expected_result_type = FormatManyResult
|
|
40
|
+
self.expected_format_many_result.append(
|
|
41
|
+
{
|
|
42
|
+
"metadata": metadata,
|
|
43
|
+
"delete_metadata_keys": delete_metadata_keys
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FormatActionTest(ActionTest):
|
|
49
|
+
def __init__(self, package_name: str):
|
|
50
|
+
"""
|
|
51
|
+
Provides structure for testing DeltaFi Format action
|
|
52
|
+
Args:
|
|
53
|
+
package_name: name of the actions package for finding resources
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(package_name)
|
|
56
|
+
|
|
57
|
+
def format(self, test_case: FormatTestCase):
|
|
58
|
+
if test_case.expected_result_type == FormatManyResult:
|
|
59
|
+
self.expect_format_many_result(test_case)
|
|
60
|
+
elif test_case.expected_result_type == FormatResult:
|
|
61
|
+
self.expect_format_result(test_case)
|
|
62
|
+
else:
|
|
63
|
+
super().execute(test_case)
|
|
64
|
+
|
|
65
|
+
def expect_format_result(self, test_case: FormatTestCase):
|
|
66
|
+
result = super().run_and_check_result_type(test_case, FormatResult)
|
|
67
|
+
self.assert_format_result(test_case, result)
|
|
68
|
+
|
|
69
|
+
def expect_format_many_result(self, test_case: FormatTestCase):
|
|
70
|
+
result = super().run_and_check_result_type(test_case, FormatManyResult)
|
|
71
|
+
self.assert_format_many_result(test_case, result)
|
|
72
|
+
|
|
73
|
+
def assert_format_result(self, test_case: FormatTestCase, result: FormatResult):
|
|
74
|
+
# Check output
|
|
75
|
+
if result.content is None:
|
|
76
|
+
self.compare_all_output(test_case.compare_tool, [])
|
|
77
|
+
else:
|
|
78
|
+
self.compare_all_output(test_case.compare_tool, [result.content])
|
|
79
|
+
|
|
80
|
+
# Check metadata
|
|
81
|
+
assert_keys_and_values(test_case.metadata, result.metadata)
|
|
82
|
+
|
|
83
|
+
# Check deleted metadata
|
|
84
|
+
for key in test_case.delete_metadata_keys:
|
|
85
|
+
assert_key_in(key, result.delete_metadata_keys)
|
|
86
|
+
|
|
87
|
+
def assert_format_many_result(self, test_case: FormatTestCase, actual: FormatManyResult):
|
|
88
|
+
assert_equal_len(test_case.expected_format_many_result, actual.format_results)
|
|
89
|
+
for index, expected_child_result in enumerate(test_case.expected_format_many_result):
|
|
90
|
+
actual_child = actual.format_results[index]
|
|
91
|
+
self.compare_one_content(test_case.compare_tool,
|
|
92
|
+
self.expected_outputs[index],
|
|
93
|
+
actual_child.format_result.content,
|
|
94
|
+
index)
|
|
95
|
+
|
|
96
|
+
assert_keys_and_values(expected_child_result['metadata'],
|
|
97
|
+
actual_child.format_result.metadata)
|
|
98
|
+
for key in expected_child_result['delete_metadata_keys']:
|
|
99
|
+
assert_key_in(key, actual_child.format_result.delete_metadata_keys)
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
import uuid
|
|
19
|
+
from abc import ABC
|
|
20
|
+
from importlib.resources import files
|
|
21
|
+
from typing import List
|
|
22
|
+
|
|
23
|
+
from deltafi.domain import DeltaFileMessage, Event, Content, Context
|
|
24
|
+
from deltafi.logger import get_logger
|
|
25
|
+
from deltafi.result import ErrorResult, FilterResult
|
|
26
|
+
from deltafi.storage import Segment
|
|
27
|
+
|
|
28
|
+
from .assertions import *
|
|
29
|
+
from .compare_helpers import GenericCompareHelper, CompareHelper
|
|
30
|
+
from .constants import *
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class IOContent:
|
|
34
|
+
"""
|
|
35
|
+
The IOContent class holds the details for loading input or output
|
|
36
|
+
content into the test framework.
|
|
37
|
+
Attributes:
|
|
38
|
+
file_name (str): The name of file in test/data.
|
|
39
|
+
content_name (str): The name of the content.
|
|
40
|
+
content_type (str): The media type of the content
|
|
41
|
+
offset (int): Offset to use in Segment
|
|
42
|
+
content_bytes (str): Bypass file read, and uses these bytes for content
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, file_name: str, content_name: str = None,
|
|
46
|
+
content_type: str = None, offset: int = 0,
|
|
47
|
+
content_bytes: str = ""):
|
|
48
|
+
self.file_name = file_name
|
|
49
|
+
if content_name is None:
|
|
50
|
+
self.content_name = file_name
|
|
51
|
+
else:
|
|
52
|
+
self.content_name = content_name
|
|
53
|
+
if content_type is None:
|
|
54
|
+
self.content_type = IOContent.file_type(file_name)
|
|
55
|
+
else:
|
|
56
|
+
self.content_type = content_type
|
|
57
|
+
self.offset = offset
|
|
58
|
+
self.content_bytes = content_bytes
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def file_type(cls, name: str):
|
|
62
|
+
if name.endswith(".json"):
|
|
63
|
+
return "application/json"
|
|
64
|
+
elif name.endswith(".xml"):
|
|
65
|
+
return "application/xnl"
|
|
66
|
+
elif name.endswith(".txt"):
|
|
67
|
+
return "text/plain"
|
|
68
|
+
else:
|
|
69
|
+
return "application/octet-stream"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LoadedContent:
|
|
73
|
+
def __init__(self, did: str, ioc: IOContent, data: str):
|
|
74
|
+
self.name = ioc.content_name
|
|
75
|
+
self.content_type = ioc.content_type
|
|
76
|
+
self.offset = ioc.offset
|
|
77
|
+
if data is not None:
|
|
78
|
+
self.data = data
|
|
79
|
+
else:
|
|
80
|
+
self.data = ioc.content_bytes
|
|
81
|
+
self.segment = Segment.from_dict({
|
|
82
|
+
"uuid": str(uuid.uuid4()),
|
|
83
|
+
"offset": self.offset,
|
|
84
|
+
"size": len(self.data),
|
|
85
|
+
"did": did
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class InternalContentService:
|
|
90
|
+
def __init__(self):
|
|
91
|
+
self.loaded_content = {}
|
|
92
|
+
self.outputs = {}
|
|
93
|
+
|
|
94
|
+
def load(self, content_list: List[LoadedContent]):
|
|
95
|
+
for c in content_list:
|
|
96
|
+
self.loaded_content[c.segment.uuid] = c
|
|
97
|
+
|
|
98
|
+
def put_str(self, did: str, string_data: str):
|
|
99
|
+
segment = Segment(uuid=str(uuid.uuid4()),
|
|
100
|
+
offset=0,
|
|
101
|
+
size=len(string_data),
|
|
102
|
+
did=did)
|
|
103
|
+
self.outputs[segment.uuid] = string_data
|
|
104
|
+
return segment
|
|
105
|
+
|
|
106
|
+
def get_str(self, segments: List[Segment]):
|
|
107
|
+
# TODO: String multiple segment ids together
|
|
108
|
+
seg_id = segments[0].uuid
|
|
109
|
+
return self.loaded_content[seg_id].data
|
|
110
|
+
|
|
111
|
+
def get_output(self, seg_id: str):
|
|
112
|
+
return self.outputs[seg_id]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TestCaseBase(ABC):
|
|
116
|
+
def __init__(self, data: Dict):
|
|
117
|
+
"""
|
|
118
|
+
A test case for DeltaFi python actions
|
|
119
|
+
:param data: Dict of test case fields
|
|
120
|
+
- action: instance of the action being tested
|
|
121
|
+
- data_dir: str: subdirectory name (e.g., test name) for locating test data files, i.e., test/data/{data_dir)
|
|
122
|
+
- compare_tool: (optional) CompareHelper instanced for comparing output content
|
|
123
|
+
- inputs: (optional) List[IOContent]: input content to action
|
|
124
|
+
- parameters: (optional) Dict: map of action input parameters
|
|
125
|
+
- in_meta: (optional) Dict: map of metadata as input to action
|
|
126
|
+
- in_domains: (optional) List[Domain]: list of domains as input to action
|
|
127
|
+
- in_enrichments: (optional) List[Domain]: list of enrichments as input to action
|
|
128
|
+
"""
|
|
129
|
+
if "action" in data:
|
|
130
|
+
self.action = data["action"]
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError("action is required")
|
|
133
|
+
|
|
134
|
+
if "data_dir" in data:
|
|
135
|
+
self.data_dir = data["data_dir"]
|
|
136
|
+
else:
|
|
137
|
+
raise ValueError("data_dir is required")
|
|
138
|
+
|
|
139
|
+
if "compare_tool" in data:
|
|
140
|
+
self.compare_tool = data["compare_tool"]
|
|
141
|
+
else:
|
|
142
|
+
self.compare_tool = GenericCompareHelper()
|
|
143
|
+
|
|
144
|
+
self.inputs = data["inputs"] if "inputs" in data else []
|
|
145
|
+
self.file_name = data["file_name"] if "file_name" in data else "filename"
|
|
146
|
+
self.outputs = data["outputs"] if "outputs" in data else []
|
|
147
|
+
self.parameters = data["parameters"] if "parameters" in data else {}
|
|
148
|
+
self.in_meta = data["in_meta"] if "in_meta" in data else {}
|
|
149
|
+
self.in_domains = data["in_domains"] if "in_domains" in data else []
|
|
150
|
+
self.in_enrichments = data["in_enrichments"] if "in_enrichments" in data else []
|
|
151
|
+
self.expected_result_type = None
|
|
152
|
+
self.cause_regex = None
|
|
153
|
+
self.context_regex = None
|
|
154
|
+
|
|
155
|
+
def expect_error_result(self, cause: str, context: str):
|
|
156
|
+
"""
|
|
157
|
+
A Sets the expected output of the action to an Error Result
|
|
158
|
+
:param cause: the expected error cause
|
|
159
|
+
:param context: the expected error context
|
|
160
|
+
"""
|
|
161
|
+
self.expected_result_type = ErrorResult
|
|
162
|
+
self.cause_regex = cause
|
|
163
|
+
self.context_regex = context
|
|
164
|
+
|
|
165
|
+
def expect_filter_result(self, cause: str):
|
|
166
|
+
"""
|
|
167
|
+
A Sets the expected output of the action to a Filter Result
|
|
168
|
+
:param cause: the expected filter cause (message)
|
|
169
|
+
"""
|
|
170
|
+
self.expected_result_type = FilterResult
|
|
171
|
+
self.cause_regex = cause
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ActionTest(ABC):
|
|
175
|
+
def __init__(self, package_name: str):
|
|
176
|
+
"""
|
|
177
|
+
Provides structure for testing DeltaFi actions
|
|
178
|
+
Args:
|
|
179
|
+
package_name: name of the actions package for finding resources
|
|
180
|
+
"""
|
|
181
|
+
self.content_service = InternalContentService()
|
|
182
|
+
self.did = ""
|
|
183
|
+
self.expected_outputs = []
|
|
184
|
+
self.loaded_inputs = []
|
|
185
|
+
self.package_name = package_name
|
|
186
|
+
self.res_path = ""
|
|
187
|
+
|
|
188
|
+
def __reset__(self):
|
|
189
|
+
self.content_service = InternalContentService()
|
|
190
|
+
self.did = str(uuid.uuid4())
|
|
191
|
+
self.expected_outputs = []
|
|
192
|
+
self.loaded_inputs = []
|
|
193
|
+
self.res_path = ""
|
|
194
|
+
|
|
195
|
+
def load_file(self, ioc: IOContent):
|
|
196
|
+
file_res = self.res_path.joinpath(ioc.file_name)
|
|
197
|
+
with file_res.open("r") as f:
|
|
198
|
+
contents = f.read()
|
|
199
|
+
return contents
|
|
200
|
+
|
|
201
|
+
def get_contents(self, test_case: TestCaseBase):
|
|
202
|
+
pkg_path = files(self.package_name)
|
|
203
|
+
self.res_path = pkg_path.joinpath(f"test/data/{test_case.data_dir}/")
|
|
204
|
+
|
|
205
|
+
# Load inputs
|
|
206
|
+
for input_ioc in test_case.inputs:
|
|
207
|
+
if len(input_ioc.content_bytes) == 0:
|
|
208
|
+
self.loaded_inputs.append(LoadedContent(self.did, input_ioc, self.load_file(input_ioc)))
|
|
209
|
+
else:
|
|
210
|
+
self.loaded_inputs.append(LoadedContent(self.did, input_ioc, None))
|
|
211
|
+
|
|
212
|
+
# Load expected outputs
|
|
213
|
+
for output_ioc in test_case.outputs:
|
|
214
|
+
if len(output_ioc.content_bytes) == 0:
|
|
215
|
+
self.expected_outputs.append(LoadedContent(self.did, output_ioc, self.load_file(output_ioc)))
|
|
216
|
+
else:
|
|
217
|
+
self.expected_outputs.append(LoadedContent(self.did, output_ioc, None))
|
|
218
|
+
|
|
219
|
+
def make_content_list(self, test_case: TestCaseBase):
|
|
220
|
+
content_list = []
|
|
221
|
+
for loaded_input in self.loaded_inputs:
|
|
222
|
+
c = Content(name=loaded_input.name,
|
|
223
|
+
segments=[loaded_input.segment],
|
|
224
|
+
media_type=loaded_input.content_type,
|
|
225
|
+
content_service=self.content_service)
|
|
226
|
+
content_list.append(c)
|
|
227
|
+
loaded_input.content = c
|
|
228
|
+
|
|
229
|
+
return content_list
|
|
230
|
+
|
|
231
|
+
def make_df_msg(self, test_case: TestCaseBase):
|
|
232
|
+
content_list = self.make_content_list(test_case)
|
|
233
|
+
self.content_service.load(self.loaded_inputs)
|
|
234
|
+
|
|
235
|
+
return DeltaFileMessage(metadata=test_case.in_meta,
|
|
236
|
+
content_list=content_list,
|
|
237
|
+
domains=test_case.in_domains,
|
|
238
|
+
enrichments=test_case.in_enrichments)
|
|
239
|
+
|
|
240
|
+
def make_context(self, test_case: TestCaseBase):
|
|
241
|
+
action_name = INGRESS_FLOW + "." + test_case.action.__class__.__name__
|
|
242
|
+
return Context(did=self.did,
|
|
243
|
+
action_flow=INGRESS_FLOW,
|
|
244
|
+
action_name=action_name,
|
|
245
|
+
source_filename=test_case.file_name,
|
|
246
|
+
ingress_flow=INGRESS_FLOW,
|
|
247
|
+
egress_flow=EGRESS_FLOW,
|
|
248
|
+
system=SYSTEM,
|
|
249
|
+
hostname=HOSTNAME,
|
|
250
|
+
content_service=self.content_service,
|
|
251
|
+
logger=get_logger())
|
|
252
|
+
|
|
253
|
+
def make_event(self, test_case: TestCaseBase):
|
|
254
|
+
return Event(
|
|
255
|
+
delta_file_messages=[self.make_df_msg(test_case)],
|
|
256
|
+
context=self.make_context(test_case),
|
|
257
|
+
params=test_case.parameters,
|
|
258
|
+
queue_name="",
|
|
259
|
+
return_address="")
|
|
260
|
+
|
|
261
|
+
def call_action(self, test_case: TestCaseBase):
|
|
262
|
+
self.get_contents(test_case)
|
|
263
|
+
return test_case.action.execute(self.make_event(test_case))
|
|
264
|
+
|
|
265
|
+
def run_and_check_result_type(self, test_case: TestCaseBase, result_type):
|
|
266
|
+
self.__reset__()
|
|
267
|
+
result = self.call_action(test_case)
|
|
268
|
+
|
|
269
|
+
if not isinstance(result, result_type):
|
|
270
|
+
raise ValueError(f"Result type {result.__class__.__name__} does not match {result_type.__name__}")
|
|
271
|
+
|
|
272
|
+
return result
|
|
273
|
+
|
|
274
|
+
def execute_error(self, test_case: TestCaseBase):
|
|
275
|
+
result = self.run_and_check_result_type(test_case, ErrorResult)
|
|
276
|
+
resp = result.response()
|
|
277
|
+
assert resp['cause'] == test_case.cause_regex
|
|
278
|
+
assert resp['context'] == test_case.context_regex
|
|
279
|
+
|
|
280
|
+
def execute_filter(self, test_case):
|
|
281
|
+
result = self.run_and_check_result_type(test_case, FilterResult)
|
|
282
|
+
resp = result.response()
|
|
283
|
+
assert resp['message'] == test_case.cause_regex
|
|
284
|
+
|
|
285
|
+
def execute(self, test_case: TestCaseBase):
|
|
286
|
+
if isinstance(test_case.expected_result_type, ErrorResult.__class__):
|
|
287
|
+
self.execute_error(test_case)
|
|
288
|
+
elif isinstance(test_case.expected_result_type, FilterResult.__class__):
|
|
289
|
+
self.execute_filter(test_case)
|
|
290
|
+
else:
|
|
291
|
+
raise ValueError(f"unknown type: {test_case.expected_result_type}")
|
|
292
|
+
|
|
293
|
+
def compare_content_details(self, expected: LoadedContent, actual: Content):
|
|
294
|
+
assert_equal(expected.content_type, actual.media_type)
|
|
295
|
+
assert_equal(expected.name, actual.name)
|
|
296
|
+
|
|
297
|
+
def compare_one_content(self, comparitor: CompareHelper, expected: LoadedContent, actual, index):
|
|
298
|
+
self.compare_content_details(expected, actual)
|
|
299
|
+
seg_id = actual.segments[0].uuid
|
|
300
|
+
comparitor.compare(
|
|
301
|
+
expected.data,
|
|
302
|
+
self.content_service.get_output(seg_id),
|
|
303
|
+
f"Content[{index}]"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def compare_all_output(self, comparitor: CompareHelper, content: List):
|
|
307
|
+
assert_equal_len(self.expected_outputs, content)
|
|
308
|
+
for index, expected in enumerate(self.expected_outputs):
|
|
309
|
+
self.compare_one_content(comparitor, expected, content[index], index)
|
|
310
|
+
|
|
311
|
+
def compare_domains(self, comparitor: CompareHelper, expected_items: List[Dict], results: List[Dict]):
|
|
312
|
+
assert_equal_len(expected_items, results)
|
|
313
|
+
for index, expected in enumerate(expected_items):
|
|
314
|
+
actual = results[index]
|
|
315
|
+
assert_equal(expected['name'], actual['name'])
|
|
316
|
+
assert_equal(expected['mediaType'], actual['mediaType'])
|
|
317
|
+
|
|
318
|
+
expected_value = expected['value']
|
|
319
|
+
if type(expected_value) == str:
|
|
320
|
+
comparitor.compare(expected_value, actual['value'], f"Domain[{index}]")
|
|
321
|
+
elif type(expected_value) == IOContent:
|
|
322
|
+
expected_data = self.load_file(expected_value)
|
|
323
|
+
comparitor.compare(expected_data, actual['value'], f"Domain[{index}]")
|
|
324
|
+
else:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"unknown expected_value type: {type(expected_value)}")
|
deltafi/test_kit/load.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
from typing import List
|
|
19
|
+
|
|
20
|
+
from deltafi.result import LoadResult, ReinjectResult
|
|
21
|
+
|
|
22
|
+
from .assertions import *
|
|
23
|
+
from .compare_helpers import GenericCompareHelper, CompareHelper
|
|
24
|
+
from .framework import TestCaseBase, ActionTest, IOContent
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LoadTestCase(TestCaseBase):
|
|
28
|
+
def __init__(self, fields: Dict):
|
|
29
|
+
super().__init__(fields)
|
|
30
|
+
self.metadata = {}
|
|
31
|
+
self.delete_metadata_keys = []
|
|
32
|
+
self.annotations = {}
|
|
33
|
+
self.domains = []
|
|
34
|
+
self.domain_cmp_tool = GenericCompareHelper()
|
|
35
|
+
self.children = []
|
|
36
|
+
|
|
37
|
+
def set_domain_compare_tool(self, helper: CompareHelper):
|
|
38
|
+
self.domain_cmp_tool = helper
|
|
39
|
+
|
|
40
|
+
def expect_load_result(self, metadata: Dict, delete_metadata_keys: List[str], annotations: Dict, domains: List):
|
|
41
|
+
self.expected_result_type = LoadResult
|
|
42
|
+
self.metadata = metadata
|
|
43
|
+
self.delete_metadata_keys = delete_metadata_keys
|
|
44
|
+
self.annotations = annotations
|
|
45
|
+
self.domains = domains
|
|
46
|
+
|
|
47
|
+
def add_reinject_child(self, filename: str, flow: str, content: IOContent, metadata: Dict):
|
|
48
|
+
self.expected_result_type = ReinjectResult
|
|
49
|
+
self.children.append(
|
|
50
|
+
{
|
|
51
|
+
"filename": filename,
|
|
52
|
+
"flow": flow,
|
|
53
|
+
"content": content,
|
|
54
|
+
"metadata": metadata
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LoadActionTest(ActionTest):
|
|
60
|
+
def __init__(self, package_name: str):
|
|
61
|
+
"""
|
|
62
|
+
Provides structure for testing DeltaFi Load action
|
|
63
|
+
Args:
|
|
64
|
+
package_name: name of the actions package for finding resources
|
|
65
|
+
"""
|
|
66
|
+
super().__init__(package_name)
|
|
67
|
+
|
|
68
|
+
def load(self, test_case: LoadTestCase):
|
|
69
|
+
if test_case.expected_result_type == ReinjectResult:
|
|
70
|
+
self.expect_reinject_result(test_case)
|
|
71
|
+
elif test_case.expected_result_type == LoadResult:
|
|
72
|
+
self.expect_load_result(test_case)
|
|
73
|
+
else:
|
|
74
|
+
super().execute(test_case)
|
|
75
|
+
|
|
76
|
+
def expect_load_result(self, test_case: LoadTestCase):
|
|
77
|
+
result = super().run_and_check_result_type(test_case, LoadResult)
|
|
78
|
+
self.assert_load_result(test_case, result)
|
|
79
|
+
|
|
80
|
+
def expect_reinject_result(self, test_case: LoadTestCase):
|
|
81
|
+
result = super().run_and_check_result_type(test_case, ReinjectResult)
|
|
82
|
+
self.assert_reinject_result(test_case, result)
|
|
83
|
+
|
|
84
|
+
def assert_load_result(self, test_case: LoadTestCase, result: LoadResult):
|
|
85
|
+
# Check output
|
|
86
|
+
self.compare_all_output(test_case.compare_tool, result.content)
|
|
87
|
+
|
|
88
|
+
# Check metadata
|
|
89
|
+
assert_keys_and_values(test_case.metadata, result.metadata)
|
|
90
|
+
|
|
91
|
+
# Check deleted metadata
|
|
92
|
+
for key in test_case.delete_metadata_keys:
|
|
93
|
+
assert_key_in(key, result.delete_metadata_keys)
|
|
94
|
+
|
|
95
|
+
# Check annotations
|
|
96
|
+
assert_keys_and_values(test_case.annotations, result.annotations)
|
|
97
|
+
|
|
98
|
+
# Check domains
|
|
99
|
+
self.compare_domains(test_case.domain_cmp_tool, test_case.domains, result.domains)
|
|
100
|
+
|
|
101
|
+
def assert_reinject_result(self, test_case: LoadTestCase, actual: ReinjectResult):
|
|
102
|
+
assert_equal_len(test_case.children, actual.children)
|
|
103
|
+
for index, expected in enumerate(test_case.children):
|
|
104
|
+
reinject_child = actual.children[index]
|
|
105
|
+
assert_equal(expected['filename'], reinject_child.filename)
|
|
106
|
+
assert_equal(expected['flow'], reinject_child.flow)
|
|
107
|
+
assert_keys_and_values(expected['metadata'], reinject_child.metadata)
|
|
108
|
+
|
|
109
|
+
expected_value = expected['content']
|
|
110
|
+
child_content = reinject_child.content[0]
|
|
111
|
+
seg_id = child_content.segments[0].uuid
|
|
112
|
+
actual_content = self.content_service.get_output(seg_id)
|
|
113
|
+
|
|
114
|
+
if type(expected_value) == str:
|
|
115
|
+
test_case.domain_cmp_tool.compare(expected_value, actual_content, f"RI_child[{index}]")
|
|
116
|
+
elif type(expected_value) == IOContent:
|
|
117
|
+
expected_data = self.load_file(expected_value)
|
|
118
|
+
test_case.domain_cmp_tool.compare(expected_data, actual_content, f"RI_child[{index}]")
|
|
119
|
+
else:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"unknown expected_value type: {type(expected_value)}")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DeltaFi - Data transformation and enrichment platform
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2021-2023 DeltaFi Contributors <deltafi@deltafi.org>
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
from typing import List
|
|
19
|
+
|
|
20
|
+
from deltafi.result import TransformResult
|
|
21
|
+
|
|
22
|
+
from .assertions import *
|
|
23
|
+
from .framework import TestCaseBase, ActionTest
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TransformTestCase(TestCaseBase):
|
|
27
|
+
def __init__(self, fields: Dict):
|
|
28
|
+
super().__init__(fields)
|
|
29
|
+
self.metadata = {}
|
|
30
|
+
self.delete_metadata_keys = []
|
|
31
|
+
self.annotations = {}
|
|
32
|
+
|
|
33
|
+
def expect_transform_result(self, metadata: Dict, delete_metadata_keys: List[str], annotations: Dict):
|
|
34
|
+
self.expected_result_type = TransformResult
|
|
35
|
+
self.metadata = metadata
|
|
36
|
+
self.delete_metadata_keys = delete_metadata_keys
|
|
37
|
+
self.annotations = annotations
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TransformActionTest(ActionTest):
|
|
41
|
+
def __init__(self, package_name: str):
|
|
42
|
+
"""
|
|
43
|
+
Provides structure for testing DeltaFi Transform action
|
|
44
|
+
Args:
|
|
45
|
+
package_name: name of the actions package for finding resources
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(package_name)
|
|
48
|
+
|
|
49
|
+
def transform(self, test_case: TransformTestCase):
|
|
50
|
+
if test_case.expected_result_type == TransformResult:
|
|
51
|
+
self.expect_transform_result(test_case)
|
|
52
|
+
else:
|
|
53
|
+
super().execute(test_case)
|
|
54
|
+
|
|
55
|
+
def expect_transform_result(self, test_case: TransformTestCase):
|
|
56
|
+
result = super().run_and_check_result_type(test_case, TransformResult)
|
|
57
|
+
self.assert_transform_result(test_case, result)
|
|
58
|
+
|
|
59
|
+
def assert_transform_result(self, test_case: TransformTestCase, result: TransformResult):
|
|
60
|
+
# Check output
|
|
61
|
+
self.compare_all_output(test_case.compare_tool, result.content)
|
|
62
|
+
|
|
63
|
+
# Check metadata
|
|
64
|
+
assert_keys_and_values(test_case.metadata, result.metadata)
|
|
65
|
+
|
|
66
|
+
# Check deleted metadata
|
|
67
|
+
for key in test_case.delete_metadata_keys:
|
|
68
|
+
assert_key_in(key, result.delete_metadata_keys)
|
|
69
|
+
|
|
70
|
+
# Check annotations
|
|
71
|
+
assert_keys_and_values(test_case.annotations, result.annotations)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deltafi
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.7
|
|
4
4
|
Summary: SDK for DeltaFi plugins and actions
|
|
5
5
|
License: Apache License, Version 2.0
|
|
6
6
|
Keywords: deltafi
|
|
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: 3.7
|
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.8
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.9
|
|
26
26
|
Classifier: Topic :: Software Development
|
|
27
|
+
Requires-Dist: deepdiff (>=6.3.1)
|
|
27
28
|
Requires-Dist: json-logging (>=1.3.0)
|
|
28
29
|
Requires-Dist: minio (>=7.1.15)
|
|
29
30
|
Requires-Dist: pydantic (>=1.10.9,<2.0.0)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
deltafi/__init__.py,sha256=PNMUt1SbY5DhoTxle11dPv21Pr_bo8paVc7TVu4Cs7E,706
|
|
2
|
+
deltafi/action.py,sha256=ssbSGP-Nn15mD2UdHXmbBuNnVw5vPZY88RZRCV9xqMw,7330
|
|
3
|
+
deltafi/actioneventqueue.py,sha256=HR1n-Co5Nq8IarYKR7Uzydi8mkCdOZbC-yJWHuA6KwA,1981
|
|
4
|
+
deltafi/actiontype.py,sha256=gt8j4PgYuxoRbif5-FJi1fu4eLSy2J4oVbQ2qceRgqM,985
|
|
5
|
+
deltafi/domain.py,sha256=-0ad8dGq-JcLyTnrIXHSMr7ZgBnailUJSZn1yPwEHI8,11197
|
|
6
|
+
deltafi/exception.py,sha256=qQ2TY2QPtMU9t5RH8jmFo_cX98AJ-zOFWP_Wfm5U6qQ,1149
|
|
7
|
+
deltafi/input.py,sha256=DuY280ZglZxEUkhjTtyr0g9Ohu2drn16UVhASH244nA,6290
|
|
8
|
+
deltafi/logger.py,sha256=q76R_Gn8BfcASH3Zy0V82m5ot34bjTnSELyKbjhvx3E,2136
|
|
9
|
+
deltafi/metric.py,sha256=eIDjZQVO53KuAFoEtjLNFwqMrp_7BC0Td9GLD1tb6sE,967
|
|
10
|
+
deltafi/plugin.py,sha256=dIBbhwRLbBlF2NyuRz09-Jj8BSIkvJa74JkAzsfiYBc,11700
|
|
11
|
+
deltafi/result.py,sha256=fRo83BZNcTfIDYDePGBJuB84FOnpgBiR23zbedJ2mD8,11512
|
|
12
|
+
deltafi/storage.py,sha256=toq58EPZgzj2TfDF-YpFUmRnsWSjACA0KQAZzkM04xU,2740
|
|
13
|
+
deltafi/test_kit/__init__.py,sha256=PNMUt1SbY5DhoTxle11dPv21Pr_bo8paVc7TVu4Cs7E,706
|
|
14
|
+
deltafi/test_kit/assertions.py,sha256=2yJKCLTenJW44CprFLGorqfjVC6_6z-oAfPY1Q7J__0,1377
|
|
15
|
+
deltafi/test_kit/compare_helpers.py,sha256=xeP6ToXvlCxVK3LX4glf9VsDfAmOoexYjkQpltfZ2T8,1580
|
|
16
|
+
deltafi/test_kit/constants.py,sha256=epz0OS-qILcc8t7iIs5B3m2-wvZx8FpYpyb19ZsImbI,833
|
|
17
|
+
deltafi/test_kit/format.py,sha256=NTG7UnwvXQlzn6WqKjMG9EZg0tKTu1g3CcNDD_6AQwk,4070
|
|
18
|
+
deltafi/test_kit/framework.py,sha256=fEdllkV8luBwdZYkwMZo7-BzgRO_GIcg5e6lMyTD8fI,12855
|
|
19
|
+
deltafi/test_kit/load.py,sha256=0K5vCQXbXl5pos9Yg_4qcZ9bJYfdQUGUosTxncv0xmw,4854
|
|
20
|
+
deltafi/test_kit/transform.py,sha256=Eeqxev_dxoiFw72eAlYJ-3MDQH-kJtuHRGSIMbaatrg,2607
|
|
21
|
+
deltafi-1.0.7.dist-info/METADATA,sha256=x24guqKi-7KbkMFCqJd-XGqgbeNGf7zhldbX34xfouQ,1749
|
|
22
|
+
deltafi-1.0.7.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
|
23
|
+
deltafi-1.0.7.dist-info/RECORD,,
|
deltafi-1.0.5.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
deltafi/__init__.py,sha256=PNMUt1SbY5DhoTxle11dPv21Pr_bo8paVc7TVu4Cs7E,706
|
|
2
|
-
deltafi/action.py,sha256=ssbSGP-Nn15mD2UdHXmbBuNnVw5vPZY88RZRCV9xqMw,7330
|
|
3
|
-
deltafi/actioneventqueue.py,sha256=HR1n-Co5Nq8IarYKR7Uzydi8mkCdOZbC-yJWHuA6KwA,1981
|
|
4
|
-
deltafi/actiontype.py,sha256=gt8j4PgYuxoRbif5-FJi1fu4eLSy2J4oVbQ2qceRgqM,985
|
|
5
|
-
deltafi/domain.py,sha256=x0tS5DKv9tXYJK7eda1SVkkbOhRk_4scpXMfU-FZSJM,11025
|
|
6
|
-
deltafi/exception.py,sha256=qQ2TY2QPtMU9t5RH8jmFo_cX98AJ-zOFWP_Wfm5U6qQ,1149
|
|
7
|
-
deltafi/input.py,sha256=DuY280ZglZxEUkhjTtyr0g9Ohu2drn16UVhASH244nA,6290
|
|
8
|
-
deltafi/logger.py,sha256=q76R_Gn8BfcASH3Zy0V82m5ot34bjTnSELyKbjhvx3E,2136
|
|
9
|
-
deltafi/metric.py,sha256=eIDjZQVO53KuAFoEtjLNFwqMrp_7BC0Td9GLD1tb6sE,967
|
|
10
|
-
deltafi/plugin.py,sha256=7eHU-P8GIqmK8fz9Re7sOWafZ9wxOayifWmmnyKXRrw,11666
|
|
11
|
-
deltafi/result.py,sha256=aIFyytP5fOQBXmV-8IQleR5cQQrKVW36DEuOJSAGCgA,10706
|
|
12
|
-
deltafi/storage.py,sha256=toq58EPZgzj2TfDF-YpFUmRnsWSjACA0KQAZzkM04xU,2740
|
|
13
|
-
deltafi-1.0.5.dist-info/METADATA,sha256=ihKnu4QL1beQdQr8Js6c0cWwOBU0VIth8KEzyNW2P3w,1715
|
|
14
|
-
deltafi-1.0.5.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
|
15
|
-
deltafi-1.0.5.dist-info/RECORD,,
|
|
File without changes
|