stoobly-agent 0.33.2__py3-none-any.whl → 0.33.4__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/requests_controller.py +2 -0
  3. stoobly_agent/app/cli/helpers/handle_test_service.py +24 -6
  4. stoobly_agent/app/cli/helpers/test_replay_context.py +2 -1
  5. stoobly_agent/app/cli/request_cli.py +2 -1
  6. stoobly_agent/app/cli/scenario_cli.py +5 -3
  7. stoobly_agent/app/models/factories/resource/local_db/helpers/create_request_columns_service.py +6 -3
  8. stoobly_agent/app/models/factories/resource/local_db/request_adapter.py +4 -5
  9. stoobly_agent/app/models/types/request.py +1 -0
  10. stoobly_agent/app/models/types/scenario.py +1 -0
  11. stoobly_agent/app/proxy/handle_test_service.py +11 -6
  12. stoobly_agent/app/proxy/intercept_settings.py +7 -0
  13. stoobly_agent/app/proxy/test/context.py +2 -2
  14. stoobly_agent/app/proxy/test/context_abc.py +3 -2
  15. stoobly_agent/app/proxy/test/helpers/test_results_builder.py +7 -0
  16. stoobly_agent/app/proxy/test/helpers/upload_test_service.py +0 -2
  17. stoobly_agent/app/proxy/test/matchers/context.py +5 -1
  18. stoobly_agent/config/constants/custom_headers.py +1 -0
  19. stoobly_agent/db/migrations/2023_05_15_212505_add_uuid_column_to_requests.py +1 -1
  20. stoobly_agent/db/migrations/2023_05_15_213119_add_uuid_column_to_scenarios.py +1 -1
  21. stoobly_agent/lib/api/interfaces/tests.py +1 -0
  22. stoobly_agent/public/{19-es2015.afe15b7c97204b5da3e6.js → 19-es2015.cfb9a99583184965c030.js} +1 -1
  23. stoobly_agent/public/{19-es5.afe15b7c97204b5da3e6.js → 19-es5.cfb9a99583184965c030.js} +1 -1
  24. stoobly_agent/public/31-es2015.f4291150f35d54ff19ca.js +1 -0
  25. stoobly_agent/public/31-es5.f4291150f35d54ff19ca.js +1 -0
  26. stoobly_agent/public/{35-es2015.ef19536d87c8aff8f629.js → 35-es2015.4281bf4c914cc833cb00.js} +1 -1
  27. stoobly_agent/public/{35-es5.ef19536d87c8aff8f629.js → 35-es5.4281bf4c914cc833cb00.js} +1 -1
  28. stoobly_agent/public/37-es2015.6567a0ce4cf87ad7287b.js +1 -0
  29. stoobly_agent/public/37-es5.6567a0ce4cf87ad7287b.js +1 -0
  30. stoobly_agent/public/index.html +1 -1
  31. stoobly_agent/public/{main-es2015.74eedbf8ac279a527ac3.js → main-es2015.3ad631f688af79edb9e7.js} +1 -1
  32. stoobly_agent/public/{main-es5.74eedbf8ac279a527ac3.js → main-es5.3ad631f688af79edb9e7.js} +1 -1
  33. stoobly_agent/public/runtime-es2015.ce140e9ace0d883b7982.js +1 -0
  34. stoobly_agent/public/runtime-es5.ce140e9ace0d883b7982.js +1 -0
  35. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  36. {stoobly_agent-0.33.2.dist-info → stoobly_agent-0.33.4.dist-info}/METADATA +1 -1
  37. {stoobly_agent-0.33.2.dist-info → stoobly_agent-0.33.4.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.2.dist-info → stoobly_agent-0.33.4.dist-info}/LICENSE +0 -0
  53. {stoobly_agent-0.33.2.dist-info → stoobly_agent-0.33.4.dist-info}/WHEEL +0 -0
  54. {stoobly_agent-0.33.2.dist-info → stoobly_agent-0.33.4.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'
2
+ VERSION = '0.33.4'
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import pdb
3
+ import uuid
3
4
 
4
5
  from datetime import datetime
5
6
  from urllib.parse import urlparse
@@ -63,6 +64,7 @@ class RequestsController:
63
64
  request, status = request_model.create(**{
64
65
  **create_params,
65
66
  'scenario_id': scenario_id,
67
+ 'uuid': str(uuid.uuid4()),
66
68
  })
67
69
 
68
70
  if context.filter_response(request, status):
@@ -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]:
@@ -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
  }
@@ -19,11 +19,14 @@ def build_request_columns(flow: MitmproxyHTTPFlow, joined_request: JoinedRequest
19
19
  'control': joined_request.request_string.control,
20
20
  'latency': joined_request.response_string.latency,
21
21
  'raw': joined_request.request_string.get(),
22
- 'status': flow.response.status_code,
23
- 'uuid': joined_request.request_string.request_id.strip(),
22
+ 'status': flow.response.status_code,
24
23
  }
25
24
 
26
- return { **request_columns, **params }
25
+ return {
26
+ **request_columns,
27
+ **params,
28
+ **{ 'uuid': params.get('uuid') or joined_request.request_string.request_id.strip() }
29
+ }
27
30
 
28
31
  def build_response_columns(flow: MitmproxyHTTPFlow, joined_request: JoinedRequest):
29
32
  response_columns: ResponseColumns = {
@@ -43,9 +43,11 @@ class LocalDBRequestAdapter(LocalDBAdapter):
43
43
  flow: MitmproxyHTTPFlow = params['flow']
44
44
  joined_request: JoinedRequest = params['joined_request']
45
45
  scenario_id = params.get('scenario_id')
46
+ uuid = params.get('uuid')
47
+
48
+ request_columns = build_request_columns(flow, joined_request, is_deleted=False, scenario_id=scenario_id, uuid=uuid)
46
49
 
47
50
  with ORM.instance().db.transaction():
48
- request_columns = build_request_columns(flow, joined_request, is_deleted=False, scenario_id=scenario_id)
49
51
  request_record = self.__request_orm.create(**request_columns)
50
52
 
51
53
  response_columns = {
@@ -53,10 +55,7 @@ class LocalDBRequestAdapter(LocalDBAdapter):
53
55
  **build_response_columns(flow, joined_request),
54
56
  }
55
57
 
56
- response_record = self.__response_orm.create(**response_columns)
57
- if not response_record:
58
- request_record.delete()
59
- return self.internal_error('Could not create requeset response')
58
+ self.__response_orm.create(**response_columns)
60
59
 
61
60
  return self.success({
62
61
  'list': [ORMToStooblyRequestTransformer(request_record, {}).transform()],
@@ -8,6 +8,7 @@ class RequestCreateParams(TypedDict):
8
8
  project_id: str
9
9
  joined_request: JoinedRequest
10
10
  scenario_id: str
11
+ uuid: str
11
12
 
12
13
  class RequestDestroyParams(TypedDict):
13
14
  force: bool
@@ -4,6 +4,7 @@ class ScenarioCreateParams(TypedDict):
4
4
  description: str
5
5
  name: str
6
6
  priority: int
7
+ uuid: str
7
8
 
8
9
  class ScenarioDestroyParams(TypedDict):
9
10
  force: bool
@@ -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
- expected = test_context.cached_expected_response_content
72
+ expected = test_context.cached_rewritten_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):
@@ -54,11 +54,11 @@ class TestContext(TestContextABC):
54
54
  return self
55
55
 
56
56
  @property
57
- def cached_expected_response_content(self) -> Union[bytes, None, str]:
57
+ def cached_rewritten_expected_response_content(self) -> FuzzyContent:
58
58
  if not self.__cached_rewritten_expected_response_content:
59
59
  return self.rewritten_expected_response_content
60
60
 
61
- return encode_response(self.__cached_rewritten_expected_response_content, self.__expected_response.content_type)
61
+ return self.__cached_rewritten_expected_response_content
62
62
 
63
63
  @property
64
64
  def endpoint(self) -> EndpointFacade:
@@ -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
 
@@ -20,7 +21,7 @@ class TestContextABC(abc.ABC):
20
21
 
21
22
  @property
22
23
  @abc.abstractmethod
23
- def cached_expected_response_content(self) -> Union[bytes, None, str]:
24
+ def cached_rewritten_expected_response_content(self) -> FuzzyContent:
24
25
  pass
25
26
 
26
27
  @property
@@ -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
@@ -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'
@@ -13,7 +13,7 @@ class AddUuidColumnToRequests(Migration):
13
13
  Run the migrations.
14
14
  """
15
15
  with self.schema.table('requests') as table:
16
- table.string('uuid', 36).default('').index()
16
+ table.string('uuid', 36).default('').index().unique()
17
17
 
18
18
  for request in Request.all():
19
19
  control = RequestStringControl(request.control)
@@ -11,7 +11,7 @@ class AddUuidColumnToScenarios(Migration):
11
11
  Run the migrations.
12
12
  """
13
13
  with self.schema.table('scenarios') as table:
14
- table.string('uuid', 36).index().default('')
14
+ table.string('uuid', 36).default('').index().unique()
15
15
 
16
16
  for scenario in Scenario.all():
17
17
  scenario.update(uuid=str(uuid.uuid4()))
@@ -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):