stoobly-agent 0.33.1__py3-none-any.whl → 0.33.3__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.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/proxy_controller.py +40 -13
- stoobly_agent/app/cli/endpoint_cli.py +0 -4
- stoobly_agent/app/cli/helpers/handle_replay_service.py +0 -2
- stoobly_agent/app/cli/helpers/handle_test_service.py +24 -6
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +5 -5
- stoobly_agent/app/cli/helpers/request_synchronize_handler.py +1 -1
- stoobly_agent/app/cli/helpers/synchronize_request_service.py +2 -1
- stoobly_agent/app/cli/helpers/test_replay_context.py +2 -1
- stoobly_agent/app/cli/request_cli.py +2 -1
- stoobly_agent/app/cli/scenario_cli.py +5 -3
- stoobly_agent/app/proxy/handle_test_service.py +10 -5
- stoobly_agent/app/proxy/intercept_settings.py +7 -0
- stoobly_agent/app/proxy/simulate_intercept_service.py +7 -0
- stoobly_agent/app/proxy/test/context.py +1 -1
- stoobly_agent/app/proxy/test/context_abc.py +2 -1
- stoobly_agent/app/proxy/test/helpers/diff_service.py +21 -16
- stoobly_agent/app/proxy/test/helpers/test_results_builder.py +7 -0
- stoobly_agent/app/proxy/test/helpers/upload_test_service.py +0 -2
- stoobly_agent/app/proxy/test/matchers/context.py +5 -1
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/lib/api/interfaces/tests.py +1 -0
- stoobly_agent/public/{19-es2015.afe15b7c97204b5da3e6.js → 19-es2015.cfb9a99583184965c030.js} +1 -1
- stoobly_agent/public/{19-es5.afe15b7c97204b5da3e6.js → 19-es5.cfb9a99583184965c030.js} +1 -1
- stoobly_agent/public/31-es2015.f4291150f35d54ff19ca.js +1 -0
- stoobly_agent/public/31-es5.f4291150f35d54ff19ca.js +1 -0
- stoobly_agent/public/{35-es2015.ef19536d87c8aff8f629.js → 35-es2015.4281bf4c914cc833cb00.js} +1 -1
- stoobly_agent/public/{35-es5.ef19536d87c8aff8f629.js → 35-es5.4281bf4c914cc833cb00.js} +1 -1
- stoobly_agent/public/37-es2015.6567a0ce4cf87ad7287b.js +1 -0
- stoobly_agent/public/37-es5.6567a0ce4cf87ad7287b.js +1 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/{main-es2015.74eedbf8ac279a527ac3.js → main-es2015.3ad631f688af79edb9e7.js} +1 -1
- stoobly_agent/public/{main-es5.74eedbf8ac279a527ac3.js → main-es5.3ad631f688af79edb9e7.js} +1 -1
- stoobly_agent/public/runtime-es2015.ce140e9ace0d883b7982.js +1 -0
- stoobly_agent/public/runtime-es5.ce140e9ace0d883b7982.js +1 -0
- {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/METADATA +3 -2
- {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/RECORD +48 -48
- stoobly_agent/public/31-es2015.198ce7bb981893b0ffb9.js +0 -1
- stoobly_agent/public/31-es5.198ce7bb981893b0ffb9.js +0 -1
- stoobly_agent/public/37-es2015.2691c88dff72bd0e1b98.js +0 -1
- stoobly_agent/public/37-es5.2691c88dff72bd0e1b98.js +0 -1
- stoobly_agent/public/runtime-es2015.8d562cec3ef3d89930a0.js +0 -1
- stoobly_agent/public/runtime-es5.8d562cec3ef3d89930a0.js +0 -1
- /stoobly_agent/public/{12-es2015.7caf8af72ac062656549.js → 12-es2015.e6cbd665db1eb1d3bbed.js} +0 -0
- /stoobly_agent/public/{12-es5.7caf8af72ac062656549.js → 12-es5.e6cbd665db1eb1d3bbed.js} +0 -0
- /stoobly_agent/public/{33-es2015.af3dab68c45d0fa4d762.js → 33-es2015.1de1bb64d23f33c17ef2.js} +0 -0
- /stoobly_agent/public/{33-es5.af3dab68c45d0fa4d762.js → 33-es5.1de1bb64d23f33c17ef2.js} +0 -0
- /stoobly_agent/public/{41-es2015.73c5bfc29200498e1251.js → 41-es2015.8679fbbd8207bda9407c.js} +0 -0
- /stoobly_agent/public/{41-es5.73c5bfc29200498e1251.js → 41-es5.8679fbbd8207bda9407c.js} +0 -0
- /stoobly_agent/public/{42-es2015.bc8a021e2b75feabf80f.js → 42-es2015.bca78151df3a77aa6c5d.js} +0 -0
- /stoobly_agent/public/{42-es5.bc8a021e2b75feabf80f.js → 42-es5.bca78151df3a77aa6c5d.js} +0 -0
- {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/LICENSE +0 -0
- {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/WHEEL +0 -0
- {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '0.33.
|
2
|
+
VERSION = '0.33.3'
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import requests
|
2
2
|
|
3
3
|
from http.cookies import SimpleCookie
|
4
|
+
from urllib3.exceptions import InsecureRequestWarning
|
4
5
|
|
5
6
|
from stoobly_agent.config.constants import headers
|
6
7
|
from stoobly_agent.config.mitmproxy import MitmproxyConfig
|
@@ -51,6 +52,7 @@ class ProxyController:
|
|
51
52
|
_cookies = self.__get_cookies(context)
|
52
53
|
_body = self.__get_body(context)
|
53
54
|
_params = context.params
|
55
|
+
_verify = not MitmproxyConfig.instance().get('ssl_insecure')
|
54
56
|
|
55
57
|
Logger.instance().debug('Request Headers')
|
56
58
|
Logger.instance().debug(_headers)
|
@@ -61,24 +63,49 @@ class ProxyController:
|
|
61
63
|
Logger.instance().debug('Query Params')
|
62
64
|
Logger.instance().debug(_params)
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
66
|
+
body = None
|
67
|
+
headers = {}
|
68
|
+
status = 0
|
69
|
+
|
70
|
+
if not _verify:
|
71
|
+
# Suppress only the single warning from urllib3 needed.
|
72
|
+
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
73
|
+
|
74
|
+
try:
|
75
|
+
res = method(
|
76
|
+
url,
|
77
|
+
allow_redirects = True,
|
78
|
+
cookies = _cookies,
|
79
|
+
data = _body,
|
80
|
+
headers = _headers,
|
81
|
+
params = _params,
|
82
|
+
stream = True,
|
83
|
+
verify = _verify
|
84
|
+
)
|
85
|
+
|
86
|
+
body = res.raw.data
|
87
|
+
headers = res.headers
|
88
|
+
status = res.status_code
|
89
|
+
except requests.exceptions.ConnectTimeout:
|
90
|
+
body = b'Gateway Timeout'
|
91
|
+
status = 504
|
92
|
+
except requests.exceptions.SSLError as e:
|
93
|
+
body = str(e).encode()
|
94
|
+
status = 502
|
95
|
+
except requests.exceptions.ConnectionError:
|
96
|
+
body = b'Bad Gateway'
|
97
|
+
status = 502
|
98
|
+
except Exception:
|
99
|
+
body = b'Unknown Error'
|
100
|
+
status = 0
|
74
101
|
|
75
102
|
Logger.instance().debug('Response Headers')
|
76
103
|
Logger.instance().debug(res.headers)
|
77
104
|
|
78
105
|
context.render(
|
79
|
-
headers =
|
80
|
-
data =
|
81
|
-
status =
|
106
|
+
headers = headers,
|
107
|
+
data = body,
|
108
|
+
status = status,
|
82
109
|
)
|
83
110
|
|
84
111
|
def __get_headers(self, context):
|
@@ -1,11 +1,7 @@
|
|
1
|
-
import os
|
2
|
-
import traceback
|
3
|
-
|
4
1
|
import click
|
5
2
|
|
6
3
|
from stoobly_agent.app.models.types import OPENAPI_FORMAT
|
7
4
|
from stoobly_agent.app.settings import Settings
|
8
|
-
from stoobly_agent.config.constants import env_vars
|
9
5
|
from stoobly_agent.lib.logger import bcolors
|
10
6
|
from stoobly_agent.lib.utils.conditional_decorator import ConditionalDecorator
|
11
7
|
|
@@ -12,8 +12,6 @@ from stoobly_agent.lib.logger import bcolors
|
|
12
12
|
from stoobly_agent.lib.utils import jmespath
|
13
13
|
from stoobly_agent.lib.utils.decode import decode
|
14
14
|
|
15
|
-
from .test_replay_context import TestReplayContext
|
16
|
-
|
17
15
|
BODY_FORMAT = 'body'
|
18
16
|
DEFAULT_FORMAT = 'default'
|
19
17
|
JSON_FORMAT = 'json'
|
@@ -23,6 +23,7 @@ class SessionContext(TypedDict):
|
|
23
23
|
passed: int
|
24
24
|
output: TestOutput
|
25
25
|
project_id: int
|
26
|
+
skipped: int
|
26
27
|
test_facade: TestFacade
|
27
28
|
total: int
|
28
29
|
|
@@ -54,6 +55,9 @@ def handle_test_complete(
|
|
54
55
|
|
55
56
|
test = context.test_show_response(session_context['test_facade'])
|
56
57
|
|
58
|
+
if test['skipped']:
|
59
|
+
session_context['skipped'] += 1
|
60
|
+
|
57
61
|
passed = bool(test.get('passed')) if isinstance(test, dict) else False
|
58
62
|
session_context['passed'] += (1 if passed else 0)
|
59
63
|
session_context['total'] += 1
|
@@ -65,7 +69,7 @@ def exit_on_failure(session_context: SessionContext, **options: ExitOnFailureOpt
|
|
65
69
|
if complete == None:
|
66
70
|
complete = True
|
67
71
|
|
68
|
-
if session_context['passed'] != session_context['total']:
|
72
|
+
if session_context['passed'] + session_context['skipped'] != session_context['total']:
|
69
73
|
if not complete:
|
70
74
|
handle_test_session_complete(session_context, format=options.get('format'))
|
71
75
|
|
@@ -112,7 +116,10 @@ def __default_test_complete_formatter(context: ReplayContext, session_context: S
|
|
112
116
|
print_with_decoding(format_request(context))
|
113
117
|
|
114
118
|
if not res['passed']:
|
115
|
-
|
119
|
+
if res['skipped']:
|
120
|
+
passed_message = f"{bcolors.WARNING}skipped{bcolors.ENDC}"
|
121
|
+
else:
|
122
|
+
passed_message = f"{bcolors.FAIL}failed{bcolors.ENDC}"
|
116
123
|
else:
|
117
124
|
passed_message = f"{bcolors.OKGREEN}passed{bcolors.ENDC}"
|
118
125
|
|
@@ -121,7 +128,7 @@ def __default_test_complete_formatter(context: ReplayContext, session_context: S
|
|
121
128
|
if res['log']:
|
122
129
|
print(res['log'])
|
123
130
|
|
124
|
-
if not res['passed']:
|
131
|
+
if not res['passed'] and not res['skipped']:
|
125
132
|
project_id = session_context['project_id']
|
126
133
|
test_facade = session_context['test_facade']
|
127
134
|
expected_response = __get_test_expected_response_with_context(context, project_id, test_facade)
|
@@ -138,21 +145,32 @@ def __default_session_complete_formatter(session_context: SessionContext):
|
|
138
145
|
if 'output' in session_context:
|
139
146
|
print(session_context['output'])
|
140
147
|
|
141
|
-
passed = session_context['passed']
|
142
148
|
total = session_context['total']
|
143
149
|
|
144
150
|
if total > 1:
|
151
|
+
passed = session_context['passed']
|
152
|
+
skipped = session_context['skipped']
|
153
|
+
|
154
|
+
passed_message = f"{passed} / {total} {bcolors.OKGREEN}passed{bcolors.ENDC}"
|
155
|
+
|
156
|
+
skipped_message = ''
|
157
|
+
if skipped:
|
158
|
+
skipped_message = f" {skipped} {bcolors.WARNING}skipped{bcolors.ENDC}"
|
159
|
+
|
145
160
|
if passed == total:
|
146
|
-
print(f"\n{
|
161
|
+
print(f"\n{passed_message}{skipped_message}")
|
147
162
|
else:
|
148
|
-
|
163
|
+
failed_message = f"{total - passed - skipped} {bcolors.FAIL}failed{bcolors.ENDC}"
|
164
|
+
print(f"\n{passed_message} {failed_message}{skipped_message}")
|
149
165
|
|
150
166
|
def __json_session_complete_formatter(session_context: SessionContext):
|
151
167
|
if 'output' not in session_context:
|
152
168
|
return
|
153
169
|
|
154
170
|
session_context['output']['passed'] = session_context['passed']
|
171
|
+
session_context['output']['skipped'] = session_context['skipped']
|
155
172
|
session_context['output']['total'] = session_context['total']
|
173
|
+
|
156
174
|
print(json.dumps(session_context['output']))
|
157
175
|
|
158
176
|
def __get_test_expected_response_with_context(context: ReplayContext, project_id: str, test_facade: TestFacade) -> Union[requests.Response, str]:
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import copy
|
2
|
-
from functools import reduce
|
3
2
|
import itertools
|
4
|
-
from pprint import pprint
|
5
3
|
import re
|
6
|
-
|
7
|
-
from urllib.parse import urlparse
|
4
|
+
import yaml
|
8
5
|
|
6
|
+
from functools import reduce
|
9
7
|
from openapi_core import Spec
|
10
|
-
import
|
8
|
+
from pprint import pprint
|
9
|
+
from typing import Dict, List, Union
|
10
|
+
from urllib.parse import urlparse
|
11
11
|
|
12
12
|
from stoobly_agent.lib.api.interfaces.endpoints import (
|
13
13
|
Alias,
|
@@ -73,13 +73,14 @@ class TestReplayContext(ReplayContext):
|
|
73
73
|
'log': self.__build_log(builder),
|
74
74
|
'passed': builder.passed,
|
75
75
|
'project_id': project_id,
|
76
|
+
'skipped': builder.skipped,
|
76
77
|
'strategy': builder.strategy,
|
77
78
|
}
|
78
79
|
|
79
80
|
def __build_log(self, builder: TestResultsBuilder):
|
80
81
|
log = [builder.log]
|
81
82
|
|
82
|
-
if not builder.passed:
|
83
|
+
if not builder.passed and not builder.skipped:
|
83
84
|
log.append("\n" + diff(builder.expected_response, builder.received_response))
|
84
85
|
|
85
86
|
return "\n".join(log)
|
@@ -131,7 +131,7 @@ def list(**kwargs):
|
|
131
131
|
)(f), not is_remote
|
132
132
|
)
|
133
133
|
@click.option('--record', is_flag=True, default=False, help='Replay request and record.')
|
134
|
-
@ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='
|
134
|
+
@ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='Save results.')(f), is_remote)
|
135
135
|
@click.option('--scenario-key', help='Record to scenario.')
|
136
136
|
@click.option('--scheme', type=click.Choice(['http', 'https']), help='Rewrite request scheme.')
|
137
137
|
@ConditionalDecorator(lambda f: click.option('--trace-id', help='Use existing trace.')(f), is_remote)
|
@@ -264,6 +264,7 @@ def test(**kwargs):
|
|
264
264
|
'aggregate_failures': kwargs['aggregate_failures'],
|
265
265
|
'passed': 0,
|
266
266
|
'project_id': request_key.project_id,
|
267
|
+
'skipped': 0,
|
267
268
|
'test_facade': TestFacade(settings),
|
268
269
|
'total': 0
|
269
270
|
}
|
@@ -274,8 +274,8 @@ if not is_remote:
|
|
274
274
|
'''
|
275
275
|
)
|
276
276
|
@ConditionalDecorator(lambda f: click.option('--remote-project-key', help='Use remote project for endpoint definitions.')(f), is_remote)
|
277
|
-
@ConditionalDecorator(lambda f: click.option('--report-key', help='Save to report.')(f), is_remote)
|
278
|
-
@ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='
|
277
|
+
@ConditionalDecorator(lambda f: click.option('--report-key', help='Save results to report.')(f), is_remote)
|
278
|
+
@ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='Save results.')(f), is_remote)
|
279
279
|
@click.option('--scheme', help='Rewrite request scheme.')
|
280
280
|
@click.option(
|
281
281
|
'--strategy',
|
@@ -298,6 +298,7 @@ def test(**kwargs):
|
|
298
298
|
|
299
299
|
if kwargs.get('report_key'):
|
300
300
|
validate_report_key(kwargs['report_key'])
|
301
|
+
kwargs['save'] = True # If report_key is set, then intention is to save results to the report
|
301
302
|
|
302
303
|
if len(kwargs['validate']):
|
303
304
|
validate_aliases(kwargs['validate'], assign=kwargs['assign'], format=kwargs['format'], trace_id=kwargs['trace_id'])
|
@@ -315,7 +316,8 @@ def test(**kwargs):
|
|
315
316
|
session_context: SessionContext = {
|
316
317
|
'aggregate_failures': kwargs['aggregate_failures'],
|
317
318
|
'passed': 0,
|
318
|
-
'project_id': scenario_key.project_id,
|
319
|
+
'project_id': scenario_key.project_id,
|
320
|
+
'skipped': 0,
|
319
321
|
'test_facade': TestFacade(settings),
|
320
322
|
'total': 0
|
321
323
|
}
|
@@ -17,7 +17,6 @@ from .mock.context import MockContext
|
|
17
17
|
from .test.helpers.test_results_builder import TestResultsBuilder
|
18
18
|
from .test.helpers.upload_test_service import inject_upload_test
|
19
19
|
from .test.context_abc import TestContextABC as TestContext
|
20
|
-
from .test.helpers.diff_service import diff
|
21
20
|
from .test.test_service import test
|
22
21
|
|
23
22
|
LOG_ID = 'HandleTest'
|
@@ -54,23 +53,29 @@ def __decorate_test_id(flow: MitmproxyHTTPFlow, test_response: TestShowResponse)
|
|
54
53
|
def __handle_mock_success(test_context: TestContext) -> None:
|
55
54
|
flow: MitmproxyHTTPFlow = test_context.flow
|
56
55
|
settings = Settings.instance()
|
56
|
+
|
57
57
|
test_context.with_endpoints_resource(EndpointsResource(settings.remote.api_url, settings.remote.api_key))
|
58
58
|
|
59
59
|
__test_hook(lifecycle_hooks.BEFORE_TEST, test_context)
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
intercept_settings = test_context.intercept_settings
|
62
|
+
if intercept_settings.test_skip:
|
63
|
+
passed, log = (False, '')
|
64
|
+
skipped = True
|
65
|
+
else:
|
66
|
+
# Run test
|
67
|
+
passed, log = test(test_context)
|
68
|
+
skipped = False
|
63
69
|
|
64
70
|
request_id = test_context.mock_request_id
|
65
71
|
if request_id:
|
66
|
-
intercept_settings = test_context.intercept_settings
|
67
|
-
|
68
72
|
expected = test_context.cached_expected_response_content
|
69
73
|
upload_test_data = {
|
70
74
|
'expected_response': expected,
|
71
75
|
'log': log,
|
72
76
|
'passed': passed,
|
73
77
|
'request_id': request_id,
|
78
|
+
'skipped': skipped,
|
74
79
|
'status': flow.response.status_code,
|
75
80
|
'strategy': test_context.strategy
|
76
81
|
}
|
@@ -215,6 +215,13 @@ class InterceptSettings:
|
|
215
215
|
return not not int(self.__headers[custom_headers.TEST_SAVE_RESULTS])
|
216
216
|
|
217
217
|
return False
|
218
|
+
|
219
|
+
@property
|
220
|
+
def test_skip(self):
|
221
|
+
if self.__headers and custom_headers.TEST_SKIP in self.__headers:
|
222
|
+
return True
|
223
|
+
|
224
|
+
return False
|
218
225
|
|
219
226
|
@property
|
220
227
|
def test_strategy(self):
|
@@ -4,6 +4,7 @@ import requests
|
|
4
4
|
from io import BytesIO
|
5
5
|
from mitmproxy.http import Request as MitmproxyRequest
|
6
6
|
from urllib3 import HTTPResponse
|
7
|
+
from urllib3.exceptions import InsecureRequestWarning
|
7
8
|
|
8
9
|
from stoobly_agent.app.proxy.mitmproxy.flow_mock import MitmproxyFlowMock
|
9
10
|
from stoobly_agent.app.models.adapters.mitmproxy import MitmproxyRequestAdapterFactory, MitmproxyResponseAdapterFactory
|
@@ -23,10 +24,16 @@ def simulate_intercept(request: requests.Request, **config):
|
|
23
24
|
|
24
25
|
res = None
|
25
26
|
|
27
|
+
if not config.get('verify'):
|
28
|
+
# Suppress only the single warning from urllib3 needed.
|
29
|
+
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
30
|
+
|
26
31
|
try:
|
27
32
|
res = session.send(prepared_request, **{'timeout': 300, **config})
|
28
33
|
except requests.exceptions.ConnectTimeout:
|
29
34
|
res = __response(b'Gateway Timeout', 504)
|
35
|
+
except requests.exceptions.SSLError as e:
|
36
|
+
res = __response(str(e).encode(), 502)
|
30
37
|
except requests.exceptions.ConnectionError:
|
31
38
|
res = __response(b'Bad Gateway', 502)
|
32
39
|
except Exception:
|
@@ -56,7 +56,7 @@ class TestContext(TestContextABC):
|
|
56
56
|
@property
|
57
57
|
def cached_expected_response_content(self) -> Union[bytes, None, str]:
|
58
58
|
if not self.__cached_rewritten_expected_response_content:
|
59
|
-
return self.rewritten_expected_response_content
|
59
|
+
return encode_response(self.rewritten_expected_response_content, self.__expected_response.content_type)
|
60
60
|
|
61
61
|
return encode_response(self.__cached_rewritten_expected_response_content, self.__expected_response.content_type)
|
62
62
|
|
@@ -5,6 +5,7 @@ from typing import Union
|
|
5
5
|
|
6
6
|
from stoobly_agent.app.proxy.test.helpers.endpoint_facade import EndpointFacade
|
7
7
|
from stoobly_agent.app.proxy.test.helpers.request_component_names_facade import RequestComponentNamesFacade
|
8
|
+
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
8
9
|
from stoobly_agent.lib.api.interfaces.endpoints import EndpointShowResponse
|
9
10
|
from stoobly_agent.lib.orm.trace import Trace
|
10
11
|
|
@@ -70,7 +71,7 @@ class TestContextABC(abc.ABC):
|
|
70
71
|
|
71
72
|
@property
|
72
73
|
@abc.abstractmethod
|
73
|
-
def intercept_settings(self):
|
74
|
+
def intercept_settings(self) -> InterceptSettings:
|
74
75
|
pass
|
75
76
|
|
76
77
|
@property
|
@@ -1,19 +1,17 @@
|
|
1
|
-
import difflib
|
2
1
|
import json
|
3
2
|
import pdb
|
4
3
|
|
5
4
|
from typing import Union
|
5
|
+
from diff_match_patch import diff_match_patch
|
6
6
|
|
7
7
|
green = '\x1b[38;5;16;48;5;2m'
|
8
|
-
green = '\x1b[42m'
|
9
8
|
red = '\x1b[38;5;16;48;5;1m'
|
10
9
|
endgreen = '\x1b[0m'
|
11
|
-
endgreen = '\033[0m'
|
12
10
|
endred = '\x1b[0m'
|
13
11
|
|
14
12
|
error = 'Error: Cannot print diff for binary output'
|
15
13
|
|
16
|
-
def diff(a: Union[bytes, str], b: Union[str, bytes]) -> str:
|
14
|
+
def diff(a: Union[bytes, str], b: Union[str, bytes], timeout = 30) -> str:
|
17
15
|
|
18
16
|
if not __string_like(a):
|
19
17
|
a = json.dumps(a, indent=2)
|
@@ -32,18 +30,25 @@ def diff(a: Union[bytes, str], b: Union[str, bytes]) -> str:
|
|
32
30
|
return error
|
33
31
|
|
34
32
|
output = []
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
output.append(
|
42
|
-
elif opcode ==
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
33
|
+
dmp = diff_match_patch()
|
34
|
+
for delta in dmp.diff_lineMode(a, b, timeout):
|
35
|
+
opcode = delta[0]
|
36
|
+
text = delta[1]
|
37
|
+
|
38
|
+
if opcode == 0:
|
39
|
+
output.append(text)
|
40
|
+
elif opcode == 1:
|
41
|
+
length = len(text)
|
42
|
+
if text[length - 1] == "\n":
|
43
|
+
output.append(f'{green}{text[0:length - 1]} {endgreen}\n')
|
44
|
+
else:
|
45
|
+
output.append(f'{green}{text}{endgreen}')
|
46
|
+
elif opcode == -1:
|
47
|
+
length = len(text)
|
48
|
+
if text[length - 1] == "\n":
|
49
|
+
output.append(f'{red}{text[0:length - 1]} {endred}\n')
|
50
|
+
else:
|
51
|
+
output.append(f'{red}{text}{endred}')
|
47
52
|
|
48
53
|
return ''.join(output)
|
49
54
|
|
@@ -19,6 +19,7 @@ class TestResultsBuilder():
|
|
19
19
|
self.__passed = test_data.get('passed')
|
20
20
|
self.__request_id = test_data.get('request_id')
|
21
21
|
self.__received_response = test_data.get('received_response')
|
22
|
+
self.__skipped = test_data.get('skipped')
|
22
23
|
self.__status = test_data.get('status')
|
23
24
|
self.__strategy = test_data.get('strategy')
|
24
25
|
|
@@ -42,6 +43,10 @@ class TestResultsBuilder():
|
|
42
43
|
def received_response(self):
|
43
44
|
return self.__received_response
|
44
45
|
|
46
|
+
@property
|
47
|
+
def skipped(self):
|
48
|
+
return self.__skipped
|
49
|
+
|
45
50
|
@property
|
46
51
|
def status(self):
|
47
52
|
return self.__status
|
@@ -83,6 +88,7 @@ class TestResultsBuilder():
|
|
83
88
|
'log': self.__log,
|
84
89
|
'passed': self.__passed,
|
85
90
|
'request_id': self.__request_id,
|
91
|
+
'skipped': self.__skipped,
|
86
92
|
'status': self.__status,
|
87
93
|
'strategy': self.__strategy,
|
88
94
|
}
|
@@ -119,6 +125,7 @@ class TestResultsBuilder():
|
|
119
125
|
self.__log = test_data.get('log')
|
120
126
|
self.__passed = test_data.get('passed')
|
121
127
|
self.__request_id = test_data.get('request_id')
|
128
|
+
self.__skipped = test_data.get('skipped')
|
122
129
|
self.__status = test_data.get('status')
|
123
130
|
self.__strategy = test_data.get('strategy')
|
124
131
|
|
@@ -213,7 +213,11 @@ class MatchContext():
|
|
213
213
|
|
214
214
|
def __param_name_matches(self, query, param_names: List[ResponseParamName]) -> bool:
|
215
215
|
for param_name in param_names:
|
216
|
-
|
216
|
+
# Not all components will have a query property,
|
217
|
+
# If query property does not exist, then use name property
|
218
|
+
_query = param_name['query'] if 'query' in param_name else param_name['name']
|
219
|
+
|
220
|
+
if _query == query:
|
217
221
|
return True
|
218
222
|
|
219
223
|
return False
|
@@ -20,6 +20,7 @@ SERVICE_URL = 'X-Service-Url'
|
|
20
20
|
TEST_FILTER = 'X-Test-Filter'
|
21
21
|
TEST_ID = 'X-Stoobly-Test-Id'
|
22
22
|
TEST_SAVE_RESULTS = 'X-Stoobly-Test-Save-Results'
|
23
|
+
TEST_SKIP = 'X-Stoobly-Test-Skip'
|
23
24
|
TEST_STRATEGY = 'X-Test-Strategy'
|
24
25
|
TRACE_ID = 'X-Stoobly-Trace-Id'
|
25
26
|
TRACE_REQUEST_ID = 'X-Stoobly-Trace-Request-Id'
|