stoobly-agent 1.9.1__py3-none-any.whl → 1.9.3__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/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +0 -3
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init +5 -1
- stoobly_agent/app/cli/scaffold_cli.py +66 -6
- stoobly_agent/app/models/adapters/joined_request_adapter.py +38 -6
- stoobly_agent/app/models/factories/resource/local_db/helpers/log.py +39 -24
- stoobly_agent/app/models/helpers/apply.py +10 -9
- stoobly_agent/app/proxy/handle_mock_service.py +15 -9
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +30 -24
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/lib/api/keys/uuid_key.py +4 -0
- stoobly_agent/test/app/cli/request/request_snapshot_test.py +54 -4
- stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py +122 -2
- stoobly_agent/test/app/cli/snapshot/snapshot_prune_test.py +3 -3
- stoobly_agent/test/app/models/adapters/joined_rquest_adapter_test.py +38 -0
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- {stoobly_agent-1.9.1.dist-info → stoobly_agent-1.9.3.dist-info}/METADATA +1 -1
- {stoobly_agent-1.9.1.dist-info → stoobly_agent-1.9.3.dist-info}/RECORD +21 -20
- {stoobly_agent-1.9.1.dist-info → stoobly_agent-1.9.3.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.9.1.dist-info → stoobly_agent-1.9.3.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.9.1.dist-info → stoobly_agent-1.9.3.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.9.
|
2
|
+
VERSION = '1.9.3'
|
@@ -2,4 +2,8 @@
|
|
2
2
|
|
3
3
|
# Add custom initialization here
|
4
4
|
|
5
|
-
app_path=$1 # Path to application source files
|
5
|
+
app_path=$1 # Path to application source files
|
6
|
+
|
7
|
+
# For example, the below commands copies files inside a dist folder into the public folder.
|
8
|
+
# The public folder is used when no recorded mocks are found.
|
9
|
+
#cp -r $app_path/dist/. public
|
@@ -6,6 +6,7 @@ import sys
|
|
6
6
|
|
7
7
|
from io import TextIOWrapper
|
8
8
|
from typing import List
|
9
|
+
from urllib.parse import urlparse
|
9
10
|
|
10
11
|
from stoobly_agent.app.cli.helpers.certificate_authority import CertificateAuthority
|
11
12
|
from stoobly_agent.app.cli.helpers.shell import exec_stream
|
@@ -148,10 +149,10 @@ def create(**kwargs):
|
|
148
149
|
sys.exit(1)
|
149
150
|
|
150
151
|
if kwargs.get('hostname'):
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
152
|
+
__validate_hostname(kwargs.get('hostname'))
|
153
|
+
|
154
|
+
if kwargs.get("proxy_mode"):
|
155
|
+
__validate_proxy_mode(kwargs.get("proxy_mode"))
|
155
156
|
|
156
157
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
157
158
|
|
@@ -216,6 +217,13 @@ def delete(**kwargs):
|
|
216
217
|
@click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
|
217
218
|
@click.option('--priority', default=5, type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
|
218
219
|
@click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
|
220
|
+
@click.option('--name', type=click.STRING, help='New name of the service to update to.')
|
221
|
+
@click.option('--proxy-mode', help='''
|
222
|
+
Proxy mode can be "regular", "transparent", "socks5",
|
223
|
+
"reverse:SPEC", or "upstream:SPEC". For reverse and
|
224
|
+
upstream proxy modes, SPEC is host specification in
|
225
|
+
the form of "http[s]://host[:port]".
|
226
|
+
''')
|
219
227
|
@click.argument('service_name')
|
220
228
|
def update(**kwargs):
|
221
229
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
@@ -228,7 +236,20 @@ def update(**kwargs):
|
|
228
236
|
service_config = ServiceConfig(service.dir_path)
|
229
237
|
|
230
238
|
if kwargs['hostname']:
|
231
|
-
|
239
|
+
__validate_hostname(kwargs['hostname'])
|
240
|
+
|
241
|
+
old_hostname = service_config.hostname
|
242
|
+
|
243
|
+
if old_hostname != kwargs['hostname']:
|
244
|
+
service_config.hostname = kwargs['hostname']
|
245
|
+
|
246
|
+
# If this is the default proxy_mode and the origin matches the original hostname, assume it is safe to update with the new hostname
|
247
|
+
if service_config.proxy_mode.startswith("reverse:"):
|
248
|
+
old_origin = service_config.proxy_mode.split("reverse:")[1]
|
249
|
+
parsed_origin_url = urlparse(old_origin)
|
250
|
+
|
251
|
+
if old_hostname == parsed_origin_url.hostname:
|
252
|
+
service_config.proxy_mode = service_config.proxy_mode.replace(old_hostname, service_config.hostname)
|
232
253
|
|
233
254
|
if kwargs['priority']:
|
234
255
|
service_config.priority = kwargs['priority']
|
@@ -239,6 +260,10 @@ def update(**kwargs):
|
|
239
260
|
if kwargs['scheme']:
|
240
261
|
service_config.scheme = kwargs['scheme']
|
241
262
|
|
263
|
+
if kwargs['proxy_mode']:
|
264
|
+
__validate_proxy_mode(kwargs['proxy_mode'])
|
265
|
+
service_config.proxy_mode = kwargs['proxy_mode']
|
266
|
+
|
242
267
|
service_config.write()
|
243
268
|
|
244
269
|
@workflow.command(
|
@@ -759,7 +784,42 @@ def __validate_app_dir(app_dir_path):
|
|
759
784
|
|
760
785
|
def __validate_service_dir(service_dir_path):
|
761
786
|
if not os.path.exists(service_dir_path):
|
762
|
-
print(f"Error: {service_dir_path} does not exist, please scaffold this service", file=sys.stderr)
|
787
|
+
print(f"Error: '{service_dir_path}' does not exist, please scaffold this service", file=sys.stderr)
|
788
|
+
sys.exit(1)
|
789
|
+
|
790
|
+
def __validate_proxy_mode(proxy_mode: str) -> None:
|
791
|
+
valid_exact_matches = {
|
792
|
+
"regular": None,
|
793
|
+
"transparent": None,
|
794
|
+
"socks5": None,
|
795
|
+
}
|
796
|
+
|
797
|
+
valid_prefixes = {
|
798
|
+
"reverse": None,
|
799
|
+
"upstream": None
|
800
|
+
}
|
801
|
+
|
802
|
+
if proxy_mode in valid_exact_matches:
|
803
|
+
return
|
804
|
+
|
805
|
+
split_str = proxy_mode.split(":", 1)
|
806
|
+
if len(split_str) != 2:
|
807
|
+
print(f"Error: {proxy_mode} is invalid.", file=sys.stderr)
|
808
|
+
sys.exit(1)
|
809
|
+
|
810
|
+
prefix = split_str[0]
|
811
|
+
spec = split_str[1]
|
812
|
+
|
813
|
+
if prefix not in valid_prefixes:
|
814
|
+
print(f"Error: {proxy_mode} is invalid.", file=sys.stderr)
|
815
|
+
sys.exit(1)
|
816
|
+
|
817
|
+
# TODO: validate SPEC
|
818
|
+
|
819
|
+
def __validate_hostname(hostname: str) -> None:
|
820
|
+
hostname_regex = re.compile(r'^[a-zA-Z0-9.-]+$')
|
821
|
+
if not re.search(hostname_regex, hostname):
|
822
|
+
print(f"Error: {hostname} is invalid.", file=sys.stderr)
|
763
823
|
sys.exit(1)
|
764
824
|
|
765
825
|
def __workflow_create(app, **kwargs):
|
@@ -17,9 +17,7 @@ class JoinedRequestAdapter():
|
|
17
17
|
if isinstance(payloads_delimitter, str):
|
18
18
|
payloads_delimitter = payloads_delimitter.encode()
|
19
19
|
|
20
|
-
self.__split_joined_request_string =
|
21
|
-
if len(self.__split_joined_request_string) != 2:
|
22
|
-
self.__split_joined_request_string = joined_request_string.split(payloads_delimitter.replace(b"\n", b"\r\n"))
|
20
|
+
self.__split_joined_request_string = self.raw_request_split(joined_request_string, payloads_delimitter)
|
23
21
|
|
24
22
|
if len(self.__split_joined_request_string) != 2:
|
25
23
|
raise ValueError(f"Could not split by {payloads_delimitter}")
|
@@ -47,7 +45,7 @@ class JoinedRequestAdapter():
|
|
47
45
|
request_string = RequestString(None)
|
48
46
|
|
49
47
|
delimitter = RequestStringCLRF
|
50
|
-
request_string_toks = self.__split_joined_request_string[0]
|
48
|
+
request_string_toks = self.repaired_string_toks(self.__split_joined_request_string[0], delimitter)
|
51
49
|
request_string.set(self.raw_request_string or delimitter.join(request_string_toks[1:]))
|
52
50
|
request_string.control = request_string_toks[0]
|
53
51
|
|
@@ -57,7 +55,7 @@ class JoinedRequestAdapter():
|
|
57
55
|
response_string = ResponseString(None, None)
|
58
56
|
|
59
57
|
delimitter = ResponseStringCLRF
|
60
|
-
response_string_toks = self.__split_joined_request_string[1]
|
58
|
+
response_string_toks = self.repaired_string_toks(self.__split_joined_request_string[1], delimitter)
|
61
59
|
response_string.set(self.raw_response_string or delimitter.join(response_string_toks[1:]))
|
62
60
|
response_string.control = response_string_toks[0]
|
63
61
|
|
@@ -68,4 +66,38 @@ class JoinedRequestAdapter():
|
|
68
66
|
|
69
67
|
joined_request.request_string = self.build_request_string()
|
70
68
|
joined_request.response_string = self.build_response_string()
|
71
|
-
return joined_request
|
69
|
+
return joined_request
|
70
|
+
|
71
|
+
# If all CRLF characters have been replaced with LF e.g. visual studio code
|
72
|
+
# Then try to repair the raw string, see https://github.com/Stoobly/stoobly-agent/issues/415
|
73
|
+
@staticmethod
|
74
|
+
def repaired_string_toks(raw_string: bytes, delimitter: bytes):
|
75
|
+
toks = raw_string.split(delimitter)
|
76
|
+
|
77
|
+
if len(toks) == 1:
|
78
|
+
lf = b"\n"
|
79
|
+
toks = raw_string.split(lf)
|
80
|
+
|
81
|
+
# See for request: https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
|
82
|
+
# See for response: https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
|
83
|
+
i = 0
|
84
|
+
for line in toks:
|
85
|
+
i += 1
|
86
|
+
|
87
|
+
# On two lf characters, then the following lines are the body
|
88
|
+
if line == b'':
|
89
|
+
break
|
90
|
+
|
91
|
+
toks = toks[:i] + [lf.join(toks[i:])]
|
92
|
+
|
93
|
+
if len(toks) == 1:
|
94
|
+
raise ValueError(f"Could not split request by {delimitter}")
|
95
|
+
|
96
|
+
return toks
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def raw_request_split(raw_string: bytes, payloads_delimitter = REQUEST_DELIMITTER):
|
100
|
+
toks = raw_string.split(payloads_delimitter)
|
101
|
+
if len(toks) != 2:
|
102
|
+
toks = raw_string.split(payloads_delimitter.replace(b"\n", b"\r\n"))
|
103
|
+
return toks
|
@@ -70,22 +70,7 @@ class Log():
|
|
70
70
|
|
71
71
|
@property
|
72
72
|
def scenario_inverted_index(self):
|
73
|
-
|
74
|
-
|
75
|
-
def handle_snapshot(snapshot: RequestSnapshot):
|
76
|
-
request_uuid = snapshot.uuid
|
77
|
-
if not request_uuid in index:
|
78
|
-
index[request_uuid] = []
|
79
|
-
|
80
|
-
index[request_uuid].append(event.resource_uuid)
|
81
|
-
|
82
|
-
for event in self.target_events:
|
83
|
-
if not event.is_scenario():
|
84
|
-
continue
|
85
|
-
|
86
|
-
event.snapshot().iter_request_snapshots(handle_snapshot)
|
87
|
-
|
88
|
-
return index
|
73
|
+
return self.build_scenario_inverted_index(self.target_events)
|
89
74
|
|
90
75
|
@property
|
91
76
|
def unprocessed_events(self) -> List[LogEvent]:
|
@@ -153,6 +138,22 @@ class Log():
|
|
153
138
|
|
154
139
|
def build_log_events(self, raw_events) -> List[LogEvent]:
|
155
140
|
return list(map(lambda raw_event: LogEvent(raw_event), raw_events))
|
141
|
+
|
142
|
+
def build_scenario_inverted_index(self, events: List[LogEvent], index = {}):
|
143
|
+
def handle_snapshot(snapshot: RequestSnapshot):
|
144
|
+
request_uuid = snapshot.uuid
|
145
|
+
if not request_uuid in index:
|
146
|
+
index[request_uuid] = []
|
147
|
+
|
148
|
+
index[request_uuid].append(event.resource_uuid)
|
149
|
+
|
150
|
+
for event in events:
|
151
|
+
if not event.is_scenario():
|
152
|
+
continue
|
153
|
+
|
154
|
+
event.snapshot().iter_request_snapshots(handle_snapshot)
|
155
|
+
|
156
|
+
return index
|
156
157
|
|
157
158
|
def next_version(self, last_processed_uuid: str = None):
|
158
159
|
uuids = self.uuids()
|
@@ -169,7 +170,7 @@ class Log():
|
|
169
170
|
def collapse(self, events: List[LogEvent]) -> List[LogEvent]:
|
170
171
|
events_count = {}
|
171
172
|
|
172
|
-
# More recent events take precedence over earlier ones,
|
173
|
+
# More recent events take precedence over earlier ones, only the most recent event
|
173
174
|
for event in events:
|
174
175
|
event_key = event.key
|
175
176
|
|
@@ -274,10 +275,10 @@ class Log():
|
|
274
275
|
|
275
276
|
def remove_dangling_events(self, processed_events: List[LogEvent], unprocessed_events: List[LogEvent]):
|
276
277
|
'''
|
277
|
-
Remove DELETE events
|
278
|
+
Remove DELETE events unless the last processed event was a PUT
|
278
279
|
'''
|
279
280
|
|
280
|
-
# Build an index
|
281
|
+
# Build an index to keep track of the last action that occurred for a resource
|
281
282
|
index = {}
|
282
283
|
for event in processed_events:
|
283
284
|
if event.action == PUT_ACTION:
|
@@ -286,13 +287,27 @@ class Log():
|
|
286
287
|
if event.resource_uuid in index:
|
287
288
|
del index[event.resource_uuid]
|
288
289
|
|
289
|
-
scenario_inverted_index = self.
|
290
|
-
|
290
|
+
scenario_inverted_index = self.build_scenario_inverted_index(processed_events)
|
291
|
+
|
291
292
|
def keep(e: LogEvent):
|
292
|
-
if
|
293
|
+
# Keep the event if it's a PUT, it may have been updated
|
294
|
+
if e.action == PUT_ACTION:
|
293
295
|
return True
|
294
|
-
|
295
|
-
|
296
|
+
|
297
|
+
if e.action == DELETE_ACTION:
|
298
|
+
if e.is_request():
|
299
|
+
referenced = e.resource_uuid in scenario_inverted_index
|
300
|
+
# Keep the DELETE event if the requests exists in a scenario
|
301
|
+
# or if it doesn't exist in a scenario, there was a previous delete event
|
302
|
+
return referenced or (e.resource_uuid in index and not referenced)
|
303
|
+
elif e.is_scenario():
|
304
|
+
# Update scenario_inverted_index with unprocessed event
|
305
|
+
self.build_scenario_inverted_index([e], scenario_inverted_index)
|
306
|
+
|
307
|
+
# Keep DELETE scenario event only if previous event for the resource was PUT
|
308
|
+
return e.resource_uuid in index
|
309
|
+
else:
|
310
|
+
return True
|
296
311
|
|
297
312
|
return list(
|
298
313
|
filter(
|
@@ -2,10 +2,10 @@ import pdb
|
|
2
2
|
|
3
3
|
from typing import List
|
4
4
|
|
5
|
+
from stoobly_agent.app.models.adapters.joined_request_adapter import JoinedRequestAdapter
|
5
6
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
|
6
7
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
|
7
8
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.scenario_snapshot import ScenarioSnapshot
|
8
|
-
from stoobly_agent.app.proxy.record import REQUEST_STRING_CLRF
|
9
9
|
from stoobly_agent.app.settings import Settings
|
10
10
|
from stoobly_agent.lib.logger import bcolors
|
11
11
|
|
@@ -165,7 +165,12 @@ class Apply():
|
|
165
165
|
self.__logger(f"{bcolors.WARNING}Skipping Request{bcolors.ENDC} {error}")
|
166
166
|
return error, 301
|
167
167
|
|
168
|
-
|
168
|
+
res, status = self.__put_request(uuid, raw_request)
|
169
|
+
|
170
|
+
if status != 200:
|
171
|
+
return f"{res} {snapshot.path}", status
|
172
|
+
|
173
|
+
return res, status
|
169
174
|
|
170
175
|
def __apply_delete_scenario(self, uuid: str):
|
171
176
|
res, status = self.scenario_model.destroy(uuid, force=self.__force)
|
@@ -227,8 +232,8 @@ class Apply():
|
|
227
232
|
|
228
233
|
if not raw_request:
|
229
234
|
return f"{request_snapshot.path} is missing", 400
|
230
|
-
|
231
|
-
toks =
|
235
|
+
|
236
|
+
toks = JoinedRequestAdapter.raw_request_split(raw_request)
|
232
237
|
if len(toks) != 2:
|
233
238
|
return f"{request_snapshot.path} contains an invalid request", 400
|
234
239
|
|
@@ -236,7 +241,7 @@ class Apply():
|
|
236
241
|
res, status = self.__put_request(uuid, raw_request, scenario_id=scenario['id'])
|
237
242
|
|
238
243
|
if status != 200:
|
239
|
-
return res, status
|
244
|
+
return f"{res} {request_snapshot.path}", status
|
240
245
|
|
241
246
|
snapshot_requests[uuid] = res
|
242
247
|
|
@@ -274,8 +279,6 @@ class Apply():
|
|
274
279
|
|
275
280
|
if self.__logger and status == 200:
|
276
281
|
self.__logger(f"{bcolors.OKGREEN}Created Request{bcolors.ENDC} {res['list'][0]['url']}")
|
277
|
-
else:
|
278
|
-
self.__logger(f"{bcolors.FAIL}{status}{bcolors.ENDC} {res}")
|
279
282
|
elif status == 200:
|
280
283
|
params = {
|
281
284
|
'is_deleted': False,
|
@@ -287,7 +290,5 @@ class Apply():
|
|
287
290
|
if self.__logger:
|
288
291
|
if status == 200:
|
289
292
|
self.__logger(f"{bcolors.OKCYAN}Updated Request{bcolors.ENDC} {res['url']}")
|
290
|
-
else:
|
291
|
-
self.__logger(f"{bcolors.FAIL}{status}{bcolors.ENDC} {res}")
|
292
293
|
|
293
294
|
return res, status
|
@@ -147,15 +147,8 @@ def handle_request_mock(context: MockContext):
|
|
147
147
|
# 2. AFTER_MOCK gets triggered (if mock found)
|
148
148
|
#
|
149
149
|
def handle_response_mock(context: MockContext):
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
if request_key:
|
154
|
-
request = context.flow.request
|
155
|
-
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}Mocked{bcolors.ENDC} {request.url} -> {request_key}")
|
156
|
-
|
157
|
-
__rewrite_response(context)
|
158
|
-
__mock_hook(lifecycle_hooks.AFTER_MOCK, context)
|
150
|
+
__rewrite_response(context)
|
151
|
+
__mock_hook(lifecycle_hooks.AFTER_MOCK, context)
|
159
152
|
|
160
153
|
def __handle_mock_failure(context: MockContext) -> None:
|
161
154
|
flow = context.flow
|
@@ -186,6 +179,19 @@ def __handle_found_policy(context: MockContext) -> None:
|
|
186
179
|
reverse_proxy(req, upstream_url, {})
|
187
180
|
|
188
181
|
def __handle_mock_success(context: MockContext) -> None:
|
182
|
+
response = context.response
|
183
|
+
|
184
|
+
if response:
|
185
|
+
request = context.flow.request
|
186
|
+
|
187
|
+
request_key = response.headers.get(custom_headers.MOCK_REQUEST_KEY)
|
188
|
+
if request_key:
|
189
|
+
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}Mocked{bcolors.ENDC} {request.url} -> {request_key}")
|
190
|
+
|
191
|
+
fixture_path = response.headers.get(custom_headers.MOCK_FIXTURE_PATH)
|
192
|
+
if fixture_path:
|
193
|
+
Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}Mocked{bcolors.ENDC} {request.url} -> {fixture_path}")
|
194
|
+
|
189
195
|
if os.environ.get(env_vars.AGENT_SIMULATE_LATENCY):
|
190
196
|
response = context.response
|
191
197
|
start_time = context.start_time
|
@@ -10,6 +10,7 @@ from requests.structures import CaseInsensitiveDict
|
|
10
10
|
from typing import Union
|
11
11
|
|
12
12
|
from stoobly_agent.lib.logger import bcolors, Logger
|
13
|
+
from stoobly_agent.config.constants.custom_headers import MOCK_FIXTURE_PATH
|
13
14
|
|
14
15
|
from .types import Fixtures
|
15
16
|
|
@@ -20,45 +21,50 @@ class Options():
|
|
20
21
|
response_fixtures: Fixtures
|
21
22
|
|
22
23
|
def eval_fixtures(request: MitmproxyRequest, **options: Options) -> Union[Response, None]:
|
23
|
-
fixture_path =
|
24
|
+
fixture_path = request.headers.get(MOCK_FIXTURE_PATH)
|
24
25
|
headers = CaseInsensitiveDict()
|
25
26
|
status_code = 200
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
if not fixture:
|
31
|
-
public_directory_path = options.get('public_directory_path')
|
32
|
-
|
33
|
-
if not public_directory_path:
|
28
|
+
if fixture_path:
|
29
|
+
if not os.path.exists(fixture_path):
|
34
30
|
return
|
31
|
+
else:
|
32
|
+
response_fixtures = options.get('response_fixtures')
|
33
|
+
fixture: dict = __eval_response_fixtures(request, response_fixtures)
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
if request.headers.get('accept'):
|
39
|
-
fixture_path = __guess_file_path(_fixture_path, request.headers['accept'])
|
35
|
+
if not fixture:
|
36
|
+
public_directory_path = options.get('public_directory_path')
|
40
37
|
|
41
|
-
|
42
|
-
|
38
|
+
if not public_directory_path:
|
39
|
+
return
|
43
40
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
41
|
+
request_path = 'index' if request.path == '/' else request.path
|
42
|
+
_fixture_path = os.path.join(public_directory_path, request_path.lstrip('/'))
|
43
|
+
if request.headers.get('accept'):
|
44
|
+
fixture_path = __guess_file_path(_fixture_path, request.headers['accept'])
|
45
|
+
|
46
|
+
if not fixture_path:
|
47
|
+
fixture_path = _fixture_path
|
50
48
|
|
51
|
-
|
52
|
-
|
49
|
+
if not os.path.isfile(fixture_path):
|
50
|
+
return
|
51
|
+
else:
|
52
|
+
fixture_path = fixture.get('path')
|
53
|
+
if not fixture_path or not os.path.isfile(fixture_path):
|
54
|
+
return
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
+
_headers = fixture.get('headers')
|
57
|
+
headers = CaseInsensitiveDict(_headers if isinstance(_headers, dict) else {})
|
56
58
|
|
59
|
+
if fixture.get('status_code'):
|
60
|
+
status_code = fixture.get('status_code')
|
61
|
+
|
57
62
|
with open(fixture_path, 'rb') as fp:
|
58
63
|
response = Response()
|
59
64
|
|
60
65
|
response.status_code = status_code
|
61
66
|
response.raw = BytesIO(fp.read())
|
67
|
+
headers[MOCK_FIXTURE_PATH] = fixture_path
|
62
68
|
response.headers = headers
|
63
69
|
|
64
70
|
if not response.headers.get('content-type'):
|
@@ -2,6 +2,7 @@ ALIAS_RESOLVE_STRATEGY = 'X-Stoobly-Alias-Resolve-Strategy'
|
|
2
2
|
CONTENT_TYPE = 'X-Stoobly-Content-Type'
|
3
3
|
CONTENT_TYPE_TEST_RESULTS = 'test/results'
|
4
4
|
REMOTE_PROJECT_KEY = 'X-Stoobly-Endpoints-Project-Id'
|
5
|
+
MOCK_FIXTURE_PATH = 'X-Stoobly-Fixture-Path'
|
5
6
|
MOCK_POLICY = 'X-Stoobly-Mock-Policy'
|
6
7
|
MOCK_REQUEST_ID = 'X-Stoobly-Request-Id'
|
7
8
|
MOCK_REQUEST_ENDPOINT_ID = 'X-Stoobly-Request-Endpoint-Id'
|
@@ -1,16 +1,18 @@
|
|
1
1
|
import pdb
|
2
2
|
import pytest
|
3
|
+
import time
|
3
4
|
|
4
5
|
from click.testing import CliRunner
|
5
|
-
|
6
|
-
from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, reset
|
6
|
+
from typing import List
|
7
7
|
|
8
8
|
from stoobly_agent.app.models.adapters.orm import JoinedRequestStringAdapter
|
9
9
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
|
10
|
-
from stoobly_agent.app.models.factories.resource.local_db.helpers.log_event import DELETE_ACTION
|
10
|
+
from stoobly_agent.app.models.factories.resource.local_db.helpers.log_event import DELETE_ACTION, PUT_ACTION
|
11
11
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
|
12
|
-
from stoobly_agent.cli import record, request
|
12
|
+
from stoobly_agent.cli import record, request, scenario, snapshot
|
13
13
|
from stoobly_agent.lib.orm.request import Request
|
14
|
+
from stoobly_agent.lib.orm.scenario import Scenario
|
15
|
+
from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, NON_DETERMINISTIC_GET_REQUEST_URL, reset
|
14
16
|
|
15
17
|
@pytest.fixture(scope='module')
|
16
18
|
def runner():
|
@@ -122,3 +124,51 @@ class TestRequestSnapshot():
|
|
122
124
|
|
123
125
|
event = unprocessed_events[0]
|
124
126
|
assert event.resource_uuid == recorded_request_two.uuid
|
127
|
+
assert event.action == PUT_ACTION
|
128
|
+
|
129
|
+
class TestWhenDeleteFirst():
|
130
|
+
|
131
|
+
@pytest.fixture(scope='class')
|
132
|
+
def recorded_request(self, runner: CliRunner):
|
133
|
+
record_result = runner.invoke(record, [DETERMINISTIC_GET_REQUEST_URL])
|
134
|
+
assert record_result.exit_code == 0
|
135
|
+
return Request.last()
|
136
|
+
|
137
|
+
def test_initial_delete(self, runner: CliRunner, recorded_request: Request):
|
138
|
+
snapshot_result = runner.invoke(request, ['snapshot', '--action', DELETE_ACTION, recorded_request.key()])
|
139
|
+
assert snapshot_result.exit_code == 0
|
140
|
+
|
141
|
+
log = Log()
|
142
|
+
|
143
|
+
events = log.raw_events
|
144
|
+
assert len(events) == 1
|
145
|
+
|
146
|
+
unprocessed_events = log.unprocessed_events
|
147
|
+
assert len(unprocessed_events) == 0
|
148
|
+
|
149
|
+
def test_puts(self, runner: CliRunner, recorded_request: Request):
|
150
|
+
snapshot_result = runner.invoke(request, ['snapshot', '--action', PUT_ACTION, recorded_request.key()])
|
151
|
+
assert snapshot_result.exit_code == 0
|
152
|
+
|
153
|
+
log = Log()
|
154
|
+
|
155
|
+
events = log.raw_events
|
156
|
+
assert len(events) == 2
|
157
|
+
|
158
|
+
unprocessed_events = log.unprocessed_events
|
159
|
+
assert len(unprocessed_events) == 1
|
160
|
+
assert unprocessed_events[0].action == PUT_ACTION
|
161
|
+
|
162
|
+
def test_final_delete(self, runner: CliRunner, recorded_request: Request):
|
163
|
+
snapshot_result = runner.invoke(request, ['snapshot', '--action', DELETE_ACTION, recorded_request.key()])
|
164
|
+
assert snapshot_result.exit_code == 0
|
165
|
+
|
166
|
+
log = Log()
|
167
|
+
|
168
|
+
events = log.events
|
169
|
+
assert len(events) == 3
|
170
|
+
|
171
|
+
collapsed_events = log.collapse(events)
|
172
|
+
assert len(collapsed_events) == 1
|
173
|
+
unprocessed_events = log.unprocessed_events
|
174
|
+
assert len(unprocessed_events) == 0
|
@@ -556,7 +556,7 @@ class TestApply():
|
|
556
556
|
1. Create scenario
|
557
557
|
2. Add 2 requests to it
|
558
558
|
3. Snapshot scenario
|
559
|
-
4. Snapshot
|
559
|
+
4. Snapshot second request with action DELETE_ACTION
|
560
560
|
5. Apply
|
561
561
|
6. Expect scenario to have 1 request
|
562
562
|
'''
|
@@ -689,4 +689,124 @@ class TestApply():
|
|
689
689
|
|
690
690
|
requests = created_scenario_two.requests
|
691
691
|
|
692
|
-
assert_orm_request_equivalent(requests[0], created_scenario_request)
|
692
|
+
assert_orm_request_equivalent(requests[0], created_scenario_request)
|
693
|
+
|
694
|
+
class TestApply():
|
695
|
+
|
696
|
+
class TestWhenNoApply():
|
697
|
+
'''
|
698
|
+
1. Create scenario
|
699
|
+
2. Add 2 requests to it
|
700
|
+
3. Snapshot scenario
|
701
|
+
4. Apply scenario
|
702
|
+
5. Snapshot second request with action DELETE_ACTION
|
703
|
+
6. Prune
|
704
|
+
7. Apply
|
705
|
+
8. Expect scenario to have 1 request, because scenario depends on the request, should not be able to prune
|
706
|
+
'''
|
707
|
+
|
708
|
+
@pytest.fixture(scope='class')
|
709
|
+
def created_scenario(self, runner: CliRunner):
|
710
|
+
create_result = runner.invoke(scenario, ['create', 'test'])
|
711
|
+
assert create_result.exit_code == 0
|
712
|
+
return Scenario.last()
|
713
|
+
|
714
|
+
@pytest.fixture(scope='class', autouse=True)
|
715
|
+
def created_scenario_requests(self, runner: CliRunner, created_scenario: Scenario):
|
716
|
+
record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), DETERMINISTIC_GET_REQUEST_URL])
|
717
|
+
assert record_result.exit_code == 0
|
718
|
+
|
719
|
+
record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), NON_DETERMINISTIC_GET_REQUEST_URL])
|
720
|
+
assert record_result.exit_code == 0
|
721
|
+
|
722
|
+
return created_scenario.requests
|
723
|
+
|
724
|
+
@pytest.fixture(scope='class', autouse=True)
|
725
|
+
def snapshots(self, runner: CliRunner, created_scenario: Scenario, created_scenario_requests: List[Request]):
|
726
|
+
snapshot_result = runner.invoke(scenario, ['snapshot', created_scenario.key()])
|
727
|
+
assert snapshot_result.exit_code == 0
|
728
|
+
|
729
|
+
created_request = created_scenario_requests[1]
|
730
|
+
snapshot_result = runner.invoke(request, ['snapshot', created_request.key(), '--action', DELETE_ACTION])
|
731
|
+
assert snapshot_result.exit_code == 0
|
732
|
+
|
733
|
+
def test_events(self):
|
734
|
+
log = Log()
|
735
|
+
|
736
|
+
events = log.events
|
737
|
+
assert len(events) == 2
|
738
|
+
|
739
|
+
def test_collapsed_events(self):
|
740
|
+
log = Log()
|
741
|
+
|
742
|
+
collapsed_events = log.collapse(log.events)
|
743
|
+
assert len(collapsed_events) == 2
|
744
|
+
|
745
|
+
def test_unprocessed_events(self):
|
746
|
+
log = Log()
|
747
|
+
|
748
|
+
unprocessed_events = log.unprocessed_events
|
749
|
+
assert len(unprocessed_events) == 2
|
750
|
+
|
751
|
+
class TestWhenRemoveScenarioRequest():
|
752
|
+
'''
|
753
|
+
1. Create scenario
|
754
|
+
2. Add 2 requests to it
|
755
|
+
3. Snapshot scenario
|
756
|
+
4. Apply scenario
|
757
|
+
5. Snapshot second request with action DELETE_ACTION
|
758
|
+
6. Prune
|
759
|
+
7. Apply
|
760
|
+
8. Expect scenario to have 1 request, because scenario depends on the request, should not be able to prune
|
761
|
+
'''
|
762
|
+
|
763
|
+
@pytest.fixture(scope='class')
|
764
|
+
def created_scenario(self, runner: CliRunner):
|
765
|
+
create_result = runner.invoke(scenario, ['create', 'test'])
|
766
|
+
assert create_result.exit_code == 0
|
767
|
+
return Scenario.last()
|
768
|
+
|
769
|
+
@pytest.fixture(scope='class', autouse=True)
|
770
|
+
def created_scenario_requests(self, runner: CliRunner, created_scenario: Scenario):
|
771
|
+
record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), DETERMINISTIC_GET_REQUEST_URL])
|
772
|
+
assert record_result.exit_code == 0
|
773
|
+
|
774
|
+
record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), NON_DETERMINISTIC_GET_REQUEST_URL])
|
775
|
+
assert record_result.exit_code == 0
|
776
|
+
|
777
|
+
return created_scenario.requests
|
778
|
+
|
779
|
+
@pytest.fixture(scope='class', autouse=True)
|
780
|
+
def apply_result(self, runner: CliRunner, created_scenario: Scenario, created_scenario_requests: List[Request]):
|
781
|
+
snapshot_result = runner.invoke(scenario, ['snapshot', created_scenario.key()])
|
782
|
+
assert snapshot_result.exit_code == 0
|
783
|
+
|
784
|
+
created_scenario = Scenario.find(created_scenario.id)
|
785
|
+
assert created_scenario.requests_count == 2
|
786
|
+
apply_result = runner.invoke(snapshot, ['apply'])
|
787
|
+
assert apply_result.exit_code == 0
|
788
|
+
|
789
|
+
created_request = created_scenario_requests[1]
|
790
|
+
snapshot_result = runner.invoke(request, ['snapshot', created_request.key(), '--action', DELETE_ACTION])
|
791
|
+
assert snapshot_result.exit_code == 0
|
792
|
+
|
793
|
+
return apply_result
|
794
|
+
|
795
|
+
def test_events(self):
|
796
|
+
log = Log()
|
797
|
+
|
798
|
+
events = log.events
|
799
|
+
assert len(events) == 2
|
800
|
+
|
801
|
+
def test_collapsed_events(self):
|
802
|
+
log = Log()
|
803
|
+
|
804
|
+
collapsed_events = log.collapse(log.events)
|
805
|
+
assert len(collapsed_events) == 2
|
806
|
+
|
807
|
+
def test_unprocessed_events(self):
|
808
|
+
log = Log()
|
809
|
+
|
810
|
+
unprocessed_events = log.unprocessed_events
|
811
|
+
assert len(unprocessed_events) == 1
|
812
|
+
assert unprocessed_events[0].action == DELETE_ACTION
|
@@ -116,10 +116,10 @@ class TestPrune():
|
|
116
116
|
1. Create scenario
|
117
117
|
2. Add 2 requests to it
|
118
118
|
3. Snapshot scenario
|
119
|
-
4. Snapshot request with action DELETE_ACTION
|
120
|
-
5. Prune
|
119
|
+
4. Snapshot second request with action DELETE_ACTION
|
120
|
+
5. Prune
|
121
121
|
6. Apply
|
122
|
-
7. Expect scenario to have 1 request
|
122
|
+
7. Expect scenario to have 1 request, because scenario depends on the request, should not be able to prune
|
123
123
|
'''
|
124
124
|
|
125
125
|
@pytest.fixture(scope='class')
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from stoobly_agent.app.models.adapters.joined_request_adapter import RequestStringCLRF, JoinedRequestAdapter
|
4
|
+
|
5
|
+
class TestJoindRequestStringAdapter():
|
6
|
+
|
7
|
+
@pytest.fixture(scope='class', autouse=True)
|
8
|
+
def corrupted_raw_string(self):
|
9
|
+
toks = [
|
10
|
+
b"control",
|
11
|
+
b"header1",
|
12
|
+
b"header2",
|
13
|
+
b'',
|
14
|
+
b'body1',
|
15
|
+
b'body2'
|
16
|
+
]
|
17
|
+
return b"\n".join(toks)
|
18
|
+
|
19
|
+
@pytest.fixture(scope='class', autouse=True)
|
20
|
+
def repaired_toks(self, corrupted_raw_string: bytes):
|
21
|
+
# [b'control', b'header1', b'header2', b'', b'body1\nbody2']
|
22
|
+
return JoinedRequestAdapter.repaired_string_toks(corrupted_raw_string, RequestStringCLRF)
|
23
|
+
|
24
|
+
def test_control(self, repaired_toks: list):
|
25
|
+
assert repaired_toks[0] == b'control'
|
26
|
+
|
27
|
+
def test_header1(self, repaired_toks: list):
|
28
|
+
assert repaired_toks[1] == b'header1'
|
29
|
+
|
30
|
+
def test_header2(self, repaired_toks: list):
|
31
|
+
assert repaired_toks[2] == b'header2'
|
32
|
+
|
33
|
+
def test_clrf(self, repaired_toks: list):
|
34
|
+
assert repaired_toks[3] == b''
|
35
|
+
|
36
|
+
def test_body(self, repaired_toks: list):
|
37
|
+
assert repaired_toks[4] == b'body1\nbody2'
|
38
|
+
|
@@ -1 +1 @@
|
|
1
|
-
1.9.
|
1
|
+
1.9.3
|
@@ -1,4 +1,4 @@
|
|
1
|
-
stoobly_agent/__init__.py,sha256=
|
1
|
+
stoobly_agent/__init__.py,sha256=vWRmKJsuKpHHO4MRnGJ8EzKAw6gjkI7dsgVj0IsJ-DA,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=Vvz53yB0bR7J-QqMAkLlhcZrA4P64ZEN7w8cMbgl6o0,5261
|
@@ -142,7 +142,7 @@ stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/.docker-compose.mock.y
|
|
142
142
|
stoobly_agent/app/cli/scaffold/templates/app/gateway/record/.docker-compose.record.yml,sha256=eyLH2h33Peunus8M1sUKL9AALCG2ABhV_heiJKhvgwo,138
|
143
143
|
stoobly_agent/app/cli/scaffold/templates/app/gateway/test/.docker-compose.test.yml,sha256=oJO6i0lsuQaQeIH80yoPZo3Vs0LzUAH2WRl853yLq6g,136
|
144
144
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.config.yml,sha256=XnLQZMzzMMIwVycjyPN5QXsmRztkTFAna1kIHYuDfJQ,19
|
145
|
-
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml,sha256=
|
145
|
+
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml,sha256=bxrtZqf3YtaJCukzScslh5PgWC5q8xkGIP1wKJf33LA,111
|
146
146
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml,sha256=JN89sU5uRf6YqHvN_O63K8rwQIAPJHbhFDLFmuUjKNM,304
|
147
147
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml,sha256=FnCn64DjxyAiB2P_1JUwFmXslMR961nVZHkYiEXytlg,232
|
148
148
|
stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml,sha256=t34FNYZboJSfrKnIB2oJ3UuE_mJaW77-hcbSn3sfWec,235
|
@@ -188,7 +188,7 @@ stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure,sha256=5k
|
|
188
188
|
stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/init,sha256=EaoFDyoJbHc9Ui8ELYKmfweXAycJptVOQblszeh3XTE,94
|
189
189
|
stoobly_agent/app/cli/scaffold/templates/workflow/record/lifecycle_hooks.py,sha256=4vaVc_gnDTCLEqtcZybIk5dcmXrKmGuesF6gc3-_kX8,473
|
190
190
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure,sha256=GAU7AfSPcyDSI9RJ7mynT83YqgN9r_E9HZYx0RXE1lU,279
|
191
|
-
stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init,sha256=
|
191
|
+
stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init,sha256=_jnP53I1yyCtv5TXOnMtQcEMbU43tpZ-za7s8Ely6P0,281
|
192
192
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml,sha256=CJlZ_kugygZpmyqIauBjNZxqk7XyLaa3yl3AWj8KV28,259
|
193
193
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/lifecycle_hooks.py,sha256=U7mlzT_wBR3uhHSG6CAyt5tBUNAvdIrCw33gdB-F294,467
|
194
194
|
stoobly_agent/app/cli/scaffold/templates/workflow/test/public/.gitignore,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -203,7 +203,7 @@ stoobly_agent/app/cli/scaffold/workflow_env.py,sha256=x8V5pJmIiklD3f2q2-qq-CORf4
|
|
203
203
|
stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=Bke4lMOMxuDUFuAx9nlXHbKgYMO4KAg9ASHvjz4aVWc,1372
|
204
204
|
stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=eF3aaK4OIZXYuSBEAeBnhAL7EZrS1G4mSYrJbEiXt2o,11082
|
205
205
|
stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=Uo_yo6rVR1ZR7xpvsQvlH48AyMBVLRupd4G-bRjzm_Q,5584
|
206
|
-
stoobly_agent/app/cli/scaffold_cli.py,sha256=
|
206
|
+
stoobly_agent/app/cli/scaffold_cli.py,sha256=DFGhuCV_vugJj2o_44dypYOIwX-IY5LRWuNt3sImcfY,31946
|
207
207
|
stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
|
208
208
|
stoobly_agent/app/cli/snapshot_cli.py,sha256=Uf6g6ivsD0hUY8G99eU0fxzS3FymncAhI70PxV7Uaac,11919
|
209
209
|
stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
|
@@ -216,7 +216,7 @@ stoobly_agent/app/cli/types/snapshot_migration.py,sha256=4_Re46FKjsflcTOO3qhNsbW
|
|
216
216
|
stoobly_agent/app/cli/types/test.py,sha256=1c458B7DFBWsEk5Q1CrZ2CUi84YzEzcs-W4qTcudwAk,714
|
217
217
|
stoobly_agent/app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
218
218
|
stoobly_agent/app/models/adapters/__init__.py,sha256=cEEE--Bvrvk6DAsHx_uPgFhLnZJETP4zSBtWjMqyIKc,233
|
219
|
-
stoobly_agent/app/models/adapters/joined_request_adapter.py,sha256=
|
219
|
+
stoobly_agent/app/models/adapters/joined_request_adapter.py,sha256=fSq16n3AAlxi8KJdBESHp3JGio_M9uzMnHbnQU8VI3w,3598
|
220
220
|
stoobly_agent/app/models/adapters/mitmproxy/__init__.py,sha256=f14D_0y3_Pz6NpRXQMIwJQiWBGhwKk0Wk_AmlY3twh0,105
|
221
221
|
stoobly_agent/app/models/adapters/mitmproxy/request/__init__.py,sha256=UZeK_-vxQKD02Tuu-YHBtpR92k5mK3XFgwmd99h2VHA,281
|
222
222
|
stoobly_agent/app/models/adapters/mitmproxy/request/python_adapter.py,sha256=lWSppm63oOv7RDmTsUI0EmQkUWlQhRGdjtViBEDU-AA,377
|
@@ -258,7 +258,7 @@ stoobly_agent/app/models/factories/resource/local_db/body_adapter.py,sha256=lrnI
|
|
258
258
|
stoobly_agent/app/models/factories/resource/local_db/header_adapter.py,sha256=NQdCErFtJL7sBaLpKLYfJSEA3AiaaVuU7LUcGJ-dHOI,3104
|
259
259
|
stoobly_agent/app/models/factories/resource/local_db/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
260
260
|
stoobly_agent/app/models/factories/resource/local_db/helpers/create_request_columns_service.py,sha256=HABMelW-cvhm2WXaywbQcd4PQhzSrz4vAbN7uOdetXM,1541
|
261
|
-
stoobly_agent/app/models/factories/resource/local_db/helpers/log.py,sha256=
|
261
|
+
stoobly_agent/app/models/factories/resource/local_db/helpers/log.py,sha256=COKyrRFZgxQgLVH6cs_Mvc616BScoldamg8QqG8JV3c,11670
|
262
262
|
stoobly_agent/app/models/factories/resource/local_db/helpers/log_event.py,sha256=30wWhqibTWgnpibosclaB9OCWzArL7R4krGS8WW2bGY,3584
|
263
263
|
stoobly_agent/app/models/factories/resource/local_db/helpers/request_builder.py,sha256=PyVvsYmi5bBQ6PsUPxQ4nJM9rhhjGVOTd7ipuc2tMMM,3227
|
264
264
|
stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py,sha256=Tpuu7sZ4A2Vc5e5OAyU9pSKzbOpLpZoI-4E2Ty7n4Ac,2672
|
@@ -287,7 +287,7 @@ stoobly_agent/app/models/factories/resource/stoobly/request_adapter.py,sha256=Zr
|
|
287
287
|
stoobly_agent/app/models/factories/resource/stoobly/scenario_adapter.py,sha256=HnM4g5Qdv16QXj8u4JCiJm2Dbw9OhAxmn9e_R8oaHG4,1105
|
288
288
|
stoobly_agent/app/models/header_model.py,sha256=m91upRZr8GfE5um0d5dguUESKigBMWhSyu_X3HFk28Y,1406
|
289
289
|
stoobly_agent/app/models/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
290
|
-
stoobly_agent/app/models/helpers/apply.py,sha256=
|
290
|
+
stoobly_agent/app/models/helpers/apply.py,sha256=VWHZnZ0BnpyyO1MU6eyohimn1HGQClkDQuvYk51BttA,8491
|
291
291
|
stoobly_agent/app/models/helpers/create_request_params_service.py,sha256=o_VB2FsTGBX55UBBw1yQ8MtsJ4-0YXcxd4kNQ9l21nM,2070
|
292
292
|
stoobly_agent/app/models/model.py,sha256=77ZTByQmH5sWBcSrCF3kG_C4muHggcFyH1DWsOhIgvg,1180
|
293
293
|
stoobly_agent/app/models/query_param_model.py,sha256=EBj76phSJ9_45KgP0vIZGbkkG6-tSn_U1fNW_7qLy_4,1455
|
@@ -309,7 +309,7 @@ stoobly_agent/app/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
309
309
|
stoobly_agent/app/proxy/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
310
310
|
stoobly_agent/app/proxy/constants/custom_response_codes.py,sha256=1CaApt_6W7GrxvN8_Ozbf_SEodVEQaNZRR2sMYpI0U8,40
|
311
311
|
stoobly_agent/app/proxy/context.py,sha256=m6iu6QSZsim8meZS8H8DsZeXyjfoC-MRMHhQD1MFKM0,532
|
312
|
-
stoobly_agent/app/proxy/handle_mock_service.py,sha256=
|
312
|
+
stoobly_agent/app/proxy/handle_mock_service.py,sha256=qz7rxAg6R8U9aiXbYjuncDkx5eliPYX3x-uVLelwSr0,9451
|
313
313
|
stoobly_agent/app/proxy/handle_record_service.py,sha256=Fe8RAcMVvHhl3-XgjZ41242p4JXooYHQ-MhjcQhJn2E,4223
|
314
314
|
stoobly_agent/app/proxy/handle_replay_service.py,sha256=kBUYd8kj8UPrsYHoBXmq06DPLjCnHo8K-QsKzhDIKcw,2526
|
315
315
|
stoobly_agent/app/proxy/handle_test_service.py,sha256=WkMPbM4argVtl-TQB7VdQIvB8cOwURAahxFX5Vkqwws,8405
|
@@ -327,7 +327,7 @@ stoobly_agent/app/proxy/mitmproxy/response_facade.py,sha256=0wCSzUULUhDDV93QXUgz
|
|
327
327
|
stoobly_agent/app/proxy/mock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
328
328
|
stoobly_agent/app/proxy/mock/context.py,sha256=vDo5_3WBL73mVFnsmQWvcxvPg5nWtRJbigSrE3zGc-o,794
|
329
329
|
stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py,sha256=0KWB3KFxVrnJOKDaYxm5eoJEccw7IpJZRyUvBX61-8k,697
|
330
|
-
stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=
|
330
|
+
stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=sVw8l916ouEBj2ViDtHFjgmEA4tLmZGvBfw8wD1Ab-c,4132
|
331
331
|
stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=A1tcE3wmrC1HwLpz0aRuRw-Nucn0dyHD_yHw5BeQEJU,8146
|
332
332
|
stoobly_agent/app/proxy/mock/hashed_request_decorator.py,sha256=h1ma90fdaYI9LBWpMWMqWBz-RjNwI628O4VuS_uUBX4,5061
|
333
333
|
stoobly_agent/app/proxy/mock/ignored_components_response_builder.py,sha256=E32_E1eSdmPn2SeM_e1jWnqu4xh5w_SnmOs32Shx99E,501
|
@@ -419,7 +419,7 @@ stoobly_agent/cli.py,sha256=sw8Ke5mCvzQ50X-zsb2Ld_zW4T6S58P0fN5GyKNOrcQ,10255
|
|
419
419
|
stoobly_agent/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
420
420
|
stoobly_agent/config/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
421
421
|
stoobly_agent/config/constants/alias_resolve_strategy.py,sha256=_R1tVqFnyGxCraVS5-dhSskaDj_X8-NthsY7i_bEt9M,119
|
422
|
-
stoobly_agent/config/constants/custom_headers.py,sha256=
|
422
|
+
stoobly_agent/config/constants/custom_headers.py,sha256=8YiKDjrpe0qH8tr4qM_CgonBy6bf_HHTqQ7B_hYv2L0,1398
|
423
423
|
stoobly_agent/config/constants/env_vars.py,sha256=HAR_ZIdXXbpWQgCDaRR5RtpVyGXCsMLr_Fh8n6S12K0,1344
|
424
424
|
stoobly_agent/config/constants/headers.py,sha256=Hfv7R8_NPXAGaMiZPqywGZDnr0qcVUyfenPb4g465rE,169
|
425
425
|
stoobly_agent/config/constants/intercept_policy.py,sha256=5hIgOft8PQmCRdOHb5OEvEj10tU66DIQF3GYltlWyM8,25
|
@@ -488,7 +488,7 @@ stoobly_agent/lib/api/keys/request_key.py,sha256=68hN0KLdf0FwxSCSjeJZ1uUs526PCbN
|
|
488
488
|
stoobly_agent/lib/api/keys/resource_key.py,sha256=9qrkp3t8iJsScZvKVcBDvVbcfwmP0yEBWy3b1tHi8CM,689
|
489
489
|
stoobly_agent/lib/api/keys/scenario_key.py,sha256=VAc6gayvJS7shWgDL3SAqVET3fmgBefcygXTsKot07U,629
|
490
490
|
stoobly_agent/lib/api/keys/test_key.py,sha256=-MCWp1oYLkJ3S_Pqs62j8KkkssXpT9quKb4YqMSq1Ks,438
|
491
|
-
stoobly_agent/lib/api/keys/uuid_key.py,sha256=
|
491
|
+
stoobly_agent/lib/api/keys/uuid_key.py,sha256=30ZJIEAQHsR1KCsmeALxsKwCaUQtm7pKvFUhLmk0J4I,643
|
492
492
|
stoobly_agent/lib/api/param_builder.py,sha256=50eq0zkqRz5MVezPZMIu69fYSB6z4CD5FG26_IU3eq0,996
|
493
493
|
stoobly_agent/lib/api/projects_resource.py,sha256=7leBuQnlpgUwbYUX445lUC3jy-dzLMpTumeNPrqRc5c,1166
|
494
494
|
stoobly_agent/lib/api/query_param_names_resource.py,sha256=k-3nlyfCOa_Vwj-TMI7v8_4g9qAs50baP073gkStAKE,1710
|
@@ -675,7 +675,7 @@ stoobly_agent/test/app/cli/request/request_list_test.py,sha256=tqMjkODU2i2eDvYyk
|
|
675
675
|
stoobly_agent/test/app/cli/request/request_replay_test.py,sha256=w13NzkXhmBKvoogpQ8CdmBbCzoyqxEQvBZsEkGDCPx0,6237
|
676
676
|
stoobly_agent/test/app/cli/request/request_reset_test.py,sha256=5My6Z452eideAOUun77tWUkuu3_yGhDVxCG1baUF8Zo,1290
|
677
677
|
stoobly_agent/test/app/cli/request/request_response_test.py,sha256=Fu-A8tIn016DKme4WIaPzo3YeFY-CPtTOpaSFigUVVM,1263
|
678
|
-
stoobly_agent/test/app/cli/request/request_snapshot_test.py,sha256=
|
678
|
+
stoobly_agent/test/app/cli/request/request_snapshot_test.py,sha256=3kMmv0CuvnMXLgDQA-_u9S1DIiNOdL63L-IptVuOpf8,6308
|
679
679
|
stoobly_agent/test/app/cli/request/request_test_test.py,sha256=-cJNXKjgryVVfVt-7IN5fIhBwe3NjFoPmeavDH8lAjU,5527
|
680
680
|
stoobly_agent/test/app/cli/scaffold/cli_invoker.py,sha256=_nGDLUsYxqkeqs5DdhvAeXy3IuotpgqKHXKVzu6GDF4,3700
|
681
681
|
stoobly_agent/test/app/cli/scaffold/cli_test.py,sha256=sMNvO845MIu5DVGa1HmwXQDmKDcwrfNTdEb3fK5886w,4557
|
@@ -688,11 +688,12 @@ stoobly_agent/test/app/cli/scenario/scenario_reset_test.py,sha256=QOyytOoFu1FALn
|
|
688
688
|
stoobly_agent/test/app/cli/scenario/scenario_snapshot_test.py,sha256=IAe1l69Smd8a9E6I0CVs8lgqnC4mI4M1EFfUjC4ZHs8,3396
|
689
689
|
stoobly_agent/test/app/cli/scenario/scenario_test_integration_test.py,sha256=BdXGe1xfb79tCCTKtp4sqI6CkZL_KamvQKLK8Wwb4u0,5543
|
690
690
|
stoobly_agent/test/app/cli/snapshot/lifecycle_hooks_migrate.py,sha256=x3x5vHfZ1xmoOpZ0yRSB3kbWbPjfbLk2PJFSa1xOLoU,316
|
691
|
-
stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py,sha256=
|
691
|
+
stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py,sha256=mpkTZx8eaFFZU_RKHPcphaNl6zKCIOTJ2i4kY9BZRgQ,32395
|
692
692
|
stoobly_agent/test/app/cli/snapshot/snapshot_copy_test.py,sha256=Yg78-FhSiG_r6Jpm-sN8sn0LjVXTwTOXt6hg8ni2GIY,1953
|
693
693
|
stoobly_agent/test/app/cli/snapshot/snapshot_migrate_test.py,sha256=voEvblK6CMGCrSJDTHVmkUkLXj0auNb78jxlGiiBBQQ,7370
|
694
|
-
stoobly_agent/test/app/cli/snapshot/snapshot_prune_test.py,sha256=
|
694
|
+
stoobly_agent/test/app/cli/snapshot/snapshot_prune_test.py,sha256=bn4yUU7Eb4-6GnwnRaPZPi5Cn7XEaIsrJ_mB7jydgWw,6693
|
695
695
|
stoobly_agent/test/app/cli/snapshot/snapshot_update_test.py,sha256=fILsX2M5j4wuLRP6LJTHe4CPB8gvaEbsSoYmFCHmKVk,4514
|
696
|
+
stoobly_agent/test/app/models/adapters/joined_rquest_adapter_test.py,sha256=bF7WMrAiASQDNzDTvIXGJhsWLNhfYOmdQpSDo0hyWYY,1098
|
696
697
|
stoobly_agent/test/app/models/adapters/orm/joined_request_string_adapter_test.py,sha256=a2IHTk3l7aiLyYF7vtqissrk0MFTF2wlUBiaKWyJKfU,2667
|
697
698
|
stoobly_agent/test/app/models/adapters/orm/request/orm_mitmproxy_request_adapter_test.py,sha256=PbJsAaxPUEbF9vM7DX4z858biWf4qlGnvE8KBuy8SgY,2763
|
698
699
|
stoobly_agent/test/app/models/adapters/orm/request/orm_python_request_adapter_test.py,sha256=1rHywokXUj7z3laHhfnei8j1GVmAHDOULvRWmtIyWuQ,2488
|
@@ -707,7 +708,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
|
|
707
708
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
|
708
709
|
stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
|
709
710
|
stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
|
710
|
-
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=
|
711
|
+
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=JyvJHgCQJn-THkr7jL9H-s1Ea2VWcQz-X3BiHK64uxk,6
|
711
712
|
stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
|
712
713
|
stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
|
713
714
|
stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
|
@@ -748,8 +749,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
|
|
748
749
|
stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
|
749
750
|
stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
|
750
751
|
stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
|
751
|
-
stoobly_agent-1.9.
|
752
|
-
stoobly_agent-1.9.
|
753
|
-
stoobly_agent-1.9.
|
754
|
-
stoobly_agent-1.9.
|
755
|
-
stoobly_agent-1.9.
|
752
|
+
stoobly_agent-1.9.3.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
|
753
|
+
stoobly_agent-1.9.3.dist-info/METADATA,sha256=2T7T6OEFqeOhMH4V-af3VMftzxq-mIRn-nOcFd6-Crc,3087
|
754
|
+
stoobly_agent-1.9.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
755
|
+
stoobly_agent-1.9.3.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
|
756
|
+
stoobly_agent-1.9.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|