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.
Files changed (54) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/proxy_controller.py +40 -13
  3. stoobly_agent/app/cli/endpoint_cli.py +0 -4
  4. stoobly_agent/app/cli/helpers/handle_replay_service.py +0 -2
  5. stoobly_agent/app/cli/helpers/handle_test_service.py +24 -6
  6. stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +5 -5
  7. stoobly_agent/app/cli/helpers/request_synchronize_handler.py +1 -1
  8. stoobly_agent/app/cli/helpers/synchronize_request_service.py +2 -1
  9. stoobly_agent/app/cli/helpers/test_replay_context.py +2 -1
  10. stoobly_agent/app/cli/request_cli.py +2 -1
  11. stoobly_agent/app/cli/scenario_cli.py +5 -3
  12. stoobly_agent/app/proxy/handle_test_service.py +10 -5
  13. stoobly_agent/app/proxy/intercept_settings.py +7 -0
  14. stoobly_agent/app/proxy/simulate_intercept_service.py +7 -0
  15. stoobly_agent/app/proxy/test/context.py +1 -1
  16. stoobly_agent/app/proxy/test/context_abc.py +2 -1
  17. stoobly_agent/app/proxy/test/helpers/diff_service.py +21 -16
  18. stoobly_agent/app/proxy/test/helpers/test_results_builder.py +7 -0
  19. stoobly_agent/app/proxy/test/helpers/upload_test_service.py +0 -2
  20. stoobly_agent/app/proxy/test/matchers/context.py +5 -1
  21. stoobly_agent/config/constants/custom_headers.py +1 -0
  22. stoobly_agent/lib/api/interfaces/tests.py +1 -0
  23. stoobly_agent/public/{19-es2015.afe15b7c97204b5da3e6.js → 19-es2015.cfb9a99583184965c030.js} +1 -1
  24. stoobly_agent/public/{19-es5.afe15b7c97204b5da3e6.js → 19-es5.cfb9a99583184965c030.js} +1 -1
  25. stoobly_agent/public/31-es2015.f4291150f35d54ff19ca.js +1 -0
  26. stoobly_agent/public/31-es5.f4291150f35d54ff19ca.js +1 -0
  27. stoobly_agent/public/{35-es2015.ef19536d87c8aff8f629.js → 35-es2015.4281bf4c914cc833cb00.js} +1 -1
  28. stoobly_agent/public/{35-es5.ef19536d87c8aff8f629.js → 35-es5.4281bf4c914cc833cb00.js} +1 -1
  29. stoobly_agent/public/37-es2015.6567a0ce4cf87ad7287b.js +1 -0
  30. stoobly_agent/public/37-es5.6567a0ce4cf87ad7287b.js +1 -0
  31. stoobly_agent/public/index.html +1 -1
  32. stoobly_agent/public/{main-es2015.74eedbf8ac279a527ac3.js → main-es2015.3ad631f688af79edb9e7.js} +1 -1
  33. stoobly_agent/public/{main-es5.74eedbf8ac279a527ac3.js → main-es5.3ad631f688af79edb9e7.js} +1 -1
  34. stoobly_agent/public/runtime-es2015.ce140e9ace0d883b7982.js +1 -0
  35. stoobly_agent/public/runtime-es5.ce140e9ace0d883b7982.js +1 -0
  36. {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/METADATA +3 -2
  37. {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/RECORD +48 -48
  38. stoobly_agent/public/31-es2015.198ce7bb981893b0ffb9.js +0 -1
  39. stoobly_agent/public/31-es5.198ce7bb981893b0ffb9.js +0 -1
  40. stoobly_agent/public/37-es2015.2691c88dff72bd0e1b98.js +0 -1
  41. stoobly_agent/public/37-es5.2691c88dff72bd0e1b98.js +0 -1
  42. stoobly_agent/public/runtime-es2015.8d562cec3ef3d89930a0.js +0 -1
  43. stoobly_agent/public/runtime-es5.8d562cec3ef3d89930a0.js +0 -1
  44. /stoobly_agent/public/{12-es2015.7caf8af72ac062656549.js → 12-es2015.e6cbd665db1eb1d3bbed.js} +0 -0
  45. /stoobly_agent/public/{12-es5.7caf8af72ac062656549.js → 12-es5.e6cbd665db1eb1d3bbed.js} +0 -0
  46. /stoobly_agent/public/{33-es2015.af3dab68c45d0fa4d762.js → 33-es2015.1de1bb64d23f33c17ef2.js} +0 -0
  47. /stoobly_agent/public/{33-es5.af3dab68c45d0fa4d762.js → 33-es5.1de1bb64d23f33c17ef2.js} +0 -0
  48. /stoobly_agent/public/{41-es2015.73c5bfc29200498e1251.js → 41-es2015.8679fbbd8207bda9407c.js} +0 -0
  49. /stoobly_agent/public/{41-es5.73c5bfc29200498e1251.js → 41-es5.8679fbbd8207bda9407c.js} +0 -0
  50. /stoobly_agent/public/{42-es2015.bc8a021e2b75feabf80f.js → 42-es2015.bca78151df3a77aa6c5d.js} +0 -0
  51. /stoobly_agent/public/{42-es5.bc8a021e2b75feabf80f.js → 42-es5.bca78151df3a77aa6c5d.js} +0 -0
  52. {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/LICENSE +0 -0
  53. {stoobly_agent-0.33.1.dist-info → stoobly_agent-0.33.3.dist-info}/WHEEL +0 -0
  54. {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.1'
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
- res = method(
65
- url,
66
- allow_redirects = True,
67
- cookies = _cookies,
68
- data = _body,
69
- headers = _headers,
70
- params = _params,
71
- stream = True,
72
- verify = not MitmproxyConfig.instance().get('ssl_insecure')
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 = res.headers,
80
- data = res.raw.data,
81
- status = res.status_code,
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
- passed_message = f"{bcolors.FAIL}failed{bcolors.ENDC}"
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{passed} / {total} {bcolors.OKGREEN}passed{bcolors.ENDC}")
161
+ print(f"\n{passed_message}{skipped_message}")
147
162
  else:
148
- print(f"\n{passed} / {total} {bcolors.OKGREEN}passed{bcolors.ENDC} {total - passed} {bcolors.FAIL}failed{bcolors.ENDC}")
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
- from typing import Dict, List, Union
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 yaml
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,
@@ -1,7 +1,7 @@
1
1
  import copy
2
2
  import pdb
3
3
 
4
- from typing import TypedDict, Union
4
+ from typing import Union
5
5
 
6
6
  from stoobly_agent.app.proxy.test.matchers.context import MatchContext
7
7
  from stoobly_agent.app.settings.constants import request_component
@@ -1,6 +1,7 @@
1
1
  import copy
2
- from dataclasses import dataclass
3
2
  import pdb
3
+
4
+ from dataclasses import dataclass
4
5
  from typing import List
5
6
  from urllib.parse import urlencode
6
7
  from urllib.parse import urlparse
@@ -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='Replay request and save to history.')(f), is_remote)
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='Replay request and save to history.')(f), is_remote)
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
- # Run test
62
- passed, log = test(test_context)
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
- matcher = difflib.SequenceMatcher(None, a, b)
36
-
37
- for opcode, a0, a1, b0, b1 in matcher.get_opcodes():
38
- if opcode == 'equal':
39
- output.append(a[a0:a1])
40
- elif opcode == 'insert':
41
- output.append(f'{green}{b[b0:b1]}{endgreen}')
42
- elif opcode == 'delete':
43
- output.append(f'{red}{a[a0:a1]}{endred}')
44
- elif opcode == 'replace':
45
- output.append(f'{green}{b[b0:b1]}{endgreen}')
46
- output.append(f'{red}{a[a0:a1]}{endred}')
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
 
@@ -54,8 +54,6 @@ def upload_test(
54
54
 
55
55
  api.with_report_key(report_key, kwargs)
56
56
 
57
- res: Response = None
58
-
59
57
  scenario_key = intercept_settings.scenario_key
60
58
  if scenario_key:
61
59
  return api.from_scenario_key(
@@ -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
- if param_name['query'] == query:
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'
@@ -10,6 +10,7 @@ class TestShowResponse(TypedDict):
10
10
  log: str
11
11
  passed: bool
12
12
  project_id: str
13
+ skipped: bool
13
14
  strategy: Union[test_strategy.DIFF, test_strategy.CUSTOM, test_strategy.FUZZY]
14
15
 
15
16
  class TestsIndexQueryParams(PaginationQueryParams):