stoobly-agent 0.34.0__py3-none-any.whl → 0.34.1__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 (27) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/cli/dev_tools_cli.py +1 -0
  3. stoobly_agent/app/cli/endpoint_cli.py +2 -2
  4. stoobly_agent/app/cli/helpers/endpoints_import_service.py +14 -9
  5. stoobly_agent/app/cli/helpers/replay_facade.py +4 -0
  6. stoobly_agent/app/cli/request_cli.py +2 -0
  7. stoobly_agent/app/cli/scenario_cli.py +2 -0
  8. stoobly_agent/app/cli/types/test.py +2 -0
  9. stoobly_agent/app/models/factories/resource/local_db/helpers/request_builder.py +1 -1
  10. stoobly_agent/app/proxy/handle_mock_service.py +15 -3
  11. stoobly_agent/app/proxy/intercept_settings.py +39 -0
  12. stoobly_agent/app/proxy/mock/eval_fixtures_service.py +61 -0
  13. stoobly_agent/app/proxy/mock/types/__init__.py +10 -0
  14. stoobly_agent/app/proxy/replay/replay_request_service.py +11 -3
  15. stoobly_agent/app/proxy/run.py +6 -0
  16. stoobly_agent/app/proxy/test/context.py +12 -0
  17. stoobly_agent/app/proxy/test/context_abc.py +15 -0
  18. stoobly_agent/cli.py +13 -2
  19. stoobly_agent/config/constants/custom_headers.py +2 -0
  20. stoobly_agent/config/constants/env_vars.py +2 -0
  21. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  22. stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py +95 -0
  23. {stoobly_agent-0.34.0.dist-info → stoobly_agent-0.34.1.dist-info}/METADATA +1 -1
  24. {stoobly_agent-0.34.0.dist-info → stoobly_agent-0.34.1.dist-info}/RECORD +27 -24
  25. {stoobly_agent-0.34.0.dist-info → stoobly_agent-0.34.1.dist-info}/LICENSE +0 -0
  26. {stoobly_agent-0.34.0.dist-info → stoobly_agent-0.34.1.dist-info}/WHEEL +0 -0
  27. {stoobly_agent-0.34.0.dist-info → stoobly_agent-0.34.1.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  COMMAND = 'stoobly-agent'
2
- VERSION = '0.34.0'
2
+ VERSION = '0.34.1'
@@ -5,6 +5,7 @@ import sys
5
5
  from stoobly_agent import VERSION
6
6
  from stoobly_agent.app.proxy.replay.body_parser_service import decode_response, is_traversable
7
7
  from stoobly_agent.app.models.adapters.raw_http_response_adapter import RawHttpResponseAdapter
8
+ from stoobly_agent.lib.api.response_param_names_resource import ResponseParamNamesResource
8
9
  from stoobly_agent.lib.orm.migrate_service import migrate as database_migrate, rollback as database_rollback
9
10
  from stoobly_agent.lib.orm.replayed_response import ReplayedResponse
10
11
  from stoobly_agent.lib.orm.request import Request
@@ -47,7 +47,7 @@ def apply(**kwargs):
47
47
  context.with_lifecycle_hooks_path(kwargs.get('lifecycle_hooks_path'))
48
48
  context.with_source(kwargs.get('source_path'), kwargs.get('source_format'))
49
49
 
50
- endpoint_handler = lambda endpoint: print(f"{bcolors.OKBLUE}Applying Endpoint: {endpoint['path']}{bcolors.ENDC}")
50
+ endpoint_handler = lambda endpoint: print(f"{bcolors.OKBLUE}Applying Endpoint {endpoint['method']} {endpoint['path']}{bcolors.ENDC}")
51
51
  context.with_endpoint_handler(endpoint_handler)
52
52
 
53
53
  request_handler = lambda request, endpoint: print(request.url)
@@ -72,7 +72,7 @@ def import_endpoint(**kwargs):
72
72
 
73
73
  context.with_source(kwargs.get('source_path'), kwargs.get('source_format'))
74
74
 
75
- endpoint_handler = lambda endpoint: print(f"{bcolors.OKBLUE}Importing Endpoint: {endpoint['method']} {endpoint['path']}{bcolors.ENDC}")
75
+ endpoint_handler = lambda endpoint: print(f"{bcolors.OKBLUE}Importing Endpoint {endpoint['method']} {endpoint['path']}{bcolors.ENDC}")
76
76
  context.with_endpoint_handler(endpoint_handler)
77
77
 
78
78
  facade = EndpointFacade(settings)
@@ -24,7 +24,7 @@ def import_endpoints(context: EndpointsImportContext):
24
24
  try:
25
25
  res = import_endpoint(context.project_id, context.resources['endpoint'], endpoint)
26
26
  except requests.HTTPError as e:
27
- error_handler(endpoint, e)
27
+ error_handler(e, f"Failed to import endpoint: {endpoint['method']} {endpoint['path']}")
28
28
  return
29
29
 
30
30
  if res.status_code == 409: # Endpoint already created
@@ -34,11 +34,12 @@ def import_endpoints(context: EndpointsImportContext):
34
34
  try:
35
35
  res = import_component_names(context.project_id, endpoint_id, endpoint, context.resources)
36
36
  except requests.HTTPError as e:
37
- error_handler(endpoint, e)
37
+ error_handler(e, f"Failed to import endpoint: {endpoint['method']} {endpoint['path']}")
38
+
38
39
  try:
39
40
  cleanup_endpoint(context.project_id, endpoint_id, context.resources['endpoint'])
40
41
  except requests.HTTPError as e:
41
- print(f"{bcolors.FAIL}Encountered {e.response.status_code} {e.response.reason}: {e.response.text} - Failed to delete endpoint: {endpoint['method']} {endpoint['path']}{bcolors.ENDC}", file=sys.stderr)
42
+ error_handler(e, f"Failed to delete endpoint: {endpoint['method']} {endpoint['path']}, id {endpoint_id}")
42
43
  return
43
44
 
44
45
  def import_endpoint(project_id: int, endpoint_resource: EndpointsResource, endpoint: EndpointShowResponse):
@@ -133,15 +134,19 @@ def import_component_names(project_id: int, endpoint_id: int, endpoint: Endpoint
133
134
  process_import(component_name, project_id, endpoint_id, resource, component)
134
135
 
135
136
  def cleanup_endpoint(project_id: int, endpoint_id: int, resource: EndpointsResource):
136
- print(f"{bcolors.FAIL}Cleaning up partial import...{bcolors.ENDC}")
137
+ print(f"{bcolors.OKBLUE}Cleaning up partial import...{bcolors.ENDC}")
137
138
  res: requests.Response = resource.destroy(endpoint_id, **{
138
139
  'project_id': project_id
139
140
  })
140
141
 
141
142
  res.raise_for_status()
142
143
 
143
- def error_handler(endpoint: EndpointShowResponse, error: requests.HTTPError):
144
- print(
145
- f"{bcolors.FAIL}Encountered {error.response.status_code} {error.response.reason}: {error.response.text} - Failed to import endpoint: {endpoint['method']} {endpoint['path']} - Aborting operation{bcolors.ENDC}",
146
- file=sys.stderr
147
- )
144
+ def error_handler(error: requests.HTTPError, message = ''):
145
+ if message:
146
+ print(
147
+ f"{bcolors.FAIL}{message}{bcolors.ENDC}",
148
+ file=sys.stderr
149
+ )
150
+
151
+ print(error.response.text, file=sys.stderr)
152
+ print(f"Error {error.response.status_code} {error.response.reason}", file=sys.stderr)
@@ -17,7 +17,9 @@ class ReplayCliOptions(TypedDict):
17
17
  lifecycle_hooks_path: str
18
18
  on_response: Callable
19
19
  project_key: str
20
+ public_directory_path: str
20
21
  record: bool
22
+ response_fixtures_path: str
21
23
  scenario_key: str
22
24
  scheme: str
23
25
  trace: Trace
@@ -57,7 +59,9 @@ class ReplayFacade():
57
59
  'host': cli_options.get('host'),
58
60
  'lifecycle_hooks_path': cli_options.get('lifecycle_hooks_path'),
59
61
  'overwrite': cli_options.get('overwrite'),
62
+ 'public_directory_path': cli_options.get('public_directory_path'),
60
63
  'request_origin': request_origin.CLI,
64
+ 'response_fixtures_path': cli_options.get('response_fixtures_path'),
61
65
  'save': cli_options.get('save'),
62
66
  'scheme': cli_options.get('scheme'),
63
67
  'trace_context': trace_context,
@@ -134,8 +134,10 @@ if not is_remote:
134
134
  Configure which tests to print. Defaults to {test_output_level.PASSED}.
135
135
  '''
136
136
  )
137
+ @click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
137
138
  @ConditionalDecorator(lambda f: click.option('--remote-project-key', help='Use remote project for endpoint definitions.')(f), is_remote and is_local)
138
139
  @ConditionalDecorator(lambda f: click.option('--report-key', help='Save to report.')(f), is_remote)
140
+ @click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
139
141
  @ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='Saves test results.')(f), is_remote)
140
142
  @click.option('--scheme', type=click.Choice(['http', 'https']), help='Rewrite request scheme.')
141
143
  @click.option('--strategy', default=test_strategy.DIFF, type=click.Choice([test_strategy.CONTRACT, test_strategy.CUSTOM, test_strategy.DIFF, test_strategy.FUZZY]), help='How to test responses.')
@@ -158,8 +158,10 @@ if is_local:
158
158
  Configure which tests to print. Defaults to {test_output_level.PASSED}.
159
159
  '''
160
160
  )
161
+ @click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
161
162
  @ConditionalDecorator(lambda f: click.option('--remote-project-key', help='Use remote project for endpoint definitions.')(f), is_remote)
162
163
  @ConditionalDecorator(lambda f: click.option('--report-key', help='Save results to report.')(f), is_remote)
164
+ @click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
163
165
  @ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='Save results.')(f), is_remote)
164
166
  @click.option('--scheme', help='Rewrite request scheme.')
165
167
  @click.option(
@@ -15,8 +15,10 @@ class TestOptions(TypedDict):
15
15
  lifecycle_hooks_path: str
16
16
  log_level: logger.LogLevel
17
17
  output_level: test_output_level.TestOutputLevel
18
+ public_directory_path: str
18
19
  remote_project_key: str
19
20
  report_key: str
21
+ response_fixtures_path: str
20
22
  save: str
21
23
  scheme: str
22
24
  strategy: test_strategy.TestStrategy
@@ -35,7 +35,7 @@ class RequestBuilder():
35
35
 
36
36
  @property
37
37
  def request_body(self):
38
- return self.__properties.get('request_body')
38
+ return self.__properties.get('request_body') or b''
39
39
 
40
40
  @property
41
41
  def request_headers(self):
@@ -14,6 +14,7 @@ from stoobly_agent.lib.logger import Logger
14
14
 
15
15
  from .constants import custom_response_codes
16
16
  from .mock.context import MockContext
17
+ from .mock.eval_fixtures_service import eval_fixtures
17
18
  from .mock.eval_request_service import inject_eval_request
18
19
  from .utils.allowed_request_service import get_active_mode_policy
19
20
  from .utils.request_handler import reverse_proxy
@@ -65,14 +66,14 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
65
66
  if handle_failure:
66
67
  res = handle_failure(context)
67
68
  elif policy == mock_policy.ALL:
68
- res = eval_request_with_retry(eval_request, request, **options)
69
+ res = eval_request_with_retry(context, eval_request, **options)
69
70
 
70
71
  context.with_response(res)
71
72
 
72
73
  if handle_success:
73
74
  res = handle_success(context) or res
74
75
  elif policy == mock_policy.FOUND:
75
- res = eval_request_with_retry(eval_request, request, **options)
76
+ res = eval_request_with_retry(context, eval_request, **options)
76
77
 
77
78
  context.with_response(res)
78
79
 
@@ -93,7 +94,8 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
93
94
 
94
95
  return pass_on(context.flow, res)
95
96
 
96
- def eval_request_with_retry(eval_request, request, **options: MockOptions):
97
+ def eval_request_with_retry(context: MockContext, eval_request, **options: MockOptions):
98
+ request = context.flow.request
97
99
  infer = bool(options.get('infer'))
98
100
  ignored_components = options['ignored_components'] if 'ignored_components' in options else []
99
101
 
@@ -103,6 +105,16 @@ def eval_request_with_retry(eval_request, request, **options: MockOptions):
103
105
  ignored_components.append(res.content)
104
106
  res = eval_request(request, ignored_components, infer=infer, retry=1)
105
107
 
108
+ if res.status_code == custom_response_codes.NOT_FOUND:
109
+ intercept_settings = context.intercept_settings
110
+ fixture = eval_fixtures(
111
+ request,
112
+ public_directory_path=intercept_settings.public_directory_path,
113
+ response_fixtures=intercept_settings.response_fixtures
114
+ )
115
+ if fixture:
116
+ res = fixture
117
+
106
118
  return res
107
119
 
108
120
  def handle_request_mock(context: MockContext):
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import pdb
3
+ import yaml
3
4
 
4
5
  from runpy import run_path
5
6
  from typing import List, Union
@@ -37,6 +38,9 @@ class InterceptSettings:
37
38
  self.__lifecycle_hooks = None
38
39
  self.__initialize_lifecycle_hooks()
39
40
 
41
+ self.__response_fixtures = None
42
+ self.__initialize_response_fixtures()
43
+
40
44
  @property
41
45
  def settings(self):
42
46
  return self.__settings
@@ -102,6 +106,14 @@ class InterceptSettings:
102
106
  except InvalidProjectKey:
103
107
  pass
104
108
 
109
+ @property
110
+ def public_directory_path(self):
111
+ if self.__headers and custom_headers.PUBLIC_DIRECTORY_PATH in self.__headers:
112
+ return self.__headers[custom_headers.PUBLIC_DIRECTORY_PATH]
113
+
114
+ if os.environ.get(env_vars.AGENT_PUBLIC_DIRECTORY_PATH):
115
+ return os.environ[env_vars.AGENT_PUBLIC_DIRECTORY_PATH]
116
+
105
117
  @property
106
118
  def remote_project_key(self):
107
119
  # When not local project, don't return set remote project_key
@@ -114,6 +126,18 @@ class InterceptSettings:
114
126
 
115
127
  return self.__settings.remote.project_key
116
128
 
129
+ @property
130
+ def response_fixtures_path(self):
131
+ if self.__headers and custom_headers.RESPONSE_FIXTURES_PATH in self.__headers:
132
+ return self.__headers[custom_headers.RESPONSE_FIXTURES_PATH]
133
+
134
+ if os.environ.get(env_vars.AGENT_RESPONSE_FIXTURES_PATH):
135
+ return os.environ[env_vars.AGENT_RESPONSE_FIXTURES_PATH]
136
+
137
+ @property
138
+ def response_fixtures(self):
139
+ return self.__response_fixtures or {}
140
+
117
141
  @property
118
142
  def parsed_remote_project_key(self):
119
143
  try:
@@ -288,6 +312,21 @@ class InterceptSettings:
288
312
  except Exception as e:
289
313
  return Logger.instance().error(e)
290
314
 
315
+ def __initialize_response_fixtures(self):
316
+ fixtures_path = self.response_fixtures_path
317
+
318
+ if not fixtures_path:
319
+ return
320
+
321
+ if not os.path.exists(fixtures_path):
322
+ return Logger.instance().error(f"Response fixtures {fixtures_path} does not exist")
323
+
324
+ with open(fixtures_path, 'r') as stream:
325
+ try:
326
+ self.__response_fixtures = yaml.safe_load(stream)
327
+ except yaml.YAMLError as exc:
328
+ Logger.instance().error(exc)
329
+
291
330
  def __policy(self, mode):
292
331
  if mode == intercept_mode.MOCK:
293
332
  if self.__headers and custom_headers.MOCK_POLICY in self.__headers:
@@ -0,0 +1,61 @@
1
+ import os
2
+ import pdb
3
+ import re
4
+
5
+ from io import BytesIO
6
+ from mitmproxy.http import Request as MitmproxyRequest
7
+ from requests import Response
8
+ from typing import Union
9
+
10
+ from stoobly_agent.lib.logger import bcolors, Logger
11
+
12
+ from .types import Fixtures
13
+
14
+ class Options():
15
+ public_directory_path: str
16
+ response_fixtures: Fixtures
17
+
18
+ def eval_fixtures(request: MitmproxyRequest, **options: Options) -> Union[Response, None]:
19
+ response_fixtures = options.get('response_fixtures')
20
+
21
+ fixture_path = __eval_response_fixtures(request, response_fixtures)
22
+ if not fixture_path:
23
+ public_directory_path = options.get('public_directory_path')
24
+
25
+ if public_directory_path and os.path.exists(public_directory_path):
26
+ static_file_path = os.path.join(public_directory_path, request.path.lstrip('/'))
27
+
28
+ if os.path.exists(static_file_path):
29
+ fixture_path = static_file_path
30
+
31
+ if not fixture_path:
32
+ return
33
+
34
+ with open(fixture_path, 'rb') as fp:
35
+ response = Response()
36
+
37
+ response.status_code = 200
38
+ response.raw = BytesIO(fp.read())
39
+
40
+ Logger.instance().debug(f"{bcolors.OKBLUE}Resolved fixture {fixture_path}{bcolors.ENDC}")
41
+
42
+ return response
43
+
44
+ def __eval_response_fixtures(request: MitmproxyRequest, response_fixtures: Fixtures):
45
+ if not response_fixtures:
46
+ return
47
+
48
+ method = request.method
49
+ routes = response_fixtures.get(method)
50
+
51
+ if not routes:
52
+ return
53
+
54
+ for path_pattern in routes:
55
+ if not re.match(path_pattern, request.path):
56
+ continue
57
+
58
+ path = routes[path_pattern].get('path')
59
+
60
+ if path and os.path.exists(path):
61
+ return path
@@ -0,0 +1,10 @@
1
+ from typing import TypedDict
2
+
3
+ class Route(TypedDict):
4
+ path: str
5
+
6
+ class Fixtures(TypedDict):
7
+ DELETE: Route
8
+ GET: Route
9
+ POST: Route
10
+ PUT: Route
@@ -27,9 +27,11 @@ class ReplayRequestOptions(TypedDict):
27
27
  after_replay: Union[Callable[[ReplayContext], Union[requests.Response, None]], None]
28
28
  project_key: Union[str, None]
29
29
  proxies: dict
30
+ public_directory_path: str
30
31
  remote_project_key: str
31
32
  report_key: Union[str, None]
32
33
  request_origin: Union[request_origin.CLI, None]
34
+ response_fixtures_path: str
33
35
  scenario_key: Union[str, None]
34
36
  scheme: str
35
37
  test_filter: test_filter.TestFilter
@@ -58,7 +60,7 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
58
60
  headers[custom_headers.ALIAS_RESOLVE_STRATEGY] = options['alias_resolve_strategy']
59
61
 
60
62
  if options.get('lifecycle_hooks_path'):
61
- __handle_lifecycle_hooks_path(options['lifecycle_hooks_path'], headers)
63
+ __handle_path_header(custom_headers.LIFECYCLE_HOOKS_PATH, options['lifecycle_hooks_path'], headers)
62
64
 
63
65
  if options.get('mode'):
64
66
  __handle_mode_option(options['mode'], request, headers)
@@ -66,6 +68,9 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
66
68
  if options.get('project_key'):
67
69
  headers[custom_headers.PROJECT_KEY] = options['project_key']
68
70
 
71
+ if options.get('public_directory_path'):
72
+ __handle_path_header(custom_headers.PUBLIC_DIRECTORY_PATH, options['public_directory_path'], headers)
73
+
69
74
  if options.get('report_key'):
70
75
  headers[custom_headers.REPORT_KEY] = options['report_key']
71
76
 
@@ -75,6 +80,9 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
75
80
  if custom_headers.REQUEST_ORIGIN not in headers:
76
81
  headers[custom_headers.REQUEST_ORIGIN] = request_origin.CLI
77
82
 
83
+ if options.get('response_fixtures_path'):
84
+ __handle_path_header(custom_headers.RESPONSE_FIXTURES_PATH, options['response_fixtures_path'], headers)
85
+
78
86
  if options.get('scenario_key'):
79
87
  headers[custom_headers.SCENARIO_KEY] = options['scenario_key']
80
88
 
@@ -146,14 +154,14 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
146
154
 
147
155
  return res
148
156
 
149
- def __handle_lifecycle_hooks_path(script_path, headers):
157
+ def __handle_path_header(header_name: str, script_path: str, headers):
150
158
  if not script_path:
151
159
  return
152
160
 
153
161
  if not os.path.isabs(script_path):
154
162
  script_path = os.path.join(os.path.abspath('.'), script_path)
155
163
 
156
- headers[custom_headers.LIFECYCLE_HOOKS_PATH] = script_path
164
+ headers[header_name] = script_path
157
165
 
158
166
  def __handle_mode_option(_mode, request: Request, headers):
159
167
  headers[custom_headers.PROXY_MODE] = _mode
@@ -124,6 +124,12 @@ def __filter_options(options):
124
124
  if 'lifecycle_hooks_path' in options:
125
125
  del options['lifecycle_hooks_path']
126
126
 
127
+ if 'public_directory_path' in options:
128
+ del options['public_directory_path']
129
+
130
+ if 'response_fixtures_path' in options:
131
+ del options['response_fixtures_path']
132
+
127
133
  if 'ui_host' in options:
128
134
  del options['ui_host']
129
135
 
@@ -142,6 +142,10 @@ class TestContext(TestContextABC):
142
142
  def passed(self, v):
143
143
  self.__passed = v
144
144
 
145
+ @property
146
+ def public_directory_path(self):
147
+ return self.__intercept_settings.public_directory_path
148
+
145
149
  @property
146
150
  def replay_context(self) -> ReplayContext:
147
151
  return self.__replay_context
@@ -162,6 +166,14 @@ class TestContext(TestContextABC):
162
166
  def response(self) -> TestContextResponse:
163
167
  return self.__response
164
168
 
169
+ @property
170
+ def response_fixtures(self):
171
+ return self.__intercept_settings.response_fixtures
172
+
173
+ @property
174
+ def response_fixtures_path(self):
175
+ return self.__intercept_settings.response_fixtures_path
176
+
165
177
  @property
166
178
  def response_param_names(self) -> RequestComponentNamesFacade:
167
179
  if self.__cached_response_param_names:
@@ -119,6 +119,11 @@ class TestContextABC(abc.ABC):
119
119
  def passed(self, v):
120
120
  pass
121
121
 
122
+ @property
123
+ @abc.abstractmethod
124
+ def public_directory_path(self):
125
+ pass
126
+
122
127
  @property
123
128
  @abc.abstractmethod
124
129
  def replay_context(self):
@@ -144,6 +149,16 @@ class TestContextABC(abc.ABC):
144
149
  def response(self) -> TestContextResponse:
145
150
  pass
146
151
 
152
+ @property
153
+ @abc.abstractmethod
154
+ def response_fixtures(self):
155
+ pass
156
+
157
+ @property
158
+ @abc.abstractmethod
159
+ def response_fixtures_path(self):
160
+ pass
161
+
147
162
  @property
148
163
  @abc.abstractmethod
149
164
  def response_param_names(self) -> RequestComponentNamesFacade:
stoobly_agent/cli.py CHANGED
@@ -105,6 +105,8 @@ def init(**kwargs):
105
105
  the form of "http[s]://host[:port]".
106
106
  ''')
107
107
  @click.option('--proxy-port', default=8080, help='Proxy service port.')
108
+ @click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
109
+ @click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
108
110
  @click.option('--ssl-insecure', is_flag=True, default=False, help='Do not verify upstream server SSL/TLS certificates.')
109
111
  @click.option('--ui-host', default='0.0.0.0', help='Address to bind UI to.')
110
112
  @click.option('--ui-port', default=4200, help='UI service port.')
@@ -116,6 +118,12 @@ def run(**kwargs):
116
118
  if kwargs.get('lifecycle_hooks_path'):
117
119
  os.environ[env_vars.AGENT_LIFECYCLE_HOOKS_PATH] = kwargs['lifecycle_hooks_path']
118
120
 
121
+ if kwargs.get('public_directory_path'):
122
+ os.environ[env_vars.AGENT_PUBLIC_DIRECOTRY_PATH] = kwargs['public_directory_path']
123
+
124
+ if kwargs.get('response_fixtures_path'):
125
+ os.environ[env_vars.AGENT_RESPONSE_FIXTURES_PATH] = kwargs['response_fixtures_path']
126
+
119
127
  # Observe config for changes
120
128
  Settings.instance().watch()
121
129
 
@@ -137,7 +145,10 @@ def run(**kwargs):
137
145
  @ConditionalDecorator(lambda f: click.option('--remote-project-key', help='Use remote project for endpoint definitions.')(f), is_remote and is_local)
138
146
  @click.option('--format', type=click.Choice([RAW_FORMAT]), help='Format response')
139
147
  @click.option('-H', '--header', multiple=True, help='Pass custom header(s) to server')
148
+ @click.option('--lifecycle-hooks-path', help='Path to lifecycle hooks script.')
140
149
  @ConditionalDecorator(lambda f: click.option('--project-key')(f), is_remote)
150
+ @click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
151
+ @click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
141
152
  @click.option('-X', '--request', default='GET', help='Specify request command to use')
142
153
  @click.option('--scenario-key')
143
154
  @click.argument('url')
@@ -165,7 +176,7 @@ def mock(**kwargs):
165
176
  print_raw_response(response)
166
177
  else:
167
178
  content = response.content
168
- print(decode(content))
179
+ print(decode(content), end='')
169
180
 
170
181
  @main.command(
171
182
  help="Record request"
@@ -194,7 +205,7 @@ def record(**kwargs):
194
205
  else:
195
206
  try:
196
207
  content = response.raw.data
197
- print(content.decode(json.detect_encoding(content)))
208
+ print(content.decode(json.detect_encoding(content)), end='')
198
209
  except UnicodeDecodeError:
199
210
  print('Warning: Binary output can mess up your terminal.')
200
211
 
@@ -9,9 +9,11 @@ DO_PROXY = 'X-Do-Proxy'
9
9
  LIFECYCLE_HOOKS_PATH = 'X-Stoobly-Lifecycle-Hooks-Path'
10
10
  PROJECT_KEY = 'X-Project-Key'
11
11
  PROXY_MODE = 'X-Proxy-Mode'
12
+ PUBLIC_DIRECTORY_PATH = 'X-Stoobly-Public-Directory-Path'
12
13
  RECORD_POLICY = 'X-Record-Policy'
13
14
  REPORT_KEY = 'X-Report-Key'
14
15
  REQUEST_ORIGIN = 'X-Proxy-Request-Origin'
16
+ RESPONSE_FIXTURES_PATH = 'X-Stoobly-Response-Fixtures-Path'
15
17
  RESPONSE_ID = 'X-Response-Id'
16
18
  RESPONSE_LATENCY = 'X-Stoobly-Request-Response-Latency'
17
19
  RESPONSE_PROXY_MODE = 'X-Response-Proxy-Mode'
@@ -9,10 +9,12 @@ AGENT_LIFECYCLE_HOOKS_PATH = 'STOOBLY_AGENT_LIFECYCLE_HOOKS_PATH'
9
9
  AGENT_POLICY = 'STOOBLY_AGENT_POLICY'
10
10
  AGENT_PROJECT_KEY = 'STOOBLY_AGENT_PROJECT_KEY'
11
11
  AGENT_PROXY_URL = 'STOOBLY_AGENT_PROXY_URL'
12
+ AGENT_PUBLIC_DIRECTORY_PATH = 'STOOBLY_AGENT_PUBLIC_DIRECTORY_PATH'
12
13
  AGENT_REMOTE_ENABLED = 'STOOBLY_AGENT_REMOTE_ENABLED'
13
14
  AGENT_REMOTE_PROJECT_KEY = 'STOOBLY_AGENT_REMOTE_PROJECT_KEY'
14
15
  AGENT_REPLAY_HOST = 'STOOBLY_AGENT_REPLAY_HOST'
15
16
  AGENT_REPLAY_SCHEME = 'STOOBLY_AGENT_REPLAY_SCHEME'
17
+ AGENT_RESPONSE_FIXTURES_PATH = 'STOOBLY_AGENT_RESPONSE_FIXTURES_PATH'
16
18
  AGENT_SERVICE_URL = 'STOOBLY_AGENT_SERVICE_URL'
17
19
  AGENT_SCENARIO_KEY = 'STOOBLY_AGENT_SCENARIO_KEY'
18
20
  AGENT_SIMULATE_LATENCY = 'STOOBLY_AGENT_SIMULATE_LATENCY'
@@ -1 +1 @@
1
- 0.33.6
1
+ 0.34.0
@@ -0,0 +1,95 @@
1
+ import os
2
+ import pdb
3
+ import pytest
4
+ import requests
5
+
6
+ from click.testing import CliRunner
7
+ from mitmproxy.http import Request as MitmproxyRequest
8
+
9
+ from stoobly_agent.test.test_helper import reset
10
+
11
+ from stoobly_agent.app.models.adapters.orm.request.mitmproxy_adapter import MitmproxyRequestAdapter
12
+ from stoobly_agent.app.models.factories.resource.local_db.helpers.request_builder import RequestBuilder
13
+ from stoobly_agent.app.proxy.mock.eval_fixtures_service import eval_fixtures
14
+ from stoobly_agent.app.proxy.mock.types import Fixtures
15
+ from stoobly_agent.app.settings import Settings
16
+ from stoobly_agent.config.data_dir import DataDir
17
+ from stoobly_agent.lib.orm.request import Request
18
+
19
+ @pytest.fixture(scope='module')
20
+ def runner():
21
+ return CliRunner()
22
+
23
+ @pytest.fixture(scope='module')
24
+ def settings():
25
+ return reset()
26
+
27
+ class TestEvalFixturesService():
28
+ @pytest.fixture(scope='class')
29
+ def request_method(self):
30
+ return 'POST'
31
+
32
+ @pytest.fixture(scope='class')
33
+ def request_url(self):
34
+ return 'https://petstore.swagger.io/index.html'
35
+
36
+ @pytest.fixture(scope='class')
37
+ def created_request(
38
+ self, settings: Settings, request_method: str, request_url: str
39
+ ):
40
+ status = RequestBuilder(
41
+ method=request_method,
42
+ response_body='',
43
+ status_code=200,
44
+ url=request_url,
45
+ ).with_settings(settings).build()[1]
46
+ assert status == 200
47
+
48
+ return Request.last()
49
+
50
+ @pytest.fixture(scope='class')
51
+ def public_directory(self):
52
+ tmp_dir_path = DataDir.instance().tmp_dir_path
53
+ public_dir_path = os.path.join(tmp_dir_path, 'public')
54
+ if not os.path.exists(public_dir_path):
55
+ os.mkdir(public_dir_path)
56
+ return public_dir_path
57
+
58
+ @pytest.fixture(scope='class')
59
+ def index_file_contents(self):
60
+ return b'Hello World!'
61
+
62
+ @pytest.fixture(autouse=True, scope='class')
63
+ def index_file_path(self, public_directory: str, index_file_contents: str):
64
+ path = os.path.join(public_directory, 'index.html')
65
+ with open(path, 'wb') as fp:
66
+ fp.write(index_file_contents)
67
+ return path
68
+
69
+ @pytest.fixture(scope='class')
70
+ def response_fixtures(self, request_method: str, index_file_path: str) -> Fixtures:
71
+ fixtures = {}
72
+ fixtures[request_method] = {
73
+ '/index.html': {
74
+ 'path': index_file_path
75
+ }
76
+ }
77
+ return fixtures
78
+
79
+ @pytest.fixture()
80
+ def mitmproxy_request(self, created_request: Request) -> MitmproxyRequest:
81
+ return MitmproxyRequestAdapter(created_request).adapt()
82
+
83
+ def test_it_evaluates_response_fixture(
84
+ self, mitmproxy_request: MitmproxyRequest, response_fixtures: Fixtures, index_file_contents: str
85
+ ):
86
+ res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures=response_fixtures)
87
+ assert res
88
+ assert res.raw.read() == index_file_contents
89
+
90
+ def test_it_evaluates_public_directory(
91
+ self, mitmproxy_request: MitmproxyRequest, public_directory: str, index_file_contents: str
92
+ ):
93
+ res: requests.Response = eval_fixtures(mitmproxy_request, public_directory_path=public_directory)
94
+ assert res
95
+ assert res.raw.read() == index_file_contents
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stoobly-agent
3
- Version: 0.34.0
3
+ Version: 0.34.1
4
4
  Summary: Record, mock, and test HTTP(s) requests. CLI agent for Stoobly
5
5
  License: Apache-2.0
6
6
  Author: Matt Le
@@ -1,4 +1,4 @@
1
- stoobly_agent/__init__.py,sha256=FHVyX9boi1cPuKfMFyCS-oQoht2C6EUTcIIyHFwO7XY,45
1
+ stoobly_agent/__init__.py,sha256=o6llxNBqohA4qNGyvnHwKNOZSKfIk-O2AgU_xubMBnY,45
2
2
  stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  stoobly_agent/app/api/__init__.py,sha256=FFSlVoTgjPfUNlYPr_7u6-P5Y4WOKyaUfAHtUcB-Xio,810
4
4
  stoobly_agent/app/api/application_http_request_handler.py,sha256=jf4fkqjOiCeI2IM5Ro7ie0v_C6y0-7-5TIE_IKMPOfg,5513
@@ -23,8 +23,8 @@ stoobly_agent/app/cli/config_cli.py,sha256=FhhB1PCAX4rLuNxBGd4_x-lBHwIlGYV3KZmj2
23
23
  stoobly_agent/app/cli/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  stoobly_agent/app/cli/decorators/config.py,sha256=AWrDGZm_gjCWFYlRwdla3iE6H7OSlM4FxkaXRNovBZA,2428
25
25
  stoobly_agent/app/cli/decorators/exec.py,sha256=170tkugOHBb6Pe3AzgDSfoQ1n7ntcah7JVZz-G5cSYU,994
26
- stoobly_agent/app/cli/dev_tools_cli.py,sha256=yQ51HBEA7kf0YYjbPm7tZw8wrjVtNcbwesBZu7lb3DI,1799
27
- stoobly_agent/app/cli/endpoint_cli.py,sha256=BEDDWcpw_Rtx56cHCtcEyStmsbscGQRmg3JC5IwDp7o,3245
26
+ stoobly_agent/app/cli/dev_tools_cli.py,sha256=gNiiB5hzQrhI--K9EObW8tI6iJXRPC7LRw43qPnsKMc,1890
27
+ stoobly_agent/app/cli/endpoint_cli.py,sha256=UfeWc_UTS-P_TChZ0oe5LVaewfyhqrQXa8MDxT6GDB4,3264
28
28
  stoobly_agent/app/cli/feature_cli.py,sha256=d-MUTbK3gbZxZNJUjM43fioZyhPyzetQNKjI1rVeuhQ,1702
29
29
  stoobly_agent/app/cli/handlers/request_cli_handler.py,sha256=IPToK9OfiDda-mkq8HpiSSqK2m-UR15-8f8LVoLAHTA,6965
30
30
  stoobly_agent/app/cli/handlers/scenario_cli_handler.py,sha256=1KSsfhR1mmPUCmhjzWTjf_L9NcQglLvVK3KURrPt92A,7425
@@ -34,7 +34,7 @@ stoobly_agent/app/cli/helpers/endpoint_facade.py,sha256=_1TR9PcRnqpl9Q4njFHMM_Bt
34
34
  stoobly_agent/app/cli/helpers/endpoints_apply_context.py,sha256=PvUzJLXNKl6wIQj2q8YZK1MAm1yqzDLLlpCYMoyWb9I,1831
35
35
  stoobly_agent/app/cli/helpers/endpoints_apply_service.py,sha256=9GYuUlo-nNn6z7jmS1KaJ_gzJo896_Bl4dapGKL5ckY,2315
36
36
  stoobly_agent/app/cli/helpers/endpoints_import_context.py,sha256=w-iRZpNh3tX8GfafEe_6QMWCBiGNNSqvJKsfAXpnrLM,2209
37
- stoobly_agent/app/cli/helpers/endpoints_import_service.py,sha256=u9bEq6_4D5lqbVmOMHXSJZwxr2c7fiYL1r3VbLajqjw,6976
37
+ stoobly_agent/app/cli/helpers/endpoints_import_service.py,sha256=PQBwp2k6DeYcgc6rX8FP3TtPOq8Y2F3z6FD4IDuXkIs,6991
38
38
  stoobly_agent/app/cli/helpers/feature_flags.py,sha256=neIdXHhDMv8Zq1zrfoZ28NDXkamUgDiwnu-7AS3LshE,558
39
39
  stoobly_agent/app/cli/helpers/handle_config_update_service.py,sha256=AA67-mxHHk6cWsE0DhkQtGwoicZSCgzk3W8EcA9FNZ8,5675
40
40
  stoobly_agent/app/cli/helpers/handle_mock_service.py,sha256=bhqzI6XaFlSq39N_f1CUWnmgOhJ3MnViW31Rmjaiutw,568
@@ -45,7 +45,7 @@ stoobly_agent/app/cli/helpers/json_print.py,sha256=E7UOcxtzGCv8_GCsz0Wos1XP5RhhM
45
45
  stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py,sha256=rUM6AeOtzqCPNxzjT6I7nDlfMEv0FR67qD1OTJlwHrc,24638
46
46
  stoobly_agent/app/cli/helpers/print_service.py,sha256=Z3m-O2aj3oETTm06h_XScgxJAfulTHlEyhOCoE10VG4,4185
47
47
  stoobly_agent/app/cli/helpers/project_facade.py,sha256=rThwVur2clCCBcHa-U4_vGynj0miKv78B3uF6ZRmBSQ,1905
48
- stoobly_agent/app/cli/helpers/replay_facade.py,sha256=mI5_FtdXfuXuzMkZFPJ4t62dPNJX3bCMn4EEdEcVivk,3523
48
+ stoobly_agent/app/cli/helpers/replay_facade.py,sha256=0Saxae9HN-cdwrelJXz89X139M9bz9hY8_9hERgxjB4,3730
49
49
  stoobly_agent/app/cli/helpers/report_facade.py,sha256=LO2SURvjkzqhbGx_Jaei8I-o7eXF7QKq6lWewBkQ9Qc,1056
50
50
  stoobly_agent/app/cli/helpers/request_facade.py,sha256=HnaB_mBP-o8wWzYTeWteq5dMn-dVkMk89fgabhl7i5A,4215
51
51
  stoobly_agent/app/cli/helpers/request_synchronize_handler.py,sha256=wawAL8hoZLqOHGJtcTYeJ4H5FeBVSdB3EwgUAdGQnSE,3603
@@ -64,8 +64,8 @@ stoobly_agent/app/cli/intercept_cli.py,sha256=jYbmH9FUI6J9vTlC4ycwRRNVYk0GRC8ZI5
64
64
  stoobly_agent/app/cli/main_group.py,sha256=kHR-0P9QL2No4XG1asgDfisIDdfxgLbj30y3vEobClQ,2175
65
65
  stoobly_agent/app/cli/project_cli.py,sha256=EXjeLjbnq9PhfCjvyfZ0UnJ2tejeCS0SIAo3Nc4fKOc,3852
66
66
  stoobly_agent/app/cli/report_cli.py,sha256=ZxJw0Xkx7KFZJn9e45BSKRKon8AD0Msrwy1fbPfbv0c,2543
67
- stoobly_agent/app/cli/request_cli.py,sha256=HcMdajTWvBCsuNPgUe7ho2rextBmDjcqKJ6nslVT-aY,7545
68
- stoobly_agent/app/cli/scenario_cli.py,sha256=OOe3siBAH4O0hiVhpz2f5L442lL5Hm7zoML6txvC90c,8022
67
+ stoobly_agent/app/cli/request_cli.py,sha256=hTN9qSq7lkKYLtAZHWMCYFvuoUoYZEhsb6CWMtn0ucI,7752
68
+ stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
69
69
  stoobly_agent/app/cli/snapshot_cli.py,sha256=SEbCHt0NxCSDR86p6doNpS_SbOIVhJ-H_JD8yDOOps0,7578
70
70
  stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
71
71
  stoobly_agent/app/cli/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -73,7 +73,7 @@ stoobly_agent/app/cli/types/output.py,sha256=2wazv56g5IwLQeJCWfJXXAxTB9Y5WH1cKMH
73
73
  stoobly_agent/app/cli/types/print_options.py,sha256=UQSf2aZu4rmamjgmt5g7xIZf8KV02uVHPCt6edZn_c0,219
74
74
  stoobly_agent/app/cli/types/request.py,sha256=QthojE5sfx7OvKu-vVNnSUfGk8n4pLzuBQO0YgoO46Q,88
75
75
  stoobly_agent/app/cli/types/scenario.py,sha256=28WxmOlbm2Bsek1uu7yc4hJGz-d5oHbYAro7LlFWRoc,81
76
- stoobly_agent/app/cli/types/test.py,sha256=9nlQKxdJHbDuVGGtD4GJsjMAH2fGE3rFnh_0C_-qq6Q,655
76
+ stoobly_agent/app/cli/types/test.py,sha256=1c458B7DFBWsEk5Q1CrZ2CUi84YzEzcs-W4qTcudwAk,714
77
77
  stoobly_agent/app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
78
  stoobly_agent/app/models/adapters/__init__.py,sha256=cEEE--Bvrvk6DAsHx_uPgFhLnZJETP4zSBtWjMqyIKc,233
79
79
  stoobly_agent/app/models/adapters/joined_request_adapter.py,sha256=iINSDAV2-WlK_Mwty-dO5DNR38-1B9DykyzhGCOFeyg,2308
@@ -120,7 +120,7 @@ stoobly_agent/app/models/factories/resource/local_db/helpers/__init__.py,sha256=
120
120
  stoobly_agent/app/models/factories/resource/local_db/helpers/create_request_columns_service.py,sha256=HABMelW-cvhm2WXaywbQcd4PQhzSrz4vAbN7uOdetXM,1541
121
121
  stoobly_agent/app/models/factories/resource/local_db/helpers/log.py,sha256=BKIWS_UoRyZxbJ7Z3zU8gp-5Tvb1n3BLk-jprQje7uc,5252
122
122
  stoobly_agent/app/models/factories/resource/local_db/helpers/log_event.py,sha256=qRws34HK5fv3C_rEoIzpeIwZrSVhCG9sLz_jiuIbatY,3373
123
- stoobly_agent/app/models/factories/resource/local_db/helpers/request_builder.py,sha256=ZbYJ28LCpXNvNEy995-yZo9xfGRwekqtsGTSoN1gTPQ,3220
123
+ stoobly_agent/app/models/factories/resource/local_db/helpers/request_builder.py,sha256=PyVvsYmi5bBQ6PsUPxQ4nJM9rhhjGVOTd7ipuc2tMMM,3227
124
124
  stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py,sha256=W0CszJaKD_-e5tr7JtfWfoOBXOl5dZBCrRXPhTF7hQk,1640
125
125
  stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py,sha256=HayWx_-O7JtTYM-6k3fJbErdtoGan7M9C6l3q3KwrCI,3652
126
126
  stoobly_agent/app/models/factories/resource/local_db/helpers/search.py,sha256=A7KVcmxj9c3CT2rh26YH6khiEPkB_4U1UHhiYelNaws,782
@@ -169,13 +169,13 @@ stoobly_agent/app/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
169
169
  stoobly_agent/app/proxy/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
170
170
  stoobly_agent/app/proxy/constants/custom_response_codes.py,sha256=1CaApt_6W7GrxvN8_Ozbf_SEodVEQaNZRR2sMYpI0U8,40
171
171
  stoobly_agent/app/proxy/context.py,sha256=XLteUEP8T8aT0hHI_jHDaL2382OaC7QyVwlni5_FS9U,456
172
- stoobly_agent/app/proxy/handle_mock_service.py,sha256=q_S56IpiyliRg7Nbp2iQ1pABfilarvJHGQa9QuvQlkM,6137
172
+ stoobly_agent/app/proxy/handle_mock_service.py,sha256=z6HOX8g7ltKlQt_WlPXCiU80R3REj8cI4l2uJ_uNGKQ,6608
173
173
  stoobly_agent/app/proxy/handle_record_service.py,sha256=_6UT4vaLncPGp1XDlUVFTqyhlc6Tv70MDTSUwVw3y-U,3355
174
174
  stoobly_agent/app/proxy/handle_replay_service.py,sha256=zBsDnmibMZtYUoi9y6co6Ou5-JYHVrKiWnsJKHSkKtk,1795
175
175
  stoobly_agent/app/proxy/handle_test_service.py,sha256=nGoE2m3lfDYVs3_k-_wOXCmmf27vz99pwlsIhMxDu9I,5732
176
176
  stoobly_agent/app/proxy/hot_reload.py,sha256=RM2xPMl9qHh5SgjnRE1mnz8GilreFSQf3wpiJ6ax0Mw,903
177
177
  stoobly_agent/app/proxy/intercept_handler.py,sha256=dnKYi9lkJSoa0yLcxOwZ4nH9gljbPPExnnfAKxEXpLU,4190
178
- stoobly_agent/app/proxy/intercept_settings.py,sha256=lniZHdq2RXVwJxWfAVpaXY5moyAtB_4fVH9y2L4mSxs,10047
178
+ stoobly_agent/app/proxy/intercept_settings.py,sha256=EqcJEYnoLsk0bqjdHwOL547jp_V5g6r_XDC74ufjG3U,11349
179
179
  stoobly_agent/app/proxy/mitmproxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
180
180
  stoobly_agent/app/proxy/mitmproxy/flow_mock.py,sha256=YxQV3pXIzicvLOCFn7zmnSoAstAmR8fzONYOXAkYbT4,1153
181
181
  stoobly_agent/app/proxy/mitmproxy/request.py,sha256=niy38718CAe4Li4k40GXnWDDmUTCPkXesDZTaC8BeLc,878
@@ -186,11 +186,13 @@ stoobly_agent/app/proxy/mitmproxy/response_facade.py,sha256=NsNH0aW-mMfLcUXB-Wt9
186
186
  stoobly_agent/app/proxy/mock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
187
187
  stoobly_agent/app/proxy/mock/context.py,sha256=vDo5_3WBL73mVFnsmQWvcxvPg5nWtRJbigSrE3zGc-o,794
188
188
  stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py,sha256=Bwy7qsPzcIUw5FTaUGeaOnsTneuCCbGkTJU-wsmN3NE,415
189
+ stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=EVxYsa9QmdnRSkIuTU27yzhXE05PXKUdJQR0ujARoIk,1586
189
190
  stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=TIWRDDVTlvVc9cBkhIULtp2A89Q_PXTBGJpkCTEHkbg,6965
190
191
  stoobly_agent/app/proxy/mock/hashed_request_decorator.py,sha256=8SnIplrK7VocpvAFcg8-io2Dbhot4i6Ez6H7k3avwWs,5094
191
192
  stoobly_agent/app/proxy/mock/ignored_components_response_builder.py,sha256=E32_E1eSdmPn2SeM_e1jWnqu4xh5w_SnmOs32Shx99E,501
192
193
  stoobly_agent/app/proxy/mock/request_hasher.py,sha256=91RLNk163digRaMd1Kg88RLzXrXkpvoNKDQxB_NeiNo,3723
193
194
  stoobly_agent/app/proxy/mock/search_endpoint.py,sha256=HDN3tIkPnlgmkt-BbZDz7iwacMTmbZH77HNtk9g-ymc,841
195
+ stoobly_agent/app/proxy/mock/types/__init__.py,sha256=r4Zwqx6V5HHZwrcYWEv6xSXG0nXaN5FqxqCm0d3U-GM,150
194
196
  stoobly_agent/app/proxy/record/__init__.py,sha256=lbW-G6DZ8IrRpRzvgt-1tn2cRuyLT744u9ERIrZ2dFI,233
195
197
  stoobly_agent/app/proxy/record/context.py,sha256=L9tykwoB35hOPHft3IHSPjuJ2v3TSj9SQAJ9E1q-U_4,343
196
198
  stoobly_agent/app/proxy/record/join_request_service.py,sha256=Cz34NWBHBIRhHGtfdgnuhTYLPfWPWx6R_mSqljPGr7k,1307
@@ -208,16 +210,16 @@ stoobly_agent/app/proxy/replay/alias_resolver.py,sha256=KzCShXKuWGG88op4AIMq5wcw
208
210
  stoobly_agent/app/proxy/replay/body_parser_service.py,sha256=hPC65IxiYKdzhV1rnU7HZHor0u7KQQWukA1vYTJIDuM,3735
209
211
  stoobly_agent/app/proxy/replay/context.py,sha256=_dtU2-OmTVY_zsXRU7gdL9E7zbx9a9jSfkm9_H6vfp0,796
210
212
  stoobly_agent/app/proxy/replay/multipart.py,sha256=jEFyX5G2n5z5v9mDCEH5gAh-Nb2T2yqdmLOuqbES_2k,2828
211
- stoobly_agent/app/proxy/replay/replay_request_service.py,sha256=u9KTUlSP88r1Csbc2wAvXhpRtZ4_DnjTh6aflVrg7A4,6633
213
+ stoobly_agent/app/proxy/replay/replay_request_service.py,sha256=1OyrGb9Vfflhcfy4GBDIwa8iP5_7v3Cr7Q0zi6U9apc,7013
212
214
  stoobly_agent/app/proxy/replay/replay_scenario_service.py,sha256=9jV-iO5EBg8geUblEtjjWRFIkom_Pqmo7P-lTc3S4Xw,2824
213
215
  stoobly_agent/app/proxy/replay/rewrite_params_service.py,sha256=jEHlT6_OHq_VBa09Hd6QaRyErv7tZnziDvW7m3Q8CQg,2234
214
216
  stoobly_agent/app/proxy/replay/trace_context.py,sha256=n-jqoVeZaUqzTdHI4ImaQ5vUqLfU4sMDDPW0Ydpvuic,10362
215
- stoobly_agent/app/proxy/run.py,sha256=y_qfwGGCVjH9i1zOTOdJxdpF5ehALRwhlxQGhcQn8Ow,3790
217
+ stoobly_agent/app/proxy/run.py,sha256=VgA-IoSjvXm2wD-3j6lc3w7Aiz3QZs_s7ohx_JyVB3w,3970
216
218
  stoobly_agent/app/proxy/settings.py,sha256=R0LkSa9HrkUXvCd-nur4syJePjbQZdlnAnOPpGnCx38,2172
217
219
  stoobly_agent/app/proxy/simulate_intercept_service.py,sha256=R-L2dh2dfYFebttWXU0NwyxFI_jP6Ud36oKPC-T8HiI,1942
218
220
  stoobly_agent/app/proxy/test/__init__.py,sha256=uW0Ab27oyH2odTeVRjcuUJF8A1FLbTT5sBMzhGZr1so,89
219
- stoobly_agent/app/proxy/test/context.py,sha256=Bw_CzwlUGdKRFhW4-7kojluFJsH12Brn5D-kbSot6vM,7026
220
- stoobly_agent/app/proxy/test/context_abc.py,sha256=pMVLE1zVZYTTPTnjG9qpJoCvdd6T_PIGugnUy3Enj2Y,3336
221
+ stoobly_agent/app/proxy/test/context.py,sha256=xb8QhurwLkt7kyWr5w4gM2xS85v0Xj0xag8KwlCtdwE,7341
222
+ stoobly_agent/app/proxy/test/context_abc.py,sha256=Yt_9OBu3SDbb1ihzk2LL1sEfbVf2-ZNXVpIeGRYa2Pk,3570
221
223
  stoobly_agent/app/proxy/test/context_response.py,sha256=UJNGpW-Ageri42YBmpQ6vrIbFzggGf1Xndjca8MB6w4,1156
222
224
  stoobly_agent/app/proxy/test/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
223
225
  stoobly_agent/app/proxy/test/helpers/diff_service.py,sha256=HuHADf7uAZCtmxhny1s1l4eXnLYZf4b_rkF254T0-CU,1541
@@ -271,12 +273,12 @@ stoobly_agent/app/settings/types/remote_settings.py,sha256=4PvEGKULXM0zv29XTDzV7
271
273
  stoobly_agent/app/settings/types/ui_settings.py,sha256=BqPy2F32AbODqzi2mp2kRk28QVUydQIwVmvftn46pco,84
272
274
  stoobly_agent/app/settings/ui_settings.py,sha256=YDEUMPuJFh0SLHaGz6O-Gpz8nwsunNzeuc-TzO9OUbM,1170
273
275
  stoobly_agent/app/settings/url_rule.py,sha256=D9g5jvagGpX-4K9WuiB9lUhvy9XfNZqZ3ZtdscUTmWE,827
274
- stoobly_agent/cli.py,sha256=kvmcrelw_gYh8g8AypLIQGgoxF3Bbwe1UdrSIbCxM-o,8271
276
+ stoobly_agent/cli.py,sha256=gvIaa-OFviVIWnKEiHe7MMOZEX_hkB6LAjWaf-roZ6I,9052
275
277
  stoobly_agent/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
276
278
  stoobly_agent/config/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
277
279
  stoobly_agent/config/constants/alias_resolve_strategy.py,sha256=_R1tVqFnyGxCraVS5-dhSskaDj_X8-NthsY7i_bEt9M,119
278
- stoobly_agent/config/constants/custom_headers.py,sha256=DmU_vre0rqskof2cWvz-PZ7nOBLJxHjEwR4Tob5LRBw,1020
279
- stoobly_agent/config/constants/env_vars.py,sha256=URziZymV7Azhr8ZXyioLLLrg2dlpx66ae00Kwg2nCAE,1206
280
+ stoobly_agent/config/constants/custom_headers.py,sha256=OTxRJ4f5tDEyoXvVQWBle87ERegdWlMkXgBGVI6_jlo,1138
281
+ stoobly_agent/config/constants/env_vars.py,sha256=HAR_ZIdXXbpWQgCDaRR5RtpVyGXCsMLr_Fh8n6S12K0,1344
280
282
  stoobly_agent/config/constants/headers.py,sha256=fm5FxKroCpNHTaKMjrIO4npNmT-eAUplXt_ix0N5Ruo,223
281
283
  stoobly_agent/config/constants/lifecycle_hooks.py,sha256=aobclZmcND_mUnFKkUgpxgwd5EU3pjgAvB-NFf2dCgE,846
282
284
  stoobly_agent/config/constants/mock_policy.py,sha256=KrRVPJYRSb0hIH9fjAV9aQ8OsnhnYo9l7dylug62YVg,41
@@ -551,12 +553,13 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
551
553
  stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=Ft5POn1ZJqt3YOSdPOb0PwwCaIkBEpw3Jug9WAeAt-g,2863
552
554
  stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
553
555
  stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
554
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=zGfMIYpM6VlkogQ-R4FXptV9dQwjRgdtsQwIVm_5jik,6
556
+ stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=NE80Jl1qURiXeGm2Dnq527KijGz5PJu06aN7bchrC4A,6
555
557
  stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
556
558
  stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
557
559
  stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
558
560
  stoobly_agent/test/app/models/schemas/request_test.py,sha256=9SF43KXbjO-vMr2uObPJlyLeop_JQstl6Jrh0M1A70c,1116
559
561
  stoobly_agent/test/app/proxy/mitmproxy/request_facade_test.py,sha256=YPeYK7hndct7DuHLergmTEKv3KWAK8ns-216bbh4Wy8,5075
562
+ stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py,sha256=fLn8MYlxjpB7ZqZgROS9dksSctmeRex6MxSXMgbQ3T0,3067
560
563
  stoobly_agent/test/app/proxy/replay/body_parser_service_test.py,sha256=MTC4a3QxrptHzroHAfgrCEzCZ3Ur0Ijyj9_3k-bG0jQ,1228
561
564
  stoobly_agent/test/app/proxy/replay/rewrite_params_service_test.py,sha256=4KVaP48KjCeoZKqY3IdrFAP5Pnb3jO86k8L7ffvz2ZI,3770
562
565
  stoobly_agent/test/app/proxy/replay/trace_context_test.py,sha256=hwugGI0RMpzuanyHsDzQeck9lW_AgqMczGZ5u2wHNkE,13150
@@ -584,8 +587,8 @@ stoobly_agent/test/mock_data/petstore.yaml,sha256=CCdliJky04Az4FIOkFA883uunwFDHL
584
587
  stoobly_agent/test/mock_data/request_show_response.py,sha256=K_a0fP0QT58T8sX9PaM6hqtX1A1depZsqg_GsNPf--k,707
585
588
  stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
586
589
  stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
587
- stoobly_agent-0.34.0.dist-info/LICENSE,sha256=8QKGyy45eN76Zk52h8gu1DKX2B_gbWgZ3nzDLofEbaE,548
588
- stoobly_agent-0.34.0.dist-info/METADATA,sha256=f0Q5ZOWWGnvIOv2RG9TaHp1bSs3q1vx6xVDYunkN4h8,3304
589
- stoobly_agent-0.34.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
590
- stoobly_agent-0.34.0.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
591
- stoobly_agent-0.34.0.dist-info/RECORD,,
590
+ stoobly_agent-0.34.1.dist-info/LICENSE,sha256=8QKGyy45eN76Zk52h8gu1DKX2B_gbWgZ3nzDLofEbaE,548
591
+ stoobly_agent-0.34.1.dist-info/METADATA,sha256=YPVORaRRT7xAaOu-Kg4gWXzoalEFeq6OuWWQdMZ6j1o,3304
592
+ stoobly_agent-0.34.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
593
+ stoobly_agent-0.34.1.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
594
+ stoobly_agent-0.34.1.dist-info/RECORD,,