stoobly-agent 0.29.1__py3-none-any.whl → 0.30.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/configs_controller.py +5 -2
- stoobly_agent/app/cli/config_cli.py +64 -14
- stoobly_agent/app/cli/endpoint_cli.py +1 -1
- stoobly_agent/app/cli/helpers/endpoint_facade.py +1 -1
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +44 -5
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +19 -3
- stoobly_agent/app/cli/helpers/replay_facade.py +2 -2
- stoobly_agent/app/cli/intercept_cli.py +14 -5
- stoobly_agent/app/cli/request_cli.py +2 -2
- stoobly_agent/app/cli/scenario_cli.py +2 -2
- stoobly_agent/app/cli/types/endpoint.py +1 -1
- stoobly_agent/app/models/adapters/raw_http_request_adapter.py +1 -1
- stoobly_agent/app/models/adapters/raw_http_response_adapter.py +1 -1
- stoobly_agent/app/proxy/context.py +18 -0
- stoobly_agent/app/proxy/handle_mock_service.py +1 -1
- stoobly_agent/app/proxy/handle_replay_service.py +2 -1
- stoobly_agent/app/proxy/intercept_handler.py +14 -3
- stoobly_agent/app/proxy/intercept_settings.py +28 -21
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +41 -35
- stoobly_agent/app/proxy/mitmproxy/response_facade.py +0 -11
- stoobly_agent/app/proxy/mock/context.py +4 -11
- stoobly_agent/app/proxy/record/context.py +4 -12
- stoobly_agent/app/proxy/record/join_request_service.py +1 -1
- stoobly_agent/app/proxy/replay/context.py +4 -11
- stoobly_agent/app/proxy/replay/replay_request_service.py +5 -5
- stoobly_agent/app/proxy/replay/rewrite_params_service.py +1 -1
- stoobly_agent/app/proxy/run.py +3 -0
- stoobly_agent/app/proxy/test/context.py +2 -2
- stoobly_agent/app/proxy/test/context_abc.py +1 -1
- stoobly_agent/app/proxy/test/matchers/custom.py +1 -1
- stoobly_agent/app/settings/rewrite_rule.py +13 -0
- stoobly_agent/app/settings/types/__init__.py +1 -6
- stoobly_agent/app/settings/types/proxy_settings.py +6 -0
- stoobly_agent/app/settings/url_rule.py +33 -0
- stoobly_agent/cli.py +4 -0
- stoobly_agent/config/constants/custom_headers.py +1 -1
- stoobly_agent/config/constants/env_vars.py +1 -0
- stoobly_agent/config/constants/lifecycle_hooks.py +2 -0
- stoobly_agent/config/schema.yml +7 -1
- stoobly_agent/lib/orm/request.py +8 -4
- stoobly_agent/public/18-es2015.46d337c47cb41abec8ad.js +1 -0
- stoobly_agent/public/18-es5.46d337c47cb41abec8ad.js +1 -0
- stoobly_agent/public/dashboard.agent-alpha-0.30.0.tar.gz +0 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/runtime-es2015.3a15c6ce90c8f8cce796.js +1 -0
- stoobly_agent/public/runtime-es5.3a15c6ce90c8f8cce796.js +1 -0
- stoobly_agent-0.30.0.dist-info/METADATA +26 -0
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/RECORD +53 -51
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/WHEEL +1 -1
- stoobly_agent/public/18-es2015.750ef954bde422debcb6.js +0 -1
- stoobly_agent/public/18-es5.750ef954bde422debcb6.js +0 -1
- stoobly_agent/public/dashboard.agent-alpha-0.29.0.tar.gz +0 -0
- stoobly_agent/public/runtime-es2015.b392473c492ce4cf1cb5.js +0 -1
- stoobly_agent/public/runtime-es5.b392473c492ce4cf1cb5.js +0 -1
- stoobly_agent-0.29.1.dist-info/METADATA +0 -26
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/entry_points.txt +0 -0
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ from typing import Callable, List
|
|
8
8
|
from urllib.parse import urlparse
|
9
9
|
|
10
10
|
from stoobly_agent.app.settings.constants import request_component
|
11
|
-
from stoobly_agent.app.settings.rewrite_rule import RewriteRule,
|
11
|
+
from stoobly_agent.app.settings.rewrite_rule import ParameterRule, RewriteRule, UrlRule
|
12
12
|
from stoobly_agent.config.constants import custom_headers
|
13
13
|
from stoobly_agent.lib.logger import Logger, bcolors
|
14
14
|
from stoobly_agent.lib.utils import jmespath
|
@@ -29,8 +29,8 @@ class MitmproxyRequestFacade(Request):
|
|
29
29
|
self.request = request
|
30
30
|
self.uri = urlparse(self.request.url)
|
31
31
|
|
32
|
-
self.
|
33
|
-
self.
|
32
|
+
self.__url_rules: List[UrlRule] = []
|
33
|
+
self.__parameter_rules: List[ParameterRule] = []
|
34
34
|
|
35
35
|
self.__body = MitmproxyRequestBodyFacade(request)
|
36
36
|
|
@@ -99,41 +99,42 @@ class MitmproxyRequestFacade(Request):
|
|
99
99
|
return self.request.port
|
100
100
|
|
101
101
|
@property
|
102
|
-
def
|
103
|
-
return self.
|
102
|
+
def url_rules(self) -> List[ParameterRule]:
|
103
|
+
return self.__url_rules
|
104
104
|
|
105
105
|
@property
|
106
|
-
def
|
107
|
-
return self.
|
106
|
+
def parameter_rules(self) -> List[ParameterRule]:
|
107
|
+
return self.__parameter_rules
|
108
108
|
|
109
|
-
def
|
109
|
+
def with_parameter_rules(self, rules: List[RewriteRule]):
|
110
110
|
if type(rules) == list:
|
111
|
-
self.
|
111
|
+
self.__parameter_rules = self.select_parameter_rules(rules)
|
112
112
|
return self
|
113
113
|
|
114
|
-
def
|
114
|
+
def with_url_rules(self, rules: List[RewriteRule]):
|
115
115
|
if type(rules) == list:
|
116
|
-
self.
|
116
|
+
self.__url_rules = self.select_url_rules(rules)
|
117
117
|
return self
|
118
118
|
|
119
|
-
def redact(self):
|
120
|
-
redacts = self.__redact_rules
|
121
|
-
if len(redacts) != 0:
|
122
|
-
self.__redact_headers(redacts)
|
123
|
-
self.__redact_query(redacts)
|
124
|
-
self.__redact_content(redacts)
|
125
|
-
|
126
119
|
def rewrite(self):
|
127
|
-
rewrites = self.
|
120
|
+
rewrites = self.__parameter_rules
|
128
121
|
|
129
122
|
if len(rewrites) != 0:
|
130
123
|
self.__rewrite_headers(rewrites)
|
131
124
|
self.__rewrite_query(rewrites)
|
132
125
|
self.__rewrite_content(rewrites)
|
133
126
|
|
127
|
+
rewrites = self.__url_rules
|
128
|
+
|
129
|
+
if len(rewrites):
|
130
|
+
self.__rewrite_url(rewrites)
|
131
|
+
|
132
|
+
# Find all the rules that match request url and method
|
133
|
+
def select_rewrite_rules(self, rules: List[RewriteRule]) -> List[RewriteRule]:
|
134
|
+
return list(filter(self.__is_rewrite_rule_selected, rules or []))
|
135
|
+
|
134
136
|
def select_parameter_rules(self, rules: List[RewriteRule]) -> List[ParameterRule]:
|
135
|
-
|
136
|
-
_rules = list(filter(self.__is_parameter_rule_selected, rules or []))
|
137
|
+
_rules = self.select_rewrite_rules(rules)
|
137
138
|
|
138
139
|
if len(_rules) == 0:
|
139
140
|
return []
|
@@ -142,7 +143,17 @@ class MitmproxyRequestFacade(Request):
|
|
142
143
|
|
143
144
|
return [item for sublist in parameter_rules for item in sublist] # flatten list
|
144
145
|
|
145
|
-
def
|
146
|
+
def select_url_rules(self, rules: List[RewriteRule]) -> List[UrlRule]:
|
147
|
+
_rules = self.select_rewrite_rules(rules)
|
148
|
+
|
149
|
+
if len(_rules) == 0:
|
150
|
+
return []
|
151
|
+
|
152
|
+
url_rules = list(map(lambda rule: rule.url_rules, _rules))
|
153
|
+
|
154
|
+
return [item for sublist in url_rules for item in sublist] # flatten list
|
155
|
+
|
156
|
+
def __is_rewrite_rule_selected(self, rewrite_rule: RewriteRule):
|
146
157
|
pattern = rewrite_rule.pattern
|
147
158
|
|
148
159
|
try:
|
@@ -168,6 +179,14 @@ class MitmproxyRequestFacade(Request):
|
|
168
179
|
Logger.instance().info(f"{bcolors.OKCYAN}Rewriting {rewrite.type.lower()}{bcolors.ENDC} {rewrite.name} => {rewrite.value}")
|
169
180
|
return rewrite.value
|
170
181
|
|
182
|
+
def __rewrite_url(self, rewrites: List[UrlRule]):
|
183
|
+
for rewrite in rewrites:
|
184
|
+
if rewrite.host:
|
185
|
+
self.request.host = rewrite.host
|
186
|
+
|
187
|
+
if rewrite.port:
|
188
|
+
self.request.port = rewrite.port
|
189
|
+
|
171
190
|
def __rewrite_headers(self, rewrites: List[ParameterRule]):
|
172
191
|
self.__apply_headers(rewrites, self.__rewrite_handler)
|
173
192
|
|
@@ -177,19 +196,6 @@ class MitmproxyRequestFacade(Request):
|
|
177
196
|
def __rewrite_content(self, rewrites: List[ParameterRule]):
|
178
197
|
self.__apply_content(rewrites, self.__rewrite_handler)
|
179
198
|
|
180
|
-
def __redact_handler(self, rewrite: ParameterRule) -> str:
|
181
|
-
Logger.instance().debug(f"{bcolors.OKCYAN}Redacting{bcolors.ENDC} {rewrite.name}")
|
182
|
-
return '[REDACTED]'
|
183
|
-
|
184
|
-
def __redact_headers(self, redacts: List[ParameterRule]):
|
185
|
-
self.__apply_headers(redacts, self.__redact_handler)
|
186
|
-
|
187
|
-
def __redact_query(self, redacts: List[ParameterRule]):
|
188
|
-
self.__apply_queries(redacts, self.__redact_handler)
|
189
|
-
|
190
|
-
def __redact_content(self, redacts: List[ParameterRule]):
|
191
|
-
self.__apply_content(redacts, self.__redact_handler)
|
192
|
-
|
193
199
|
def __apply_headers(self, rewrites: List[ParameterRule], handler: Callable):
|
194
200
|
rewrites = list(filter(lambda rewrite: rewrite.type == request_component.HEADER, rewrites))
|
195
201
|
self.__apply_rewrites(self.request.headers, rewrites, handler)
|
@@ -11,7 +11,6 @@ class MitmproxyResponseFacade(Response):
|
|
11
11
|
self.content = response.raw_content
|
12
12
|
|
13
13
|
self.rewrite_rules = []
|
14
|
-
self.rewrite_rules = []
|
15
14
|
|
16
15
|
@property
|
17
16
|
def code(self):
|
@@ -42,22 +41,12 @@ class MitmproxyResponseFacade(Response):
|
|
42
41
|
self.response.headers['content-length'] = str(len(self.content))
|
43
42
|
|
44
43
|
|
45
|
-
def with_redact_rules(self, rules: RewriteRule):
|
46
|
-
if type(rules) == list:
|
47
|
-
self.rewrite_rules = rules
|
48
|
-
|
49
|
-
return self
|
50
|
-
|
51
44
|
def with_rewrite_rules(self, rules: RewriteRule):
|
52
45
|
if type(rules) == list:
|
53
46
|
self.rewrite_rules = rules
|
54
47
|
|
55
48
|
return self
|
56
49
|
|
57
|
-
# TODO
|
58
|
-
def redact(self):
|
59
|
-
pass
|
60
|
-
|
61
50
|
# TODO
|
62
51
|
def rewrite(self):
|
63
52
|
pass
|
@@ -5,10 +5,11 @@ from requests import Response
|
|
5
5
|
|
6
6
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
7
7
|
|
8
|
-
|
8
|
+
from ..context import InterceptContext
|
9
|
+
|
10
|
+
class MockContext(InterceptContext):
|
9
11
|
def __init__(self, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
|
10
|
-
|
11
|
-
self.__intercept_settings = intercept_settings
|
12
|
+
super().__init__(flow, intercept_settings)
|
12
13
|
|
13
14
|
self.__start_time = time.time()
|
14
15
|
self.__end_time = None
|
@@ -18,14 +19,6 @@ class MockContext():
|
|
18
19
|
def end_time(self):
|
19
20
|
return self.__end_time
|
20
21
|
|
21
|
-
@property
|
22
|
-
def flow(self):
|
23
|
-
return self.__flow
|
24
|
-
|
25
|
-
@property
|
26
|
-
def intercept_settings(self):
|
27
|
-
return self.__intercept_settings
|
28
|
-
|
29
22
|
@property
|
30
23
|
def response(self):
|
31
24
|
return self.__response
|
@@ -2,16 +2,8 @@ from mitmproxy.http import HTTPFlow as MitmproxyHTTPFlow
|
|
2
2
|
|
3
3
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
4
4
|
|
5
|
-
|
6
|
-
def __init__(self, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
|
7
|
-
self.__flow = flow
|
8
|
-
self.__intercept_settings = intercept_settings
|
9
|
-
|
10
|
-
@property
|
11
|
-
def flow(self):
|
12
|
-
return self.__flow
|
13
|
-
|
14
|
-
@property
|
15
|
-
def intercept_settings(self):
|
16
|
-
return self.__intercept_settings
|
5
|
+
from ..context import InterceptContext
|
17
6
|
|
7
|
+
class RecordContext(InterceptContext):
|
8
|
+
def __init__(self, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
|
9
|
+
super().__init__(flow, intercept_settings)
|
@@ -26,7 +26,7 @@ def join_rewritten_request(flow: MitmproxyHTTPFlow, intercept_settings: Intercep
|
|
26
26
|
response = MitmproxyResponseFacade(flow.response)
|
27
27
|
rewrite_rules = intercept_settings.record_rewrite_rules
|
28
28
|
|
29
|
-
request.
|
29
|
+
request.with_parameter_rules(rewrite_rules).rewrite()
|
30
30
|
response.with_rewrite_rules(rewrite_rules).rewrite()
|
31
31
|
|
32
32
|
return join_request(request, response, intercept_settings)
|
@@ -5,10 +5,11 @@ from requests import Response
|
|
5
5
|
|
6
6
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
7
7
|
|
8
|
-
|
8
|
+
from ..context import InterceptContext
|
9
|
+
|
10
|
+
class ReplayContext(InterceptContext):
|
9
11
|
def __init__(self, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
|
10
|
-
|
11
|
-
self.__intercept_settings = intercept_settings
|
12
|
+
super().__init__(flow, intercept_settings)
|
12
13
|
|
13
14
|
self.__start_time = time.time()
|
14
15
|
self.__end_time = None
|
@@ -18,14 +19,6 @@ class ReplayContext():
|
|
18
19
|
def end_time(self):
|
19
20
|
return self.__end_time
|
20
21
|
|
21
|
-
@property
|
22
|
-
def flow(self):
|
23
|
-
return self.__flow
|
24
|
-
|
25
|
-
@property
|
26
|
-
def intercept_settings(self):
|
27
|
-
return self.__intercept_settings
|
28
|
-
|
29
22
|
@property
|
30
23
|
def response(self):
|
31
24
|
return self.__response
|
@@ -21,7 +21,7 @@ class ReplayRequestOptions(TypedDict):
|
|
21
21
|
alias_resolve_strategy: alias_resolve_strategy.AliasResolveStrategy
|
22
22
|
group_by: str
|
23
23
|
host: str
|
24
|
-
|
24
|
+
lifecycle_hooks_path: str
|
25
25
|
mode: Union[mode.MOCK, mode.RECORD, mode.TEST, None]
|
26
26
|
before_replay: Union[Callable[[ReplayContext], None], None]
|
27
27
|
after_replay: Union[Callable[[ReplayContext], Union[requests.Response, None]], None]
|
@@ -54,8 +54,8 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
|
|
54
54
|
if options.get('alias_resolve_strategy'):
|
55
55
|
headers[custom_headers.ALIAS_RESOLVE_STRATEGY] = options['alias_resolve_strategy']
|
56
56
|
|
57
|
-
if options.get('
|
58
|
-
|
57
|
+
if options.get('lifecycle_hooks_path'):
|
58
|
+
__handle_lifecycle_hooks_path(options['lifecycle_hooks_path'], headers)
|
59
59
|
|
60
60
|
if options.get('mode'):
|
61
61
|
__handle_mode_option(options['mode'], request, headers)
|
@@ -137,14 +137,14 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
|
|
137
137
|
|
138
138
|
return res
|
139
139
|
|
140
|
-
def
|
140
|
+
def __handle_lifecycle_hooks_path(script_path, headers):
|
141
141
|
if not script_path:
|
142
142
|
return
|
143
143
|
|
144
144
|
if not os.path.isabs(script_path):
|
145
145
|
script_path = os.path.join(os.path.abspath('.'), script_path)
|
146
146
|
|
147
|
-
headers[custom_headers.
|
147
|
+
headers[custom_headers.LIFECYCLE_HOOKS_PATH] = script_path
|
148
148
|
|
149
149
|
def __handle_mode_option(_mode, request: Request, headers):
|
150
150
|
headers[custom_headers.PROXY_MODE] = _mode
|
stoobly_agent/app/proxy/run.py
CHANGED
@@ -74,8 +74,8 @@ class TestContext(TestContextABC):
|
|
74
74
|
return self.__intercept_settings.lifecycle_hooks
|
75
75
|
|
76
76
|
@property
|
77
|
-
def
|
78
|
-
return self.__intercept_settings.
|
77
|
+
def lifecycle_hooks_path(self):
|
78
|
+
return self.__intercept_settings.lifecycle_hooks_path
|
79
79
|
|
80
80
|
@property
|
81
81
|
def end_time(self):
|
@@ -4,7 +4,7 @@ def matches(context: TestContext):
|
|
4
4
|
lifecycle_hooks = context.lifecycle_hooks
|
5
5
|
|
6
6
|
if not 'handle_test' in lifecycle_hooks:
|
7
|
-
return False, f"Expected function 'handle_test' to be defined in {context.
|
7
|
+
return False, f"Expected function 'handle_test' to be defined in {context.lifecycle_hooks_path}"
|
8
8
|
|
9
9
|
try:
|
10
10
|
status, log = lifecycle_hooks['handle_test'](context)
|
@@ -2,6 +2,7 @@ import pdb
|
|
2
2
|
|
3
3
|
from .parameter_rule import ParameterRule
|
4
4
|
from .types.proxy_settings import RewriteRule as IRewriteRule
|
5
|
+
from .url_rule import UrlRule
|
5
6
|
|
6
7
|
class RewriteRule:
|
7
8
|
|
@@ -14,6 +15,9 @@ class RewriteRule:
|
|
14
15
|
self.__raw_parameter_rules = self.__rewrite_rule.get('parameter_rules') or []
|
15
16
|
self.__parameter_rules = list(map(lambda rule: ParameterRule(rule), self.__raw_parameter_rules))
|
16
17
|
|
18
|
+
self.__raw_url_rules = self.__rewrite_rule.get('url_rules') or []
|
19
|
+
self.__url_rules = list(map(lambda rule: UrlRule(rule), self.__raw_url_rules))
|
20
|
+
|
17
21
|
@property
|
18
22
|
def methods(self):
|
19
23
|
return self.__methods
|
@@ -30,9 +34,18 @@ class RewriteRule:
|
|
30
34
|
def parameter_rules(self, v):
|
31
35
|
self.__parameter_rules = v
|
32
36
|
|
37
|
+
@property
|
38
|
+
def url_rules(self):
|
39
|
+
return self.__url_rules
|
40
|
+
|
41
|
+
@url_rules.setter
|
42
|
+
def url_rules(self, v):
|
43
|
+
self.__url_rules = v
|
44
|
+
|
33
45
|
def to_dict(self):
|
34
46
|
return {
|
35
47
|
'methods': self.__methods,
|
36
48
|
'pattern': self.__pattern,
|
37
49
|
'parameter_rules': list(map(lambda parameter: parameter.to_dict(), self.__parameter_rules)),
|
50
|
+
'url_rules': list(map(lambda url: url.to_dict(), self.__url_rules)),
|
38
51
|
}
|
@@ -23,11 +23,6 @@ class IgnoreRule(TypedDict):
|
|
23
23
|
method: str
|
24
24
|
pattern: str
|
25
25
|
|
26
|
-
class RedactRule(TypedDict):
|
27
|
-
redacts: List[Redact]
|
28
|
-
method: str
|
29
|
-
pattern: str
|
30
|
-
|
31
26
|
class RewriteRule(TypedDict):
|
32
27
|
rewrites: List[Rewrite]
|
33
28
|
method: str
|
@@ -97,4 +92,4 @@ Component = {
|
|
97
92
|
'QueryParam': 'Query Param',
|
98
93
|
}
|
99
94
|
IProjectModeSettings = Union[IProjectMockSettings, IProjectRecordSettings, IProjectTestSettings]
|
100
|
-
Rule = Union[IgnoreRule,
|
95
|
+
Rule = Union[IgnoreRule, RewriteRule]
|
@@ -15,6 +15,11 @@ class ParameterRule(TypedDict):
|
|
15
15
|
type: str
|
16
16
|
value: str
|
17
17
|
|
18
|
+
class UrlRule(TypedDict):
|
19
|
+
host: str
|
20
|
+
modes: List[Mode]
|
21
|
+
port: str
|
22
|
+
|
18
23
|
class DataRules(TypedDict):
|
19
24
|
mock_policy: MockPolicy
|
20
25
|
record_policy: RecordPolicy
|
@@ -30,6 +35,7 @@ class RewriteRule(TypedDict):
|
|
30
35
|
methods: List[Method]
|
31
36
|
pattern: str
|
32
37
|
parameter_rules: List[ParameterRule]
|
38
|
+
url_rule: List[UrlRule]
|
33
39
|
|
34
40
|
class FirewallRule(TypedDict):
|
35
41
|
action: List[FirewallAction]
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from .types.proxy_settings import UrlRule as IUrlRule
|
4
|
+
|
5
|
+
class UrlRule:
|
6
|
+
|
7
|
+
def __init__(self, url_rule: IUrlRule):
|
8
|
+
self.update(url_rule)
|
9
|
+
|
10
|
+
@property
|
11
|
+
def host(self):
|
12
|
+
return self.__host
|
13
|
+
|
14
|
+
@property
|
15
|
+
def modes(self):
|
16
|
+
return self.__modes
|
17
|
+
|
18
|
+
@property
|
19
|
+
def port(self):
|
20
|
+
return self.__port
|
21
|
+
|
22
|
+
def update(self, url_rule: IUrlRule):
|
23
|
+
self.__url_rule = url_rule
|
24
|
+
self.__host = self.__url_rule.get('host')
|
25
|
+
self.__modes = self.__url_rule.get('modes')
|
26
|
+
self.__port = self.__url_rule.get('port')
|
27
|
+
|
28
|
+
def to_dict(self):
|
29
|
+
return {
|
30
|
+
'host': self.__host,
|
31
|
+
'modes': self.__modes,
|
32
|
+
'port': self.__port,
|
33
|
+
}
|
stoobly_agent/cli.py
CHANGED
@@ -87,6 +87,7 @@ def init(**kwargs):
|
|
87
87
|
@click.option('--log-level', default=logger.INFO, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]), help='''
|
88
88
|
Log levels can be "debug", "info", "warning", or "error"
|
89
89
|
''')
|
90
|
+
@click.option('--lifecycle-hooks-path', help='Path to lifecycle hooks script.')
|
90
91
|
@click.option('--modify-headers', multiple=True, help='''
|
91
92
|
Header modify pattern of the form "[/flow-filter]/header-name/[@]header-value", where the separator can be any character. The @ allows to provide a file path that is used to read the header value string.
|
92
93
|
An empty header-value removes existing header-name headers. May be passed multiple times.
|
@@ -107,6 +108,9 @@ def run(**kwargs):
|
|
107
108
|
|
108
109
|
os.environ[env_vars.AGENT_PROXY_URL] = f"http://{kwargs['proxy_host']}:{kwargs['proxy_port']}"
|
109
110
|
|
111
|
+
if kwargs.get('lifecycle_hooks_path'):
|
112
|
+
os.environ[env_vars.AGENT_LIFECYCLE_HOOKS_PATH] = kwargs['lifecycle_hooks_path']
|
113
|
+
|
110
114
|
# Observe config for changes
|
111
115
|
Settings.instance().watch()
|
112
116
|
|
@@ -3,7 +3,7 @@ MOCK_POLICY = 'X-Mock-Policy'
|
|
3
3
|
MOCK_REQUEST_ID = 'X-Stoobly-Request-Id'
|
4
4
|
MOCK_REQUEST_ENDPOINT_ID = 'X-Stoobly-Request-Endpoint-Id'
|
5
5
|
DO_PROXY = 'X-Do-Proxy'
|
6
|
-
|
6
|
+
LIFECYCLE_HOOKS_PATH = 'X-Stoobly-Lifecycle-Hooks-Path'
|
7
7
|
PROJECT_KEY = 'X-Project-Key'
|
8
8
|
PROXY_MODE = 'X-Proxy-Mode'
|
9
9
|
RECORD_POLICY = 'X-Record-Policy'
|
@@ -5,6 +5,7 @@ AGENT_SELF_INTERCEPT_ENABLED = 'STOOBLY_AGENT_SELF_INTERCEPT_ENABLED'
|
|
5
5
|
AGENT_INCLUDE_PATTERNS = 'STOOBLY_AGENT_INCLUDE_PATTERNS'
|
6
6
|
AGENT_IS_HEADLESS = 'STOOBLY_AGENT_IS_HEADLESS'
|
7
7
|
AGENT_EXCLUDE_PATTERNS = 'STOOBLY_AGENT_EXCLUDE_PATTERNS'
|
8
|
+
AGENT_LIFECYCLE_HOOKS_PATH = 'STOOBLY_AGENT_LIFECYCLE_HOOKS_PATH'
|
8
9
|
AGENT_POLICY = 'STOOBLY_AGENT_POLICY'
|
9
10
|
AGENT_PROJECT_KEY = 'STOOBLY_AGENT_PROJECT_KEY'
|
10
11
|
AGENT_PROXY_URL = 'STOOBLY_AGENT_PROXY_URL'
|
@@ -5,6 +5,8 @@ AFTER_TEST = 'handle_after_test'
|
|
5
5
|
BEFORE_MOCK = 'handle_before_mock'
|
6
6
|
BEFORE_RECORD = 'handle_before_record'
|
7
7
|
BEFORE_REPLAY = 'handle_before_replay'
|
8
|
+
BEFORE_REQUEST = 'handle_before_request'
|
9
|
+
BEFORE_RESPONSE = 'handle_before_response'
|
8
10
|
BEFORE_TEST = 'handle_before_test'
|
9
11
|
ON_LENGTH_MATCH_ERROR = 'handle_length_match_error'
|
10
12
|
ON_PARAM_NAME_EXISTS_ERROR = 'handle_param_name_exists_error'
|
stoobly_agent/config/schema.yml
CHANGED
@@ -48,6 +48,12 @@ rewrite_rule:
|
|
48
48
|
methods: list(include('method'))
|
49
49
|
pattern: str()
|
50
50
|
parameter_rules: list(include('parameter_rule'))
|
51
|
+
url_rules: list(include('url_rule'))
|
52
|
+
port: str()
|
53
|
+
url_rule:
|
54
|
+
host: str()
|
55
|
+
modes: list(include('mode'), min=1, max=4)
|
56
|
+
port: str()
|
51
57
|
firewall_rule:
|
52
58
|
action: include('firewall_action')
|
53
59
|
methods: list(include('method'))
|
@@ -57,4 +63,4 @@ match_rule:
|
|
57
63
|
components: include('component')
|
58
64
|
methods: list(include('method'))
|
59
65
|
modes: list(include('mode'), min=1, max=4)
|
60
|
-
pattern: str()
|
66
|
+
pattern: str()
|
stoobly_agent/lib/orm/request.py
CHANGED
@@ -155,11 +155,15 @@ def handle_deleting(request):
|
|
155
155
|
replayed_response.delete()
|
156
156
|
|
157
157
|
def handle_deleted(request):
|
158
|
-
|
158
|
+
request_before = request.get_original()
|
159
|
+
|
160
|
+
if request_before.get('scenario_id'):
|
161
|
+
# request.scenario returns a cached version of the scenario
|
162
|
+
scenario = Scenario.find(request_before['scenario_id'])
|
159
163
|
|
160
|
-
|
161
|
-
|
162
|
-
|
164
|
+
if scenario:
|
165
|
+
scenario.requests_count -= 1
|
166
|
+
scenario.save()
|
163
167
|
|
164
168
|
Request.saved(handle_saved)
|
165
169
|
Request.saving(handle_saving)
|