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.
Files changed (59) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/configs_controller.py +5 -2
  3. stoobly_agent/app/cli/config_cli.py +64 -14
  4. stoobly_agent/app/cli/endpoint_cli.py +1 -1
  5. stoobly_agent/app/cli/helpers/endpoint_facade.py +1 -1
  6. stoobly_agent/app/cli/helpers/handle_config_update_service.py +44 -5
  7. stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +19 -3
  8. stoobly_agent/app/cli/helpers/replay_facade.py +2 -2
  9. stoobly_agent/app/cli/intercept_cli.py +14 -5
  10. stoobly_agent/app/cli/request_cli.py +2 -2
  11. stoobly_agent/app/cli/scenario_cli.py +2 -2
  12. stoobly_agent/app/cli/types/endpoint.py +1 -1
  13. stoobly_agent/app/models/adapters/raw_http_request_adapter.py +1 -1
  14. stoobly_agent/app/models/adapters/raw_http_response_adapter.py +1 -1
  15. stoobly_agent/app/proxy/context.py +18 -0
  16. stoobly_agent/app/proxy/handle_mock_service.py +1 -1
  17. stoobly_agent/app/proxy/handle_replay_service.py +2 -1
  18. stoobly_agent/app/proxy/intercept_handler.py +14 -3
  19. stoobly_agent/app/proxy/intercept_settings.py +28 -21
  20. stoobly_agent/app/proxy/mitmproxy/request_facade.py +41 -35
  21. stoobly_agent/app/proxy/mitmproxy/response_facade.py +0 -11
  22. stoobly_agent/app/proxy/mock/context.py +4 -11
  23. stoobly_agent/app/proxy/record/context.py +4 -12
  24. stoobly_agent/app/proxy/record/join_request_service.py +1 -1
  25. stoobly_agent/app/proxy/replay/context.py +4 -11
  26. stoobly_agent/app/proxy/replay/replay_request_service.py +5 -5
  27. stoobly_agent/app/proxy/replay/rewrite_params_service.py +1 -1
  28. stoobly_agent/app/proxy/run.py +3 -0
  29. stoobly_agent/app/proxy/test/context.py +2 -2
  30. stoobly_agent/app/proxy/test/context_abc.py +1 -1
  31. stoobly_agent/app/proxy/test/matchers/custom.py +1 -1
  32. stoobly_agent/app/settings/rewrite_rule.py +13 -0
  33. stoobly_agent/app/settings/types/__init__.py +1 -6
  34. stoobly_agent/app/settings/types/proxy_settings.py +6 -0
  35. stoobly_agent/app/settings/url_rule.py +33 -0
  36. stoobly_agent/cli.py +4 -0
  37. stoobly_agent/config/constants/custom_headers.py +1 -1
  38. stoobly_agent/config/constants/env_vars.py +1 -0
  39. stoobly_agent/config/constants/lifecycle_hooks.py +2 -0
  40. stoobly_agent/config/schema.yml +7 -1
  41. stoobly_agent/lib/orm/request.py +8 -4
  42. stoobly_agent/public/18-es2015.46d337c47cb41abec8ad.js +1 -0
  43. stoobly_agent/public/18-es5.46d337c47cb41abec8ad.js +1 -0
  44. stoobly_agent/public/dashboard.agent-alpha-0.30.0.tar.gz +0 -0
  45. stoobly_agent/public/index.html +1 -1
  46. stoobly_agent/public/runtime-es2015.3a15c6ce90c8f8cce796.js +1 -0
  47. stoobly_agent/public/runtime-es5.3a15c6ce90c8f8cce796.js +1 -0
  48. stoobly_agent-0.30.0.dist-info/METADATA +26 -0
  49. {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/RECORD +53 -51
  50. {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/WHEEL +1 -1
  51. stoobly_agent/public/18-es2015.750ef954bde422debcb6.js +0 -1
  52. stoobly_agent/public/18-es5.750ef954bde422debcb6.js +0 -1
  53. stoobly_agent/public/dashboard.agent-alpha-0.29.0.tar.gz +0 -0
  54. stoobly_agent/public/runtime-es2015.b392473c492ce4cf1cb5.js +0 -1
  55. stoobly_agent/public/runtime-es5.b392473c492ce4cf1cb5.js +0 -1
  56. stoobly_agent-0.29.1.dist-info/METADATA +0 -26
  57. {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/LICENSE +0 -0
  58. {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/entry_points.txt +0 -0
  59. {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, ParameterRule
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.__redact_rules: List[ParameterRule] = []
33
- self.__rewrite_rules: List[ParameterRule] = []
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 redact_rules(self) -> List[ParameterRule]:
103
- return self.__redact_rules
102
+ def url_rules(self) -> List[ParameterRule]:
103
+ return self.__url_rules
104
104
 
105
105
  @property
106
- def rewrite_rules(self) -> List[ParameterRule]:
107
- return self.__rewrite_rules
106
+ def parameter_rules(self) -> List[ParameterRule]:
107
+ return self.__parameter_rules
108
108
 
109
- def with_rewrite_rules(self, rules: List[RewriteRule]):
109
+ def with_parameter_rules(self, rules: List[RewriteRule]):
110
110
  if type(rules) == list:
111
- self.__rewrite_rules = self.select_parameter_rules(rules)
111
+ self.__parameter_rules = self.select_parameter_rules(rules)
112
112
  return self
113
113
 
114
- def with_redact_rules(self, rules: List[RewriteRule]):
114
+ def with_url_rules(self, rules: List[RewriteRule]):
115
115
  if type(rules) == list:
116
- self.__redact_rules = self.select_parameter_rules(rules)
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.__rewrite_rules
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
- # Find all the rules that match request url and method
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 __is_parameter_rule_selected(self, rewrite_rule: RewriteRule):
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
- class MockContext():
8
+ from ..context import InterceptContext
9
+
10
+ class MockContext(InterceptContext):
9
11
  def __init__(self, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
10
- self.__flow = flow
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
- class RecordContext():
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.with_rewrite_rules(rewrite_rules).rewrite()
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
- class ReplayContext():
8
+ from ..context import InterceptContext
9
+
10
+ class ReplayContext(InterceptContext):
9
11
  def __init__(self, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
10
- self.__flow = flow
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
- lifecycle_hooks_script_path: str
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('lifecycle_hooks_script_path'):
58
- __handle_lifecycle_hooks_script_path(options['lifecycle_hooks_script_path'], headers)
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 __handle_lifecycle_hooks_script_path(script_path, headers):
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.LIFECYCLE_HOOKS_SCRIPT_PATH] = script_path
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
@@ -1,6 +1,6 @@
1
1
  import pdb
2
2
 
3
- from typing import Callable, Dict, List, TypedDict, Union
3
+ from typing import Dict, List, Union
4
4
 
5
5
  from stoobly_agent.app.proxy.replay.alias_context import AliasContext
6
6
  from stoobly_agent.app.proxy.replay.alias_resolver import AliasResolver
@@ -113,6 +113,9 @@ def __filter_options(options):
113
113
  if 'headless' in options:
114
114
  del options['headless']
115
115
 
116
+ if 'lifecycle_hooks_path' in options:
117
+ del options['lifecycle_hooks_path']
118
+
116
119
  if 'ui_host' in options:
117
120
  del options['ui_host']
118
121
 
@@ -74,8 +74,8 @@ class TestContext(TestContextABC):
74
74
  return self.__intercept_settings.lifecycle_hooks
75
75
 
76
76
  @property
77
- def lifecycle_hooks_script_path(self):
78
- return self.__intercept_settings.lifecycle_hooks_script_path
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):
@@ -35,7 +35,7 @@ class TestContextABC(abc.ABC):
35
35
 
36
36
  @property
37
37
  @abc.abstractmethod
38
- def lifecycle_hooks_script_path(self):
38
+ def lifecycle_hooks_path(self):
39
39
  pass
40
40
 
41
41
  @property
@@ -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.lifecycle_hooks_script_path}"
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, RedactRule, RewriteRule]
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
- LIFECYCLE_HOOKS_SCRIPT_PATH = 'X-Stoobly-Lifecycle-Hooks-Script-Path'
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'
@@ -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()
@@ -155,11 +155,15 @@ def handle_deleting(request):
155
155
  replayed_response.delete()
156
156
 
157
157
  def handle_deleted(request):
158
- scenario = request.scenario
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
- if scenario:
161
- scenario.requests_count -= 1
162
- scenario.save()
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)