stoobly-agent 1.5.0__py3-none-any.whl → 1.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +2 -2
- stoobly_agent/app/cli/scaffold/constants.py +1 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +7 -10
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +1 -0
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +7 -1
- stoobly_agent/app/cli/scaffold_cli.py +10 -9
- stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py +4 -4
- stoobly_agent/app/models/factories/resource/local_db/request_adapter.py +17 -11
- stoobly_agent/app/models/types/request.py +1 -2
- stoobly_agent/app/proxy/handle_mock_service.py +75 -55
- stoobly_agent/app/proxy/handle_test_service.py +17 -8
- stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py +5 -0
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +3 -2
- stoobly_agent/app/proxy/mock/eval_request_service.py +16 -11
- stoobly_agent/app/proxy/utils/response_handler.py +11 -0
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/config/constants/query_params.py +2 -0
- stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py +4 -4
- stoobly_agent/test/cli/mock_scenario_lifecycle_hooks.py +5 -0
- stoobly_agent/test/cli/mock_scenario_test.py +62 -0
- stoobly_agent/test/cli/mock_test.py +1 -9
- {stoobly_agent-1.5.0.dist-info → stoobly_agent-1.5.2.dist-info}/METADATA +1 -1
- {stoobly_agent-1.5.0.dist-info → stoobly_agent-1.5.2.dist-info}/RECORD +27 -24
- {stoobly_agent-1.5.0.dist-info → stoobly_agent-1.5.2.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.5.0.dist-info → stoobly_agent-1.5.2.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.5.0.dist-info → stoobly_agent-1.5.2.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.5.
|
2
|
+
VERSION = '1.5.2'
|
@@ -58,9 +58,9 @@ def handle_intercept_active_update(new_settings: Settings, context: Context = No
|
|
58
58
|
scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
|
59
59
|
elif _mode == intercept_mode.MOCK:
|
60
60
|
# When mock is stopped, clear request access counts
|
61
|
-
from stoobly_agent.app.models.factories.resource.local_db.helpers.tiebreak_scenario_request import
|
61
|
+
from stoobly_agent.app.models.factories.resource.local_db.helpers.tiebreak_scenario_request import reset_sessions
|
62
62
|
|
63
|
-
|
63
|
+
reset_sessions()
|
64
64
|
|
65
65
|
def handle_scenario_update(new_settings: Settings, context = None):
|
66
66
|
new_scenario_key = __scenario_key(new_settings.proxy)
|
@@ -20,17 +20,15 @@ ca_certs_dir=$$(realpath "$${STOOBLY_CA_CERTS_DIR:-$$(realpath ~)/.mitmproxy}")
|
|
20
20
|
certs_dir=$$(realpath "$${STOOBLY_CERTS_DIR:-$(app_data_dir)/certs}")
|
21
21
|
context_dir=$$(realpath "$${STOOBLY_CONTEXT_DIR:-$(CONTEXT_DIR_DEFAULT)}")
|
22
22
|
|
23
|
-
context_dir_option=--context-dir-path $(context_dir)
|
24
23
|
user_id_option=--user-id $(USER_ID)
|
25
24
|
stoobly_exec_options=--profile $(EXEC_WORKFLOW_NAME) -p $(EXEC_WORKFLOW_NAME)
|
26
25
|
workflow_down_options=$(user_id_option)
|
27
26
|
workflow_service_options=$(shell echo $$STOOBLY_WORKFLOW_SERVICE_OPTIONS)
|
28
|
-
workflow_up_options
|
27
|
+
workflow_up_options=--app-dir-path $(app_dir) --context-dir-path $(context_dir) --ca-certs-dir-path $(ca_certs_dir) --certs-dir-path $(certs_dir) --from-make $(user_id_option)
|
29
28
|
|
30
29
|
app_data_dir=$(app_dir)/.stoobly
|
31
30
|
app_namespace_dir=$(app_data_dir)/docker
|
32
31
|
app_tmp_dir=$(app_data_dir)/tmp
|
33
|
-
data_dir=$(context_dir)/.stoobly
|
34
32
|
dockerfile_path=$(app_namespace_dir)/.Dockerfile.context
|
35
33
|
docker_compose_file_path=$(app_namespace_dir)/stoobly-ui/exec/.docker-compose.exec.yml
|
36
34
|
workflow_run_script=$(app_data_dir)/tmp/run.sh
|
@@ -38,7 +36,7 @@ workflow_run_script=$(app_data_dir)/tmp/run.sh
|
|
38
36
|
# Commands
|
39
37
|
docker_command=docker
|
40
38
|
docker_compose_command=$(docker_command) compose
|
41
|
-
exec_env=
|
39
|
+
exec_env=APP_DIR="$(app_dir)" CA_CERTS_DIR="$(ca_certs_dir)" USER_ID="$(USER_ID)"
|
42
40
|
exec_up=$(docker_compose_command) -f "$(docker_compose_file_path)" $(stoobly_exec_options) up --remove-orphans
|
43
41
|
source_env=set -a; [ -f .env ] && source .env; set +a
|
44
42
|
|
@@ -47,15 +45,14 @@ stoobly_exec_build=$(docker_command) build $(stoobly_exec_build_args) $(app_name
|
|
47
45
|
stoobly_exec_build_args=-f "$(dockerfile_path)" -t stoobly.$(USER_ID) --build-arg USER_ID=$(USER_ID) $(PULL_OPTION) --quiet
|
48
46
|
|
49
47
|
# Exec any
|
50
|
-
stoobly_exec=$(stoobly_exec_build) && $(stoobly_exec_env)
|
51
|
-
stoobly_exec_env=$(source_env) && $(exec_env)
|
48
|
+
stoobly_exec=$(stoobly_exec_build) && $(stoobly_exec_env) $(exec_up)
|
49
|
+
stoobly_exec_env=$(source_env) && $(exec_env) CONTEXT_DIR="$(context_dir)"
|
52
50
|
|
53
51
|
# Exec workflow run
|
54
52
|
# Because scaffold is stored in the application source code directory,
|
55
|
-
# when running a scaffold command from within a container,
|
56
|
-
|
57
|
-
|
58
|
-
stoobly_exec_run_env=$(source_env) && $(exec_env) && export CONTEXT_DIR="$(app_dir)"
|
53
|
+
# when running a scaffold command from within a container, it needs access to $(app_dir) rather than $(context_dir)
|
54
|
+
stoobly_exec_run=$(stoobly_exec_build) && $(stoobly_exec_run_env) $(exec_up)
|
55
|
+
stoobly_exec_run_env=$(source_env) && $(exec_env) CONTEXT_DIR="$(app_dir)"
|
59
56
|
|
60
57
|
# Workflow run
|
61
58
|
workflow_run=$(source_env) && bash "$(workflow_run_script)"
|
@@ -11,7 +11,7 @@ from stoobly_agent.lib.logger import Logger
|
|
11
11
|
|
12
12
|
from .app import App
|
13
13
|
from .constants import (
|
14
|
-
APP_NETWORK_ENV, CA_CERTS_DIR_ENV, CERTS_DIR_ENV, CONTEXT_DIR_ENV, NAMESERVERS_FILE,
|
14
|
+
APP_DIR_ENV, APP_NETWORK_ENV, CA_CERTS_DIR_ENV, CERTS_DIR_ENV, CONTEXT_DIR_ENV, NAMESERVERS_FILE,
|
15
15
|
SERVICE_DNS_ENV, SERVICE_NAME_ENV, USER_ID_ENV, WORKFLOW_NAME_ENV, WORKFLOW_NAMESPACE_ENV
|
16
16
|
)
|
17
17
|
from .docker.constants import DOCKERFILE_CONTEXT
|
@@ -41,12 +41,17 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
41
41
|
def __init__(self, app: App, **kwargs):
|
42
42
|
super().__init__(app, **kwargs)
|
43
43
|
|
44
|
+
self.__app_dir_path = os.path.abspath(kwargs.get('app_dir_path') or app.dir_path)
|
44
45
|
self.__current_working_dir = os.getcwd()
|
45
46
|
self.__ca_certs_dir_path = kwargs.get('ca_certs_dir_path') or app.ca_certs_dir_path
|
46
47
|
self.__certs_dir_path = kwargs.get('certs_dir_path') or app.certs_dir_path
|
47
48
|
self.__context_dir_path = kwargs.get('context_dir_path') or app.context_dir_path
|
48
49
|
self.__network = kwargs.get('network') or self.app_config.network
|
49
50
|
|
51
|
+
@property
|
52
|
+
def app_dir_path(self):
|
53
|
+
return self.__app_dir_path
|
54
|
+
|
50
55
|
@property
|
51
56
|
def ca_certs_dir_path(self):
|
52
57
|
return self.__ca_certs_dir_path
|
@@ -244,6 +249,7 @@ class WorkflowRunCommand(WorkflowCommand):
|
|
244
249
|
user_id = options.get('user_id')
|
245
250
|
|
246
251
|
_config = {}
|
252
|
+
_config[APP_DIR_ENV] = self.app_dir_path
|
247
253
|
_config[CA_CERTS_DIR_ENV] = self.ca_certs_dir_path
|
248
254
|
_config[CERTS_DIR_ENV] = self.certs_dir_path
|
249
255
|
_config[CONTEXT_DIR_ENV] = self.context_dir_path
|
@@ -170,7 +170,7 @@ def create(**kwargs):
|
|
170
170
|
help="List services",
|
171
171
|
name="list"
|
172
172
|
)
|
173
|
-
@click.option('--app-dir-path', default=
|
173
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
174
174
|
@click.option('--format', type=click.Choice(FORMATS), help='Format output.')
|
175
175
|
@click.option('--select', multiple=True, help='Select column(s) to display.')
|
176
176
|
@click.option('--service', multiple=True, help='Select specific services.')
|
@@ -302,8 +302,6 @@ def copy(**kwargs):
|
|
302
302
|
@click.option('--user-id', default=os.getuid(), help='OS user ID of the owner of context dir path.')
|
303
303
|
@click.argument('workflow_name')
|
304
304
|
def down(**kwargs):
|
305
|
-
cwd = os.getcwd()
|
306
|
-
|
307
305
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
308
306
|
|
309
307
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE, **kwargs)
|
@@ -322,7 +320,7 @@ def down(**kwargs):
|
|
322
320
|
config = { **kwargs }
|
323
321
|
config['service_name'] = service
|
324
322
|
command = WorkflowRunCommand(app, **config)
|
325
|
-
command.current_working_dir =
|
323
|
+
command.current_working_dir = current_working_dir
|
326
324
|
commands.append(command)
|
327
325
|
|
328
326
|
commands = sorted(commands, key=lambda command: command.service_config.priority)
|
@@ -445,11 +443,14 @@ def logs(**kwargs):
|
|
445
443
|
@click.option('--verbose', is_flag=True)
|
446
444
|
@click.argument('workflow_name')
|
447
445
|
def up(**kwargs):
|
448
|
-
cwd = os.getcwd()
|
449
|
-
|
450
446
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
451
447
|
|
452
|
-
|
448
|
+
from_make = kwargs['from_make']
|
449
|
+
|
450
|
+
# Because we are running a docker-compose command which depends on APP_DIR env var
|
451
|
+
# when we are running this command through make, the host's app_dir_path will likely differ
|
452
|
+
app_dir_path = current_working_dir if from_make else kwargs['app_dir_path']
|
453
|
+
app = App(app_dir_path, DOCKER_NAMESPACE, **kwargs)
|
453
454
|
__validate_app(app)
|
454
455
|
|
455
456
|
# If namespace is set, default network to namespace
|
@@ -469,7 +470,7 @@ def up(**kwargs):
|
|
469
470
|
config = { **kwargs }
|
470
471
|
config['service_name'] = service
|
471
472
|
command = WorkflowRunCommand(app, **config)
|
472
|
-
command.current_working_dir =
|
473
|
+
command.current_working_dir = current_working_dir
|
473
474
|
commands.append(command)
|
474
475
|
|
475
476
|
# Before services can be started, their image and network needs to be created
|
@@ -477,7 +478,7 @@ def up(**kwargs):
|
|
477
478
|
command: WorkflowRunCommand = commands[0]
|
478
479
|
|
479
480
|
init_commands = []
|
480
|
-
if not
|
481
|
+
if not from_make:
|
481
482
|
create_image_command = command.create_image(user_id=kwargs['user_id'], verbose=kwargs['verbose'])
|
482
483
|
init_commands.append(create_image_command)
|
483
484
|
|
@@ -5,7 +5,7 @@ from typing import List
|
|
5
5
|
from stoobly_agent.lib.cache import Cache
|
6
6
|
from stoobly_agent.lib.orm.request import Request
|
7
7
|
|
8
|
-
|
8
|
+
SUFFIX = 'last_request_id'
|
9
9
|
|
10
10
|
def access_request(session_id: str, request_id: int, timeout = None):
|
11
11
|
cache = Cache.instance()
|
@@ -20,8 +20,8 @@ def generate_session_id(query: dict):
|
|
20
20
|
|
21
21
|
return hashlib.md5(b'.'.join(toks)).hexdigest()
|
22
22
|
|
23
|
-
def
|
24
|
-
Cache.instance().clear(f"
|
23
|
+
def reset_sessions():
|
24
|
+
Cache.instance().clear(f".+\\.{SUFFIX}")
|
25
25
|
|
26
26
|
def tiebreak_scenario_request(session_id: str, requests: List[Request]):
|
27
27
|
if len(requests) == 0:
|
@@ -48,4 +48,4 @@ def tiebreak_scenario_request(session_id: str, requests: List[Request]):
|
|
48
48
|
|
49
49
|
def __last_request_id_key(_key = None):
|
50
50
|
_key = _key or generate_session_id()
|
51
|
-
return f"{_key}.{
|
51
|
+
return f"{_key}.{SUFFIX}"
|
@@ -10,7 +10,7 @@ from stoobly_agent.app.models.types import RequestCreateParams, RequestDestroyPa
|
|
10
10
|
from stoobly_agent.app.proxy.mock.custom_not_found_response_builder import CustomNotFoundResponseBuilder
|
11
11
|
from stoobly_agent.app.proxy.mock.ignored_components_response_builder import IgnoreComponentsResponseBuilder
|
12
12
|
from stoobly_agent.app.proxy.record.joined_request import JoinedRequest
|
13
|
-
from stoobly_agent.config.constants import custom_headers
|
13
|
+
from stoobly_agent.config.constants import custom_headers, query_params as request_query_params
|
14
14
|
from stoobly_agent.lib.orm import ORM
|
15
15
|
from stoobly_agent.lib.orm.request import Request
|
16
16
|
from stoobly_agent.lib.orm.response import Response
|
@@ -73,7 +73,6 @@ class LocalDBRequestAdapter(LocalDBAdapter):
|
|
73
73
|
def response(self, **query_params: RequestColumns) -> requests.Response:
|
74
74
|
self.__adapt_scenario_id(query_params)
|
75
75
|
|
76
|
-
endpoint_promise = query_params.get('endpoint_promise')
|
77
76
|
request = None
|
78
77
|
|
79
78
|
if not query_params.get('request_id'):
|
@@ -85,16 +84,19 @@ class LocalDBRequestAdapter(LocalDBAdapter):
|
|
85
84
|
requests = self.__request_orm.where_for(**request_columns).get()
|
86
85
|
|
87
86
|
if 'scenario_id' in query_params:
|
88
|
-
# TODO: Would need an additional ID to distinguish different scenario sessions
|
89
|
-
session_id = generate_session_id(request_columns)
|
90
|
-
|
91
87
|
if len(requests) > 1:
|
92
|
-
|
88
|
+
session_id = query_params.get(request_query_params.SESSION_ID)
|
89
|
+
request_session_id_components = { **request_columns }
|
90
|
+
|
91
|
+
if session_id:
|
92
|
+
request_session_id_components[request_query_params.SESSION_ID] = session_id
|
93
|
+
|
94
|
+
# When multiple requests are matched for a scenario, return them in sequence
|
95
|
+
request_session_id = generate_session_id(request_session_id_components)
|
96
|
+
request = tiebreak_scenario_request(request_session_id, requests)
|
97
|
+
access_request(request_session_id, request.id)
|
93
98
|
else:
|
94
99
|
request = requests.last()
|
95
|
-
|
96
|
-
if request:
|
97
|
-
access_request(session_id, request.id)
|
98
100
|
else:
|
99
101
|
request = requests.last()
|
100
102
|
else:
|
@@ -104,6 +106,7 @@ class LocalDBRequestAdapter(LocalDBAdapter):
|
|
104
106
|
request = None
|
105
107
|
|
106
108
|
if not request:
|
109
|
+
endpoint_promise = query_params.get(request_query_params.ENDPOINT_PROMISE)
|
107
110
|
return self.__handle_request_not_found(endpoint_promise)
|
108
111
|
|
109
112
|
response_record = request.response
|
@@ -315,8 +318,8 @@ class LocalDBRequestAdapter(LocalDBAdapter):
|
|
315
318
|
return candidates.get()
|
316
319
|
|
317
320
|
def __filter_request_response_columns(self, request_columns: RequestCreateParams):
|
318
|
-
if request_columns.get(
|
319
|
-
del request_columns[
|
321
|
+
if request_columns.get(request_query_params.ENDPOINT_PROMISE):
|
322
|
+
del request_columns[request_query_params.ENDPOINT_PROMISE]
|
320
323
|
|
321
324
|
if request_columns.get('infer'):
|
322
325
|
del request_columns['infer']
|
@@ -327,6 +330,9 @@ class LocalDBRequestAdapter(LocalDBAdapter):
|
|
327
330
|
if request_columns.get('retry'):
|
328
331
|
del request_columns['retry']
|
329
332
|
|
333
|
+
if request_columns.get(request_query_params.SESSION_ID):
|
334
|
+
del request_columns[request_query_params.SESSION_ID]
|
335
|
+
|
330
336
|
def __request(self, request_id: str):
|
331
337
|
if self.validate_uuid(request_id):
|
332
338
|
return self.__request_orm.find_by(uuid=request_id)
|
@@ -3,7 +3,7 @@ import pdb
|
|
3
3
|
import requests
|
4
4
|
import time
|
5
5
|
|
6
|
-
from mitmproxy.http import Request as MitmproxyRequest
|
6
|
+
from mitmproxy.http import HTTPFlow as MitmproxyHTTPFlow, Request as MitmproxyRequest
|
7
7
|
from typing import Callable, TypedDict
|
8
8
|
|
9
9
|
from stoobly_agent.app.models.request_model import RequestModel
|
@@ -18,7 +18,7 @@ from .mock.eval_fixtures_service import eval_fixtures
|
|
18
18
|
from .mock.eval_request_service import inject_eval_request
|
19
19
|
from .utils.allowed_request_service import get_active_mode_policy
|
20
20
|
from .utils.request_handler import reverse_proxy
|
21
|
-
from .utils.response_handler import bad_request, pass_on
|
21
|
+
from .utils.response_handler import bad_request, enable_cors, pass_on
|
22
22
|
from .utils.rewrite import rewrite_request, rewrite_response
|
23
23
|
|
24
24
|
LOG_ID = 'Mock'
|
@@ -44,60 +44,68 @@ def handle_request_mock_generic_without_rewrite(context: MockContext, **options:
|
|
44
44
|
# @param settings [Dict]
|
45
45
|
#
|
46
46
|
def handle_request_mock_generic(context: MockContext, **options: MockOptions):
|
47
|
+
handle_error = options['error'] if 'error' in options and callable(options['error']) else None
|
48
|
+
handle_failure = options['failure'] if 'failure' in options and callable(options['failure']) else None
|
49
|
+
handle_success = options['success'] if 'success' in options and callable(options['success']) else None
|
47
50
|
intercept_settings = context.intercept_settings
|
48
51
|
request: MitmproxyRequest = context.flow.request
|
49
|
-
|
50
|
-
handle_failure = options['failure'] if 'failure' in options and callable(options['failure']) else None
|
52
|
+
res = None
|
51
53
|
|
52
54
|
policy = get_active_mode_policy(request, intercept_settings)
|
53
55
|
if policy == mock_policy.NONE:
|
56
|
+
if handle_error:
|
57
|
+
res = handle_error(context)
|
58
|
+
|
59
|
+
return pass_on(context.flow, res)
|
60
|
+
|
61
|
+
if policy not in [mock_policy.ALL, mock_policy.FOUND]:
|
62
|
+
if handle_error:
|
63
|
+
res = handle_error(context)
|
64
|
+
|
65
|
+
return bad_request(
|
66
|
+
context.flow,
|
67
|
+
"Valid env MOCK_POLICY: %s, Got: %s" %
|
68
|
+
([mock_policy.ALL, mock_policy.FOUND, mock_policy.NONE], policy)
|
69
|
+
)
|
70
|
+
|
71
|
+
if not options.get('no_rewrite'):
|
72
|
+
__rewrite_request(context)
|
73
|
+
|
74
|
+
__mock_hook(lifecycle_hooks.BEFORE_MOCK, context)
|
75
|
+
|
76
|
+
# If ignore rules are set, then ignore specified request parameters
|
77
|
+
ignore_rules = intercept_settings.ignore_rules
|
78
|
+
if len(ignore_rules) > 0:
|
79
|
+
request_facade = MitmproxyRequestFacade(request)
|
80
|
+
_ignore_rules = request_facade.select_parameter_rules(ignore_rules)
|
81
|
+
ignored_components = rewrite_rules_to_ignored_components(_ignore_rules)
|
82
|
+
options['ignored_components'] += ignored_components if 'ignored_components' in options else ignored_components
|
83
|
+
|
84
|
+
request_model = RequestModel(intercept_settings.settings)
|
85
|
+
eval_request = inject_eval_request(request_model, intercept_settings)
|
86
|
+
|
87
|
+
if policy == mock_policy.ALL:
|
88
|
+
res = eval_request_with_retry(context, eval_request, **options)
|
89
|
+
|
90
|
+
context.with_response(res)
|
91
|
+
elif policy == mock_policy.FOUND:
|
92
|
+
res = eval_request_with_retry(context, eval_request, **options)
|
93
|
+
|
94
|
+
context.with_response(res)
|
95
|
+
|
96
|
+
if res.status_code in [custom_response_codes.NOT_FOUND, custom_response_codes.IGNORE_COMPONENTS]:
|
97
|
+
try:
|
98
|
+
res = __handle_found_policy(context)
|
99
|
+
except RuntimeError:
|
100
|
+
# Do nothing, return custom error response
|
101
|
+
pass
|
102
|
+
|
103
|
+
if res.status_code == custom_response_codes.NOT_FOUND:
|
54
104
|
if handle_failure:
|
55
|
-
res = handle_failure(context)
|
105
|
+
res = handle_failure(context) or res
|
56
106
|
else:
|
57
|
-
if
|
58
|
-
|
59
|
-
|
60
|
-
__mock_hook(lifecycle_hooks.BEFORE_MOCK, context)
|
61
|
-
|
62
|
-
# If ignore rules are set, then ignore specified request parameters
|
63
|
-
ignore_rules = intercept_settings.ignore_rules
|
64
|
-
if len(ignore_rules) > 0:
|
65
|
-
request_facade = MitmproxyRequestFacade(request)
|
66
|
-
_ignore_rules = request_facade.select_parameter_rules(ignore_rules)
|
67
|
-
ignored_components = rewrite_rules_to_ignored_components(_ignore_rules)
|
68
|
-
options['ignored_components'] += ignored_components if 'ignored_components' in options else ignored_components
|
69
|
-
|
70
|
-
request_model = RequestModel(intercept_settings.settings)
|
71
|
-
eval_request = inject_eval_request(request_model, intercept_settings)
|
72
|
-
|
73
|
-
if policy == mock_policy.ALL:
|
74
|
-
res = eval_request_with_retry(context, eval_request, **options)
|
75
|
-
|
76
|
-
context.with_response(res)
|
77
|
-
|
78
|
-
if handle_success:
|
79
|
-
res = handle_success(context) or res
|
80
|
-
elif policy == mock_policy.FOUND:
|
81
|
-
res = eval_request_with_retry(context, eval_request, **options)
|
82
|
-
|
83
|
-
context.with_response(res)
|
84
|
-
|
85
|
-
if res.status_code in [custom_response_codes.NOT_FOUND, custom_response_codes.IGNORE_COMPONENTS]:
|
86
|
-
if handle_failure:
|
87
|
-
try:
|
88
|
-
res = handle_failure(context)
|
89
|
-
except RuntimeError:
|
90
|
-
# Do nothing, return custom error response
|
91
|
-
pass
|
92
|
-
else:
|
93
|
-
if handle_success:
|
94
|
-
res = handle_success(context) or res
|
95
|
-
else:
|
96
|
-
return bad_request(
|
97
|
-
context.flow,
|
98
|
-
"Valid env MOCK_POLICY: %s, Got: %s" %
|
99
|
-
([mock_policy.ALL, mock_policy.FOUND, mock_policy.NONE], policy)
|
100
|
-
)
|
107
|
+
if handle_success:
|
108
|
+
res = handle_success(context) or res
|
101
109
|
|
102
110
|
return pass_on(context.flow, res)
|
103
111
|
|
@@ -147,13 +155,19 @@ def handle_response_mock(context: MockContext):
|
|
147
155
|
__rewrite_response(context)
|
148
156
|
__mock_hook(lifecycle_hooks.AFTER_MOCK, context)
|
149
157
|
|
150
|
-
def __handle_mock_success(context: MockContext) -> None:
|
151
|
-
if os.environ.get(env_vars.AGENT_SIMULATE_LATENCY):
|
152
|
-
response = context.response
|
153
|
-
start_time = context.start_time
|
154
|
-
__simulate_latency(response.headers.get(custom_headers.RESPONSE_LATENCY), start_time)
|
155
|
-
|
156
158
|
def __handle_mock_failure(context: MockContext) -> None:
|
159
|
+
flow = context.flow
|
160
|
+
request = flow.request
|
161
|
+
|
162
|
+
if request.method.upper() != 'OPTIONS':
|
163
|
+
return False
|
164
|
+
|
165
|
+
# Default OPTIONS request to allow CORS
|
166
|
+
enable_cors(flow)
|
167
|
+
|
168
|
+
return True
|
169
|
+
|
170
|
+
def __handle_found_policy(context: MockContext) -> None:
|
157
171
|
req = context.flow.request
|
158
172
|
intercept_settings = context.intercept_settings
|
159
173
|
upstream_url = intercept_settings.upstream_url
|
@@ -169,6 +183,12 @@ def __handle_mock_failure(context: MockContext) -> None:
|
|
169
183
|
|
170
184
|
reverse_proxy(req, upstream_url, {})
|
171
185
|
|
186
|
+
def __handle_mock_success(context: MockContext) -> None:
|
187
|
+
if os.environ.get(env_vars.AGENT_SIMULATE_LATENCY):
|
188
|
+
response = context.response
|
189
|
+
start_time = context.start_time
|
190
|
+
__simulate_latency(response.headers.get(custom_headers.RESPONSE_LATENCY), start_time)
|
191
|
+
|
172
192
|
def __rewrite_request(context: MockContext):
|
173
193
|
# Rewrite request with paramter rules for mock
|
174
194
|
|
@@ -62,6 +62,7 @@ def handle_response_test(context: ReplayContext) -> None:
|
|
62
62
|
# At this point, the request may already been rewritten during replay, do not rewrite again
|
63
63
|
handle_request_mock_generic_without_rewrite(
|
64
64
|
MockContext(flow, intercept_settings),
|
65
|
+
error=lambda mock_context: __handle_mock_error(TestContext(context, mock_context)),
|
65
66
|
failure=lambda mock_context: __handle_mock_failure(TestContext(context, mock_context)),
|
66
67
|
#infer=intercept_settings.test_strategy == test_strategy.FUZZY, # For fuzzy testing we can use an inferred response
|
67
68
|
success=lambda mock_context: __handle_mock_success(TestContext(context, mock_context))
|
@@ -71,6 +72,22 @@ def __decorate_test_id(flow: MitmproxyHTTPFlow, test_response: TestShowResponse)
|
|
71
72
|
if test_response.get('id'):
|
72
73
|
flow.response.headers[custom_headers.TEST_ID] = str(test_response['id'])
|
73
74
|
|
75
|
+
def __handle_mock_error(test_context: TestContext):
|
76
|
+
Logger.instance().warn(f"{LOG_ID}:TestStatus: Mock not enabled")
|
77
|
+
|
78
|
+
intercept_settings = test_context.intercept_settings
|
79
|
+
|
80
|
+
if intercept_settings.request_origin == request_origin.CLI:
|
81
|
+
return build_response(False, 'No test found')
|
82
|
+
|
83
|
+
def __handle_mock_failure(test_context: TestContext) -> None:
|
84
|
+
Logger.instance().warn(f"{LOG_ID}:TestStatus: No test found")
|
85
|
+
|
86
|
+
intercept_settings = test_context.intercept_settings
|
87
|
+
|
88
|
+
if intercept_settings.request_origin == request_origin.CLI:
|
89
|
+
return build_response(False, 'No test found')
|
90
|
+
|
74
91
|
def __handle_mock_success(test_context: TestContext) -> None:
|
75
92
|
flow: MitmproxyHTTPFlow = test_context.flow
|
76
93
|
|
@@ -138,14 +155,6 @@ def __handle_mock_success(test_context: TestContext) -> None:
|
|
138
155
|
|
139
156
|
return flow.response
|
140
157
|
|
141
|
-
def __handle_mock_failure(test_context: TestContext) -> None:
|
142
|
-
Logger.instance().warn(f"{LOG_ID}:TestStatus: No test found")
|
143
|
-
|
144
|
-
intercept_settings = test_context.intercept_settings
|
145
|
-
|
146
|
-
if intercept_settings.request_origin == request_origin.CLI:
|
147
|
-
return build_response(False, 'No test found')
|
148
|
-
|
149
158
|
def __override_response(flow: MitmproxyHTTPFlow, content: bytes):
|
150
159
|
headers = { 'Content-Type': 'text/plain' }
|
151
160
|
headers[custom_headers.CONTENT_TYPE] = custom_headers.CONTENT_TYPE_TEST_RESULTS
|
@@ -12,4 +12,9 @@ class CustomNotFoundResponseBuilder():
|
|
12
12
|
def build(self):
|
13
13
|
self.__response.status_code = custom_response_codes.NOT_FOUND
|
14
14
|
self.__response.raw = BytesIO('Request not found'.encode())
|
15
|
+
self.__response.headers = {
|
16
|
+
'Access-Control-Allow-Origin': '*',
|
17
|
+
'Access-Control-Allow-Methods': 'GET, OPTIONS, POST, PATCH, PUT, DELETE',
|
18
|
+
'Content-Type': 'text/plain',
|
19
|
+
}
|
15
20
|
return self.__response
|
@@ -40,8 +40,9 @@ def eval_fixtures(request: MitmproxyRequest, **options: Options) -> Union[Respon
|
|
40
40
|
|
41
41
|
if not fixture_path:
|
42
42
|
fixture_path = _fixture_path
|
43
|
-
|
44
|
-
|
43
|
+
|
44
|
+
if not os.path.isfile(fixture_path):
|
45
|
+
return
|
45
46
|
else:
|
46
47
|
fixture_path = fixture.get('path')
|
47
48
|
if not fixture_path or not os.path.isfile(fixture_path):
|
@@ -5,16 +5,17 @@ import re
|
|
5
5
|
from mitmproxy.http import Request as MitmproxyRequest
|
6
6
|
from requests import Response
|
7
7
|
from typing import List, TypedDict, Union
|
8
|
-
from stoobly_agent.config.constants import custom_headers
|
9
8
|
|
10
|
-
from stoobly_agent.lib.api.param_builder import ParamBuilder
|
11
|
-
from stoobly_agent.lib.api.interfaces.requests import RequestResponseShowQueryParams
|
12
|
-
from stoobly_agent.lib.logger import Logger
|
13
9
|
from stoobly_agent.app.models.request_model import RequestModel
|
14
10
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
11
|
+
from stoobly_agent.app.proxy.mock.custom_not_found_response_builder import CustomNotFoundResponseBuilder
|
15
12
|
from stoobly_agent.app.settings import Settings
|
16
13
|
from stoobly_agent.app.settings.constants import request_component
|
17
14
|
from stoobly_agent.app.settings.match_rule import MatchRule
|
15
|
+
from stoobly_agent.config.constants import custom_headers, query_params as request_query_params
|
16
|
+
from stoobly_agent.lib.api.param_builder import ParamBuilder
|
17
|
+
from stoobly_agent.lib.api.interfaces.requests import RequestResponseShowQueryParams
|
18
|
+
from stoobly_agent.lib.logger import Logger
|
18
19
|
|
19
20
|
from .hashed_request_decorator import HashedRequestDecorator
|
20
21
|
from .search_endpoint import inject_search_endpoint
|
@@ -40,10 +41,6 @@ def inject_eval_request(
|
|
40
41
|
request_model, intercept_settings, request, ignored_components or [], **options
|
41
42
|
)
|
42
43
|
|
43
|
-
###
|
44
|
-
#
|
45
|
-
# @param settings [Settings.mode.mock | Settings.mode.record]
|
46
|
-
#
|
47
44
|
def eval_request(
|
48
45
|
request_model: RequestModel,
|
49
46
|
intercept_settings: InterceptSettings,
|
@@ -52,9 +49,14 @@ def eval_request(
|
|
52
49
|
**options: EvalRequestOptions
|
53
50
|
) -> Response:
|
54
51
|
query_params_builder = ParamBuilder({})
|
55
|
-
query_params_builder.with_resource_scoping(intercept_settings.project_key, intercept_settings.scenario_key)
|
56
52
|
|
57
|
-
|
53
|
+
try:
|
54
|
+
query_params_builder.with_resource_scoping(intercept_settings.project_key, intercept_settings.scenario_key)
|
55
|
+
except:
|
56
|
+
# If project_key or scenario_key are invalid, assume custom not found
|
57
|
+
return CustomNotFoundResponseBuilder().build()
|
58
|
+
|
59
|
+
# Tease out API returning ignored components on custom not found
|
58
60
|
if request_model.is_local and not options.get('retry'):
|
59
61
|
remote_project_key = intercept_settings.parsed_remote_project_key
|
60
62
|
|
@@ -63,7 +65,7 @@ def eval_request(
|
|
63
65
|
remote_project_id = remote_project_key.id
|
64
66
|
endpoint_promise = lambda: search_endpoint(remote_project_id, request.method, request.url, ignored_components=1)
|
65
67
|
|
66
|
-
query_params_builder.with_param(
|
68
|
+
query_params_builder.with_param(request_query_params.ENDPOINT_PROMISE, endpoint_promise)
|
67
69
|
|
68
70
|
ignored_components = __build_ignored_components(ignored_components_list or [])
|
69
71
|
query_params_builder.with_params(__build_request_params(request, ignored_components))
|
@@ -142,6 +144,9 @@ def __build_optional_params(request: MitmproxyRequest, options: EvalRequestOptio
|
|
142
144
|
if custom_headers.MOCK_REQUEST_ID in headers:
|
143
145
|
optional_params['request_id'] = headers[custom_headers.MOCK_REQUEST_ID]
|
144
146
|
|
147
|
+
if custom_headers.SESSION_ID in headers:
|
148
|
+
optional_params[request_query_params.SESSION_ID] = headers[custom_headers.SESSION_ID]
|
149
|
+
|
145
150
|
return optional_params
|
146
151
|
|
147
152
|
def __filter_by_match_rules(request: MitmproxyRequest, match_rules: List[MatchRule], query_params: RequestResponseShowQueryParams):
|
@@ -36,6 +36,17 @@ def bad_request(flow: MitmproxyHTTPFlow, message: str):
|
|
36
36
|
{'Content-Type': 'text/plain'} # (optional) headers
|
37
37
|
)
|
38
38
|
|
39
|
+
def enable_cors(flow: MitmproxyHTTPFlow):
|
40
|
+
flow.response = MitmproxyResponse.make(
|
41
|
+
200,
|
42
|
+
'',
|
43
|
+
{
|
44
|
+
'Access-Control-Allow-Origin': '*',
|
45
|
+
'Access-Control-Allow-Methods': 'GET, OPTIONS, POST, PATCH, PUT, DELETE',
|
46
|
+
'Access-Control-Allow-Headers': '*'
|
47
|
+
}
|
48
|
+
)
|
49
|
+
|
39
50
|
# Without deleting this header, causes parsing issues when reading response
|
40
51
|
def disable_transfer_encoding(response: MitmproxyResponse) -> None:
|
41
52
|
header_name = 'Transfer-Encoding'
|
@@ -19,6 +19,7 @@ RESPONSE_ID = 'X-Stoobly-Response-Id'
|
|
19
19
|
RESPONSE_LATENCY = 'X-Stoobly-Request-Response-Latency'
|
20
20
|
RESPONSE_PROXY_MODE = 'X-Stoobly-Response-Proxy-Mode'
|
21
21
|
SCENARIO_KEY = 'X-Stoobly-Scenario-Key'
|
22
|
+
SESSION_ID = 'X-Stoobly-Session-Id'
|
22
23
|
SERVICE_URL = 'X-Stoobly-Service-Url'
|
23
24
|
TEST_FILTER = 'X-Stoobly-Test-Filter'
|
24
25
|
TEST_ID = 'X-Stoobly-Test-Id'
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py
CHANGED
@@ -5,7 +5,7 @@ import time
|
|
5
5
|
from typing import List
|
6
6
|
|
7
7
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.tiebreak_scenario_request import (
|
8
|
-
access_request, generate_session_id,
|
8
|
+
access_request, generate_session_id, reset_sessions, tiebreak_scenario_request
|
9
9
|
)
|
10
10
|
from stoobly_agent.lib.cache import Cache
|
11
11
|
from stoobly_agent.lib.orm.request import Request
|
@@ -92,7 +92,7 @@ class TestTiebreakScenarioRequest():
|
|
92
92
|
request = tiebreak_scenario_request(session_id, requests)
|
93
93
|
assert request.id == 2
|
94
94
|
|
95
|
-
class
|
95
|
+
class TestWhenResetSessions():
|
96
96
|
@pytest.fixture(scope='class')
|
97
97
|
def created_request_one(self):
|
98
98
|
return RequestMock(1)
|
@@ -105,7 +105,7 @@ class TestTiebreakScenarioRequest():
|
|
105
105
|
def test_it_resets(self, cache: Cache, created_request_one: Request):
|
106
106
|
cache.write('persists', 1)
|
107
107
|
access_request('1', created_request_one.id)
|
108
|
-
|
108
|
+
reset_sessions()
|
109
109
|
|
110
110
|
assert cache.read('persists') != None
|
111
|
-
assert len(cache.read_all()) == 1
|
111
|
+
assert len(cache.read_all()) == 1
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import pdb
|
2
|
+
import pytest
|
3
|
+
|
4
|
+
from click.testing import CliRunner
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, NON_DETERMINISTIC_GET_REQUEST_URL
|
8
|
+
|
9
|
+
from stoobly_agent.config.constants.custom_headers import SESSION_ID
|
10
|
+
from stoobly_agent.cli import mock, record, scenario
|
11
|
+
from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey
|
12
|
+
from stoobly_agent.lib.orm.request import Request
|
13
|
+
|
14
|
+
@pytest.fixture(scope='module')
|
15
|
+
def runner():
|
16
|
+
return CliRunner()
|
17
|
+
|
18
|
+
class TestMocking():
|
19
|
+
@pytest.fixture(scope='class')
|
20
|
+
def lifecycle_hooks_path(self):
|
21
|
+
return str(Path(__file__).parent / 'mock_scenario_lifecycle_hooks.py')
|
22
|
+
|
23
|
+
class TestScenario():
|
24
|
+
@pytest.fixture(scope='class')
|
25
|
+
def scenario_key(self, runner: CliRunner):
|
26
|
+
res = runner.invoke(scenario, ['create', '--select', 'key', '--without-headers', 'test-scenario'])
|
27
|
+
assert res.exit_code == 0
|
28
|
+
return ScenarioKey(res.stdout.strip()).raw
|
29
|
+
|
30
|
+
@pytest.fixture(autouse=True, scope='class')
|
31
|
+
def recorded_request1(self, runner: CliRunner, scenario_key: str):
|
32
|
+
record_result = runner.invoke(record, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
|
33
|
+
assert record_result.exit_code == 0
|
34
|
+
return Request.last()
|
35
|
+
|
36
|
+
@pytest.fixture(autouse=True, scope='class')
|
37
|
+
def recorded_request2(self, runner: CliRunner, scenario_key: str):
|
38
|
+
record_result = runner.invoke(record, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
|
39
|
+
assert record_result.exit_code == 0
|
40
|
+
return Request.last()
|
41
|
+
|
42
|
+
def test_it_does_not_mocks(self, runner: CliRunner, scenario_key: str):
|
43
|
+
mock_result = runner.invoke(mock, ['--scenario-key', scenario_key, NON_DETERMINISTIC_GET_REQUEST_URL])
|
44
|
+
assert mock_result.exit_code == 1
|
45
|
+
|
46
|
+
def test_it_mocks(self, runner: CliRunner, scenario_key: str):
|
47
|
+
mock_result = runner.invoke(mock, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
|
48
|
+
assert mock_result.exit_code == 0
|
49
|
+
|
50
|
+
def test_it_mocks_in_order(
|
51
|
+
self, runner: CliRunner, lifecycle_hooks_path: str, recorded_request1: Request, recorded_request2: Request, scenario_key: str
|
52
|
+
):
|
53
|
+
session_id = 'test'
|
54
|
+
args = [
|
55
|
+
'--lifecycle-hooks-path', lifecycle_hooks_path, '--scenario-key', scenario_key, '--output', '/dev/null',
|
56
|
+
'-H', f"{SESSION_ID}: {session_id}", DETERMINISTIC_GET_REQUEST_URL
|
57
|
+
]
|
58
|
+
mock_result = runner.invoke(mock, args)
|
59
|
+
assert int(mock_result.stdout) == recorded_request1.id
|
60
|
+
|
61
|
+
mock_result = runner.invoke(mock, args)
|
62
|
+
assert int(mock_result.stdout) == recorded_request2.id
|
@@ -8,8 +8,7 @@ from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, reset
|
|
8
8
|
|
9
9
|
from stoobly_agent.config.constants import mode
|
10
10
|
from stoobly_agent.app.settings.constants import request_component
|
11
|
-
from stoobly_agent.cli import config, mock, record
|
12
|
-
from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey
|
11
|
+
from stoobly_agent.cli import config, mock, record
|
13
12
|
from stoobly_agent.lib.orm.request import Request
|
14
13
|
|
15
14
|
@pytest.fixture(scope='module')
|
@@ -143,10 +142,3 @@ class TestMocking():
|
|
143
142
|
mock_result = runner.invoke(mock, [self.url])
|
144
143
|
assert mock_result.exit_code == 0
|
145
144
|
|
146
|
-
class TestScenario():
|
147
|
-
|
148
|
-
@pytest.fixture
|
149
|
-
def scenario_key(self, runner: CliRunner):
|
150
|
-
res = runner.invoke(scenario, ['create', '--select', 'key', '--without-headers', 'test-scenario'])
|
151
|
-
assert res.exit_code == 0
|
152
|
-
return ScenarioKey(res.stdout.strip())
|
@@ -1,4 +1,4 @@
|
|
1
|
-
stoobly_agent/__init__.py,sha256=
|
1
|
+
stoobly_agent/__init__.py,sha256=TpjT1MsvwczXCQiCcj9IYYMVu3xK_HcFzvk6Y9h109w,44
|
2
2
|
stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
stoobly_agent/app/api/__init__.py,sha256=ctkB8KR-eXO0SFhj602huHiyvQ3PslFWd8fkcufgrAI,1000
|
4
4
|
stoobly_agent/app/api/application_http_request_handler.py,sha256=ZgCfmdr5um-OFAiuRHdBxFqPIeph9WqYkVg-oVQWw-E,5543
|
@@ -36,7 +36,7 @@ stoobly_agent/app/cli/helpers/endpoints_apply_service.py,sha256=9GYuUlo-nNn6z7jm
|
|
36
36
|
stoobly_agent/app/cli/helpers/endpoints_import_context.py,sha256=w-iRZpNh3tX8GfafEe_6QMWCBiGNNSqvJKsfAXpnrLM,2209
|
37
37
|
stoobly_agent/app/cli/helpers/endpoints_import_service.py,sha256=FSMy3TboX6hYtYt91jk97gD8a3PpSv_RR2pl3L9-0Zs,8322
|
38
38
|
stoobly_agent/app/cli/helpers/feature_flags.py,sha256=neIdXHhDMv8Zq1zrfoZ28NDXkamUgDiwnu-7AS3LshE,558
|
39
|
-
stoobly_agent/app/cli/helpers/handle_config_update_service.py,sha256=
|
39
|
+
stoobly_agent/app/cli/helpers/handle_config_update_service.py,sha256=0wGSvGP0t3tCFXMU872UJbyuenzwXwFw-ZAt_4oX0f8,5916
|
40
40
|
stoobly_agent/app/cli/helpers/handle_mock_service.py,sha256=9rHhWVXniNWsAaR1jw5GgabiK5_YApq700P3F9w3gD8,701
|
41
41
|
stoobly_agent/app/cli/helpers/handle_replay_service.py,sha256=MfCXVWoGXsuX-nt87kxLv9rScwQ_1Ec6B0m5exMAzx8,3955
|
42
42
|
stoobly_agent/app/cli/helpers/handle_test_service.py,sha256=nlso1XXjPKoPayN13FXsE_cAhhxa8VuNCW3Md7ufqiY,7212
|
@@ -73,7 +73,7 @@ stoobly_agent/app/cli/scaffold/app_config.py,sha256=Gs-BynV1vY7_PpTOenn2mqc7lBIu
|
|
73
73
|
stoobly_agent/app/cli/scaffold/app_create_command.py,sha256=sy4017eiir9MK0TEbTi1NyG9mhOGlenGGVnq0GsGUrQ,966
|
74
74
|
stoobly_agent/app/cli/scaffold/command.py,sha256=aoTsdkkBzyu7TkVSMdNQQGk0Gj874jNgFcjR14y3TuM,254
|
75
75
|
stoobly_agent/app/cli/scaffold/config.py,sha256=HZU5tkvr3dkPr4JMXZtrJlu2wxxO-134Em6jReFFcq0,688
|
76
|
-
stoobly_agent/app/cli/scaffold/constants.py,sha256=
|
76
|
+
stoobly_agent/app/cli/scaffold/constants.py,sha256=Vn7TLVmxbiLOb7_wFDE4mlwkcr4vs0ZEgMxhS1ANizA,2084
|
77
77
|
stoobly_agent/app/cli/scaffold/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
78
|
stoobly_agent/app/cli/scaffold/docker/app_builder.py,sha256=7z5pk5JKlRDHx2USxY-WurttLyyUkIVYfl34_u1x9dE,501
|
79
79
|
stoobly_agent/app/cli/scaffold/docker/builder.py,sha256=OE7r8vEdEkW3vwpmIctePiO0S5jvao5zMJNpxc-Yayw,2851
|
@@ -103,8 +103,8 @@ stoobly_agent/app/cli/scaffold/service_workflow.py,sha256=sQ_Edy_wGHKMXpD0DmhnOW
|
|
103
103
|
stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=iORyPZEQurgWVxrdMgkdD3VsCz3ar2D15dmyY3RoeKw,9904
|
104
104
|
stoobly_agent/app/cli/scaffold/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
105
105
|
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=EVoSqGJH_H7Xw1Ktan5GH2SWaOXIr916NUpptr2hkiA,158
|
106
|
-
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=
|
107
|
-
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=
|
106
|
+
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=JOunQ18rT64lYqYkFb3DVXDxP5m1WCtLgy8aAlGLyLs,7999
|
107
|
+
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=ieZmXTY0RLKowpP102uSKlfu1sbm0849T-mQz858M80,360
|
108
108
|
stoobly_agent/app/cli/scaffold/templates/app/Makefile,sha256=TEmPG7Bf0KZOnmfsgdzza3UdwcVMmM5Lj1YdLc4cgjA,79
|
109
109
|
stoobly_agent/app/cli/scaffold/templates/app/build/.config.yml,sha256=8Wt8ZZ5irvBYYS44xGrR_EWlZDuXH9kyWmquzsh7s8g,19
|
110
110
|
stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml,sha256=H43dtzQkuwWeeB-XzwQNqdKqwS04Ph_Al8AVCOjxCkQ,420
|
@@ -193,9 +193,9 @@ stoobly_agent/app/cli/scaffold/workflow_copy_command.py,sha256=R9hh5dWVz7vGld7pE
|
|
193
193
|
stoobly_agent/app/cli/scaffold/workflow_create_command.py,sha256=bjNRph9TiOzleDiG6vYAExHWLVLAROBCPt6bPxtAmDo,3408
|
194
194
|
stoobly_agent/app/cli/scaffold/workflow_env.py,sha256=x8V5pJmIiklD3f2q2-qq-CORf4YaXYq_r2JpR2MmSwk,416
|
195
195
|
stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=Bke4lMOMxuDUFuAx9nlXHbKgYMO4KAg9ASHvjz4aVWc,1372
|
196
|
-
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=
|
196
|
+
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=08tJIMAU3EasFAnE1hMPOFzMEYNtl4pxneAI3VcRmkg,9822
|
197
197
|
stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=fhHciJXg_u32Wmh8us8LhgQj8D1SxkBADtuBBF4K0FM,4377
|
198
|
-
stoobly_agent/app/cli/scaffold_cli.py,sha256=
|
198
|
+
stoobly_agent/app/cli/scaffold_cli.py,sha256=xELl0zXYen7FkjLkDcb1AJXzIzbj_D519UYeYjYKjuU,26783
|
199
199
|
stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
|
200
200
|
stoobly_agent/app/cli/snapshot_cli.py,sha256=cpCjxFYBuVwLuq_b2lIUu-5zWqupRlrp4xWgDytirSM,10047
|
201
201
|
stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
|
@@ -258,12 +258,12 @@ stoobly_agent/app/models/factories/resource/local_db/helpers/search.py,sha256=A7
|
|
258
258
|
stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot.py,sha256=8GNlt33L4zV8yIkkhmb2iLSMYegSiP91HrPyJkE9daE,733
|
259
259
|
stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_service.py,sha256=fbqh5J5sMj0n_YFQFei2xhXLrSGU9vNmSX_uifA3U3M,1671
|
260
260
|
stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_types.py,sha256=1pX0jrg13632g_yv_sF1ABzPR7LY7r2HC9g1a5zR1qI,379
|
261
|
-
stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py,sha256=
|
261
|
+
stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py,sha256=BajElzsTE115hWBT6v67eyNFnrHGz-sU6WBNCzWp7Fg,1237
|
262
262
|
stoobly_agent/app/models/factories/resource/local_db/local_db_adapter.py,sha256=uQ5wQ_n0iM8sjY5mkdqED7w4VUKFKjHQGjez1B6ruyg,384
|
263
263
|
stoobly_agent/app/models/factories/resource/local_db/orm_request_builder.py,sha256=4UjFY26ln2Drlg71R4AKY5vUZYbQ28HYQolJv3SBDqE,1581
|
264
264
|
stoobly_agent/app/models/factories/resource/local_db/query_param_adapter.py,sha256=GutK2RLOPvlCWDsj1PBwJFRNT7EwweQzjObvrA2cIaM,4067
|
265
265
|
stoobly_agent/app/models/factories/resource/local_db/replayed_response_adapter.py,sha256=vUN9ARmRVrxKHpyzWB5w-XX-quOBMFpWOKkyBxTn6mM,3634
|
266
|
-
stoobly_agent/app/models/factories/resource/local_db/request_adapter.py,sha256=
|
266
|
+
stoobly_agent/app/models/factories/resource/local_db/request_adapter.py,sha256=wtNj8H3pylwToc4Q0TDfl6v6rgN6ae48GcF8WZBfd40,12992
|
267
267
|
stoobly_agent/app/models/factories/resource/local_db/response_adapter.py,sha256=g2plyjBm5NUu1W6SVZavM2PgUYW_DuynFt_MREFhPVI,3228
|
268
268
|
stoobly_agent/app/models/factories/resource/local_db/response_header_adapter.py,sha256=lSehvihNQgX6moFY0XLDyLkTeEweCRAwQ16QSdPsvEg,3070
|
269
269
|
stoobly_agent/app/models/factories/resource/local_db/scenario_adapter.py,sha256=K6ITkYTUeEPNGL4_skL80K5G1kgqLtlM3IwpwXsleMw,3883
|
@@ -292,7 +292,7 @@ stoobly_agent/app/models/schemas/request.py,sha256=WVo-v0Di-WX5sYk5fQ5piS3uo4ROz
|
|
292
292
|
stoobly_agent/app/models/types/__init__.py,sha256=TdT8MVvcJ1i4VJl-JN60FJBEQeYsKClyjuOFzIsGdgI,161
|
293
293
|
stoobly_agent/app/models/types/endpoint.py,sha256=7e60mWQs-pNFO7KmhG9wreiF9pBYB-tct-kqQi5MIVo,588
|
294
294
|
stoobly_agent/app/models/types/replayed_response.py,sha256=VbeX2yAPWACQBc7ePQ9PfU5Ot_Vq5aKFIGrPTvMQ8Xw,403
|
295
|
-
stoobly_agent/app/models/types/request.py,sha256=
|
295
|
+
stoobly_agent/app/models/types/request.py,sha256=TLfgNSo7atZvCjdI_wdMRrH1qdxeO98JRNPaPjMEZHo,703
|
296
296
|
stoobly_agent/app/models/types/request_components.py,sha256=6qEiadFnJWaTYfnxLblIOyRytLXx5FQ9yUzkWnHr0IM,471
|
297
297
|
stoobly_agent/app/models/types/response.py,sha256=OQNvHnyLvRSJF3E4A1GaROlZekKvflzsuTZH6hDx1tY,107
|
298
298
|
stoobly_agent/app/models/types/scenario.py,sha256=865pzF2vlhpslWf_77KTcb3XmF0lO8_LJy7qzJJNWmQ,184
|
@@ -300,10 +300,10 @@ stoobly_agent/app/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
300
300
|
stoobly_agent/app/proxy/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
301
301
|
stoobly_agent/app/proxy/constants/custom_response_codes.py,sha256=1CaApt_6W7GrxvN8_Ozbf_SEodVEQaNZRR2sMYpI0U8,40
|
302
302
|
stoobly_agent/app/proxy/context.py,sha256=m6iu6QSZsim8meZS8H8DsZeXyjfoC-MRMHhQD1MFKM0,532
|
303
|
-
stoobly_agent/app/proxy/handle_mock_service.py,sha256=
|
303
|
+
stoobly_agent/app/proxy/handle_mock_service.py,sha256=2EHaQOcXhbeeKly-robt4tqBY3l5lkuJ4_ZkwW9r2Nw,8859
|
304
304
|
stoobly_agent/app/proxy/handle_record_service.py,sha256=pd_3W7YxS52KYcHqzHGDIDMXQF0oLnfmnOFFQYFNWNI,3795
|
305
305
|
stoobly_agent/app/proxy/handle_replay_service.py,sha256=kBUYd8kj8UPrsYHoBXmq06DPLjCnHo8K-QsKzhDIKcw,2526
|
306
|
-
stoobly_agent/app/proxy/handle_test_service.py,sha256=
|
306
|
+
stoobly_agent/app/proxy/handle_test_service.py,sha256=WkMPbM4argVtl-TQB7VdQIvB8cOwURAahxFX5Vkqwws,8405
|
307
307
|
stoobly_agent/app/proxy/hot_reload.py,sha256=bcxdN0WM5l7_zY2XYLV2wF7xCpGdZILi4dVsjqAVgig,983
|
308
308
|
stoobly_agent/app/proxy/intercept_handler.py,sha256=XlFwO501lsAgWvIwjnNmbsdXfBd2WuNG2O8L7eeiAjc,4180
|
309
309
|
stoobly_agent/app/proxy/intercept_settings.py,sha256=KoDoUjrwvX491NHgW2M2CvaEUHOIFejiPUX4icAPZIw,11890
|
@@ -317,9 +317,9 @@ stoobly_agent/app/proxy/mitmproxy/response_body_facade.py,sha256=vIu6cuCSiZDocvn
|
|
317
317
|
stoobly_agent/app/proxy/mitmproxy/response_facade.py,sha256=0wCSzUULUhDDV93QXUgzMNxiiSZwcu-kUB2C5z1Ck0Y,4311
|
318
318
|
stoobly_agent/app/proxy/mock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
319
319
|
stoobly_agent/app/proxy/mock/context.py,sha256=vDo5_3WBL73mVFnsmQWvcxvPg5nWtRJbigSrE3zGc-o,794
|
320
|
-
stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py,sha256=
|
321
|
-
stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=
|
322
|
-
stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=
|
320
|
+
stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py,sha256=xHjA2CNhW5A4NyPkDzws6DY5YQeJqF6b3Y3k0iJKNss,611
|
321
|
+
stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=hc4VLnN50HBaWvFnrhQUJqdH-r3jBu9DHrpt8gbvkHY,3847
|
322
|
+
stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=EKHZT_sKZhGcGFCDa3XOSzYkSJIfJJUg4164BTxKUeY,7365
|
323
323
|
stoobly_agent/app/proxy/mock/hashed_request_decorator.py,sha256=h1ma90fdaYI9LBWpMWMqWBz-RjNwI628O4VuS_uUBX4,5061
|
324
324
|
stoobly_agent/app/proxy/mock/ignored_components_response_builder.py,sha256=E32_E1eSdmPn2SeM_e1jWnqu4xh5w_SnmOs32Shx99E,501
|
325
325
|
stoobly_agent/app/proxy/mock/request_hasher.py,sha256=WeOeksmW_b5Ql0Xb0fL2WMH3MVHrm-9GYWYoKYS1teg,3755
|
@@ -375,7 +375,7 @@ stoobly_agent/app/proxy/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
|
|
375
375
|
stoobly_agent/app/proxy/utils/allowed_request_service.py,sha256=dk4xnHzCgxyvD2Ih6JDbp4_9Y7s2ar-3lpM93CW--XA,3632
|
376
376
|
stoobly_agent/app/proxy/utils/publish_change_service.py,sha256=vSpFt0Ox9fceJlalHji61N6StdkEZwAvVXhHXO6fwEI,1259
|
377
377
|
stoobly_agent/app/proxy/utils/request_handler.py,sha256=VscAXf2_F1C1yrt2gNImPkatosuGj356pARTd_WnTUE,846
|
378
|
-
stoobly_agent/app/proxy/utils/response_handler.py,sha256=
|
378
|
+
stoobly_agent/app/proxy/utils/response_handler.py,sha256=t5spjycvPDOZi0YU39gpavnYLhb7C29HnRe9RmzGCtU,1749
|
379
379
|
stoobly_agent/app/proxy/utils/rewrite.py,sha256=-Lf5H8aF63G37beoaG3ehr5LIAQfsKTWQvMCeNL34I4,2459
|
380
380
|
stoobly_agent/app/proxy/utils/rewrite_rules_to_ignored_components_service.py,sha256=TEWakpZ7C468PW8lVJsRwqkRElpaCR5EOgNNzyGAleY,1043
|
381
381
|
stoobly_agent/app/settings/__init__.py,sha256=Fm9Y4e0g_wQyPULavyv9IJopINTml7RYck6mJAPtEHY,6071
|
@@ -410,7 +410,7 @@ stoobly_agent/cli.py,sha256=nZDwOnhcNcZlr2hlznWVDnidPMe3LkVv88HOjamAqmA,10255
|
|
410
410
|
stoobly_agent/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
411
411
|
stoobly_agent/config/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
412
412
|
stoobly_agent/config/constants/alias_resolve_strategy.py,sha256=_R1tVqFnyGxCraVS5-dhSskaDj_X8-NthsY7i_bEt9M,119
|
413
|
-
stoobly_agent/config/constants/custom_headers.py,sha256=
|
413
|
+
stoobly_agent/config/constants/custom_headers.py,sha256=cZZ3nae2fpnk_uUXFNYjrPp7Cdtgv0ytRw-WdHzYMZg,1315
|
414
414
|
stoobly_agent/config/constants/env_vars.py,sha256=HAR_ZIdXXbpWQgCDaRR5RtpVyGXCsMLr_Fh8n6S12K0,1344
|
415
415
|
stoobly_agent/config/constants/headers.py,sha256=Hfv7R8_NPXAGaMiZPqywGZDnr0qcVUyfenPb4g465rE,169
|
416
416
|
stoobly_agent/config/constants/intercept_policy.py,sha256=5hIgOft8PQmCRdOHb5OEvEj10tU66DIQF3GYltlWyM8,25
|
@@ -418,6 +418,7 @@ stoobly_agent/config/constants/lifecycle_hooks.py,sha256=aobclZmcND_mUnFKkUgpxgw
|
|
418
418
|
stoobly_agent/config/constants/mitmproxy.py,sha256=AhaxRGMb7OC4DBqNvyBP0OVQSByCeA-itefzmMoIS14,116
|
419
419
|
stoobly_agent/config/constants/mock_policy.py,sha256=oS3eKL5Ezpnm_4zvztcLvvtKppxm7eKvOOBHYKyKpeU,133
|
420
420
|
stoobly_agent/config/constants/mode.py,sha256=J35vV1LPQRhD2ob6DaJ7mLGJrMhv-CVnC6IHWd7b_GM,160
|
421
|
+
stoobly_agent/config/constants/query_params.py,sha256=b-QQGKTqf-HePt0X4ltFksf5ETiIFPgBvh-kFrF7kUk,63
|
421
422
|
stoobly_agent/config/constants/record_policy.py,sha256=n5h5Mw0-RcwEGH18koE3-Krg-ek2rRMDOHdqMxJd4Gg,181
|
422
423
|
stoobly_agent/config/constants/replay_policy.py,sha256=FgSUaVb52wknIbTu27cWxvB35ZhxP0g1KNc6Twhtjmc,117
|
423
424
|
stoobly_agent/config/constants/request_origin.py,sha256=zLdLbGb8TQOK5TvsPdD17SAW_OupbM3Y9mKnv7C3BRU,39
|
@@ -692,7 +693,7 @@ stoobly_agent/test/app/models/adapters/python/request/stoobly_adapter_test.py,sh
|
|
692
693
|
stoobly_agent/test/app/models/adapters/python/response/mitmproxy_response_adapter_test.py,sha256=dEUhIH5kekZsJDwOmWz0eHA2dWZ7QPYmgq_Na9ce3HA,1176
|
693
694
|
stoobly_agent/test/app/models/adapters/python/response/raw_response_adapter_test.py,sha256=oJ0l2FZEH3-JANcpX4N83GKxqgVkaGRKIS7rsY8O7mM,1199
|
694
695
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sha256=FbGtjO_K_L2lZw1i6b1LlZoUyhisYqVST7f9T0PmpFk,3501
|
695
|
-
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=
|
696
|
+
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
|
696
697
|
stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
|
697
698
|
stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
|
698
699
|
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=H03AltWPfSHjh1ZxrubymxIKuEIY-kfbLLU7yetbTaw,5
|
@@ -714,7 +715,9 @@ stoobly_agent/test/app/test/matchers/contract_test.py,sha256=fMV8BX4JfbIOD5i7ZV7
|
|
714
715
|
stoobly_agent/test/app/test/matchers/diff_test.py,sha256=fNbzAz3MptieVIrMInxGlNa4FsBh-VQ2m7-OsrNm9sw,10266
|
715
716
|
stoobly_agent/test/app/test/matchers/fuzzy_test.py,sha256=wkySpK00dFZLlXmW7xE7Q6UPJYLiq8aCnIHt4Nm4WEk,3231
|
716
717
|
stoobly_agent/test/cli/lifecycle_hooks_test.py,sha256=zjLI8zteszWXgQuyEYM47MtI8KgLbEFVYJTaJdyBqOc,2491
|
717
|
-
stoobly_agent/test/cli/
|
718
|
+
stoobly_agent/test/cli/mock_scenario_lifecycle_hooks.py,sha256=e57A3FWHt1ZgaWKhbl4Hs2Y4ux8tKMu9nxbqPCj6w54,254
|
719
|
+
stoobly_agent/test/cli/mock_scenario_test.py,sha256=tHT-p8Vm8APP2KOlyfZ-5z_AxZO9TS4sDgui3X2qkaE,2620
|
720
|
+
stoobly_agent/test/cli/mock_test.py,sha256=zYAFhHkR-1oafZzzL_4aPOWzM2nXWRHGt36yTAuG8Rw,5636
|
718
721
|
stoobly_agent/test/cli/record_test.py,sha256=CG7prrooTgkW8RYaiDRYD3LuuDIR8dz8jaP9RNRrF4o,15814
|
719
722
|
stoobly_agent/test/config/data_dir_test.py,sha256=sszUW6MKGFCS9JGhYL-E91hBzfI2jMaHRJMifNjZ00M,2659
|
720
723
|
stoobly_agent/test/mock_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -734,8 +737,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=x7
|
|
734
737
|
stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
|
735
738
|
stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
|
736
739
|
stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
|
737
|
-
stoobly_agent-1.5.
|
738
|
-
stoobly_agent-1.5.
|
739
|
-
stoobly_agent-1.5.
|
740
|
-
stoobly_agent-1.5.
|
741
|
-
stoobly_agent-1.5.
|
740
|
+
stoobly_agent-1.5.2.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
|
741
|
+
stoobly_agent-1.5.2.dist-info/METADATA,sha256=h0M6PU0p1COzIMJABfrZtaWjTshuY-Jk2gudZzsj958,3087
|
742
|
+
stoobly_agent-1.5.2.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
743
|
+
stoobly_agent-1.5.2.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
|
744
|
+
stoobly_agent-1.5.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|