stoobly-agent 1.9.0__py3-none-any.whl → 1.9.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
stoobly_agent/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  COMMAND = 'stoobly-agent'
2
- VERSION = '1.9.0'
2
+ VERSION = '1.9.2'
@@ -8,6 +8,7 @@ from stoobly_agent.config.data_dir import DataDir
8
8
  from stoobly_agent.lib.logger import bcolors, Logger
9
9
 
10
10
  from .log_event import LogEvent
11
+ from .request_snapshot import RequestSnapshot
11
12
  from .snapshot_types import DELETE_ACTION, PUT_ACTION, Resource
12
13
 
13
14
  EVENT_DELIMITTER = "\n"
@@ -67,6 +68,10 @@ class Log():
67
68
  contents = self.read()
68
69
  return self.build_raw_events(contents)
69
70
 
71
+ @property
72
+ def scenario_inverted_index(self):
73
+ return self.build_scenario_inverted_index(self.target_events)
74
+
70
75
  @property
71
76
  def unprocessed_events(self) -> List[LogEvent]:
72
77
  events = self.events
@@ -133,6 +138,22 @@ class Log():
133
138
 
134
139
  def build_log_events(self, raw_events) -> List[LogEvent]:
135
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
136
157
 
137
158
  def next_version(self, last_processed_uuid: str = None):
138
159
  uuids = self.uuids()
@@ -149,7 +170,7 @@ class Log():
149
170
  def collapse(self, events: List[LogEvent]) -> List[LogEvent]:
150
171
  events_count = {}
151
172
 
152
- # More recent events take precedence over earlier ones, keep only the most recent event
173
+ # More recent events take precedence over earlier ones, only the most recent event
153
174
  for event in events:
154
175
  event_key = event.key
155
176
 
@@ -197,12 +218,18 @@ class Log():
197
218
 
198
219
  resource_index[event.resource_uuid].append(event)
199
220
 
221
+ scenario_inverted_index = self.scenario_inverted_index
222
+
200
223
  pruned_events = self.collapse(events)
201
224
  for event in pruned_events:
202
225
  snapshot = event.snapshot()
203
226
  snapshot_exists = snapshot.exists
204
227
 
205
228
  if event.action == DELETE_ACTION or not snapshot_exists:
229
+ if event.is_request() and event.resource_uuid in scenario_inverted_index:
230
+ # If a request is deleted, only prune if it's not also a part of a scenario
231
+ continue
232
+
206
233
  Logger.instance(LOG_ID).info(f"{bcolors.OKBLUE}Removing{bcolors.ENDC} {event.resource} {event.resource_uuid}")
207
234
 
208
235
  resource_events: List[LogEvent] = resource_index[event.resource_uuid]
@@ -218,8 +245,16 @@ class Log():
218
245
  removed_events[event.uuid] = True
219
246
 
220
247
  if event.action == DELETE_ACTION and snapshot_exists:
221
- if not dry_run:
248
+ if dry_run:
249
+ continue
250
+
251
+ if event.is_scenario():
252
+ # We still need to check each request in a scenario to make sure another scenario does not depend on it
253
+ snapshot.remove(lambda snapshot: self.remove_request_snapshot(snapshot, scenario_inverted_index))
254
+ elif event.is_request():
255
+ # We have already checked that a scenario does not depend on the request above
222
256
  snapshot.remove()
257
+
223
258
  Logger.instance(LOG_ID).info(f"Removing {event.resource} snapshot")
224
259
 
225
260
  def build_raw_events(self, contents: str) -> List[str]:
@@ -240,8 +275,10 @@ class Log():
240
275
 
241
276
  def remove_dangling_events(self, processed_events: List[LogEvent], unprocessed_events: List[LogEvent]):
242
277
  '''
243
- Remove DELETE events where the last processed event was a PUT
278
+ Remove DELETE events unless the last processed event was a PUT
244
279
  '''
280
+
281
+ # Build an index to keep track of the last action that occurred for a resource
245
282
  index = {}
246
283
  for event in processed_events:
247
284
  if event.action == PUT_ACTION:
@@ -249,10 +286,32 @@ class Log():
249
286
  elif event.action == DELETE_ACTION:
250
287
  if event.resource_uuid in index:
251
288
  del index[event.resource_uuid]
289
+
290
+ scenario_inverted_index = self.build_scenario_inverted_index(processed_events)
291
+
292
+ def keep(e: LogEvent):
293
+ # Keep the event if it's a PUT, it may have been updated
294
+ if e.action == PUT_ACTION:
295
+ return True
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
252
311
 
253
312
  return list(
254
313
  filter(
255
- lambda e: e.action != DELETE_ACTION or (e.action == DELETE_ACTION and e.resource_uuid in index),
314
+ keep,
256
315
  unprocessed_events
257
316
  )
258
317
  )
@@ -289,6 +348,12 @@ class Log():
289
348
  remaining_events = self.remove_dangling_events(processed_events, unprocessed_events)
290
349
  return list(filter(lambda e: e.uuid not in remaining_version_uuids, remaining_events))
291
350
 
351
+ def remove_request_snapshot(self, snapshot: RequestSnapshot, scenario_inverted_index: dict = None):
352
+ scenario_inverted_index = scenario_inverted_index or self.scenario_inverted_index
353
+
354
+ if snapshot.uuid not in scenario_inverted_index:
355
+ snapshot.remove()
356
+
292
357
  # Rotate log to history
293
358
  def rotate(self):
294
359
  if not os.path.exists(self.__log_file_path):
@@ -1,5 +1,4 @@
1
1
  import os
2
- import shutil
3
2
 
4
3
  from stoobly_agent.app.models.adapters.orm import JoinedRequestStringAdapter
5
4
  from stoobly_agent.lib.orm.request import Request
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import os
3
3
  import pdb
4
- import shutil
5
4
 
6
5
  from typing import Callable, List
7
6
 
@@ -128,12 +127,12 @@ class ScenarioSnapshot(Snapshot):
128
127
  if os.path.exists(metadata_file_path):
129
128
  os.remove(metadata_file_path)
130
129
 
131
- def remove_requests(self):
130
+ def remove_requests(self, handle_remove_request_snapshot = None):
132
131
  requests_file_path = self.requests_path
133
132
 
134
133
  if os.path.exists(requests_file_path):
135
134
  # A request only every belongs to one scenario
136
- self.iter_request_snapshots(self.__handle_remove_requests)
135
+ self.iter_request_snapshots(handle_remove_request_snapshot or self.__handle_remove_requests)
137
136
 
138
137
  os.remove(requests_file_path)
139
138
 
@@ -14,15 +14,17 @@ def snapshot_request(request: Request, **options: RequestSnapshotOptions):
14
14
  return
15
15
 
16
16
  snapshot = RequestSnapshot(request.uuid)
17
-
18
17
  snapshot.backup()
19
18
 
19
+ log = Log()
20
+
20
21
  if action == PUT_ACTION:
21
22
  snapshot.write(request, **options)
22
23
  elif action == DELETE_ACTION:
23
- snapshot.remove()
24
+ inverted_scenario_index = log.scenario_inverted_index
24
25
 
25
- log = Log()
26
+ # If a scenario currently depends on this request, we can't remove the snapshot until the scenario is also removed
27
+ log.remove_request_snapshot(snapshot, inverted_scenario_index)
26
28
 
27
29
  try:
28
30
  if action == PUT_ACTION:
@@ -40,7 +42,6 @@ def snapshot_scenario(scenario: Scenario, **options):
40
42
  return
41
43
 
42
44
  snapshot = ScenarioSnapshot(scenario.uuid)
43
-
44
45
  snapshot.backup_metadata()
45
46
 
46
47
  if action == PUT_ACTION:
@@ -50,13 +51,13 @@ def snapshot_scenario(scenario: Scenario, **options):
50
51
 
51
52
  snapshot.backup_requests()
52
53
 
54
+ log = Log()
55
+ inverted_scenario_index = log.scenario_inverted_index
53
56
  if action == PUT_ACTION:
54
- snapshot.remove_requests()
57
+ snapshot.remove_requests(lambda snapshot: log.remove_request_snapshot(snapshot, inverted_scenario_index))
55
58
  snapshot.write_requests(scenario, **options)
56
59
  elif action == DELETE_ACTION:
57
- snapshot.remove_requests()
58
-
59
- log = Log()
60
+ snapshot.remove_requests(lambda snapshot: log.remove_request_snapshot(snapshot, inverted_scenario_index))
60
61
 
61
62
  if action == PUT_ACTION:
62
63
  log.put(scenario)
@@ -1,5 +1,7 @@
1
1
  import pdb
2
2
 
3
+ from typing import List
4
+
3
5
  from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
4
6
  from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
5
7
  from stoobly_agent.app.models.factories.resource.local_db.helpers.scenario_snapshot import ScenarioSnapshot
@@ -71,6 +73,7 @@ class Apply():
71
73
  if results:
72
74
  status = results[1]
73
75
  if status == 0 or status >= 400:
76
+ self.__logger(f"{bcolors.FAIL}Error{bcolors.ENDC} {results[0]}")
74
77
  completed = False
75
78
  break
76
79
 
@@ -218,14 +221,16 @@ class Apply():
218
221
 
219
222
  snapshot_requests = {}
220
223
 
221
- request_snapshots = snapshot.request_snapshots
224
+ request_snapshots: List[RequestSnapshot] = snapshot.request_snapshots
222
225
  for request_snapshot in request_snapshots:
223
226
  raw_request = request_snapshot.request
224
-
225
- toks = raw_request.split(REQUEST_STRING_CLRF, 1)
226
227
 
228
+ if not raw_request:
229
+ return f"{request_snapshot.path} is missing", 400
230
+
231
+ toks = raw_request.split(REQUEST_STRING_CLRF, 1)
227
232
  if len(toks) != 2:
228
- return f"{snapshot.requests_path} contains an invalid request", 400
233
+ return f"{request_snapshot.path} contains an invalid request", 400
229
234
 
230
235
  uuid = request_snapshot.uuid
231
236
  res, status = self.__put_request(uuid, raw_request, scenario_id=scenario['id'])
@@ -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
- response = context.flow.response
151
- request_key = response.headers.get(custom_headers.MOCK_REQUEST_KEY)
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 = None
24
+ fixture_path = request.headers.get(MOCK_FIXTURE_PATH)
24
25
  headers = CaseInsensitiveDict()
25
26
  status_code = 200
26
27
 
27
- response_fixtures = options.get('response_fixtures')
28
- fixture: dict = __eval_response_fixtures(request, response_fixtures)
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
- request_path = 'index' if request.path == '/' else request.path
37
- _fixture_path = os.path.join(public_directory_path, request_path.lstrip('/'))
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
- if not fixture_path:
42
- fixture_path = _fixture_path
38
+ if not public_directory_path:
39
+ return
43
40
 
44
- if not os.path.isfile(fixture_path):
45
- return
46
- else:
47
- fixture_path = fixture.get('path')
48
- if not fixture_path or not os.path.isfile(fixture_path):
49
- return
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
- _headers = fixture.get('headers')
52
- headers = CaseInsensitiveDict(_headers if isinstance(_headers, dict) else {})
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
- if fixture.get('status_code'):
55
- status_code = fixture.get('status_code')
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'
@@ -28,6 +28,10 @@ class UuidKey(ResourceKey):
28
28
  toks = key.split(DELIMITTER)
29
29
 
30
30
  d = {}
31
+
32
+ if len(toks) == 0:
33
+ return d
34
+
31
35
  for tok in toks:
32
36
  d[tok[0]] = tok[1:]
33
37
 
@@ -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
@@ -547,4 +547,266 @@ class TestApply():
547
547
  recreated_scenario = Scenario.find_by(uuid=created_scenario.uuid)
548
548
  assert recreated_scenario
549
549
 
550
- assert len(recreated_scenario.requests) == 0
550
+ assert len(recreated_scenario.requests) == 0
551
+
552
+ class TestWhenDeletingRequestWhileReferenced():
553
+
554
+ class TestWhenRemoveScenarioRequest():
555
+ '''
556
+ 1. Create scenario
557
+ 2. Add 2 requests to it
558
+ 3. Snapshot scenario
559
+ 4. Snapshot second request with action DELETE_ACTION
560
+ 5. Apply
561
+ 6. Expect scenario to have 1 request
562
+ '''
563
+
564
+ @pytest.fixture(scope='class')
565
+ def created_scenario(self, runner: CliRunner):
566
+ create_result = runner.invoke(scenario, ['create', 'test'])
567
+ assert create_result.exit_code == 0
568
+ return Scenario.last()
569
+
570
+ @pytest.fixture(scope='class', autouse=True)
571
+ def created_scenario_requests(self, runner: CliRunner, created_scenario: Scenario):
572
+ record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), DETERMINISTIC_GET_REQUEST_URL])
573
+ assert record_result.exit_code == 0
574
+
575
+ record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), NON_DETERMINISTIC_GET_REQUEST_URL])
576
+ assert record_result.exit_code == 0
577
+
578
+ return created_scenario.requests
579
+
580
+ @pytest.fixture(scope='class', autouse=True)
581
+ def delete_event(self, runner: CliRunner, created_scenario: Scenario, created_scenario_requests: List[Request]):
582
+ snapshot_result = runner.invoke(scenario, ['snapshot', created_scenario.key()])
583
+ assert snapshot_result.exit_code == 0
584
+
585
+ time.sleep(0.5) # So events do not have the same uuid
586
+
587
+ created_request = created_scenario_requests[1]
588
+ snapshot_result = runner.invoke(request, ['snapshot', created_request.key(), '--action', DELETE_ACTION])
589
+ assert snapshot_result.exit_code == 0
590
+
591
+ log = Log()
592
+ events = log.events
593
+ return events[len(events) - 1]
594
+
595
+ @pytest.fixture(scope='class')
596
+ def apply_result(self, runner: CliRunner, created_scenario: Scenario):
597
+ created_scenario = Scenario.find(created_scenario.id)
598
+ assert created_scenario.requests_count == 2
599
+ apply_result = runner.invoke(snapshot, ['apply'])
600
+
601
+ return apply_result
602
+
603
+ def test_it_updates_scenario(self, created_scenario: Scenario, apply_result):
604
+ assert apply_result.exit_code == 0
605
+
606
+ created_scenario = Scenario.find(created_scenario.id)
607
+ assert created_scenario.requests_count == 1
608
+
609
+ def test_it_maintains_requests(self, created_scenario: Scenario, created_scenario_requests: List[Request]):
610
+ created_scenario = Scenario.find(created_scenario.id)
611
+ assert len(created_scenario.requests) == 1
612
+
613
+ requests = created_scenario.requests
614
+
615
+ assert_orm_request_equivalent(requests[0], created_scenario_requests[0])
616
+
617
+ class TestWhenMovingScenarioRequest():
618
+ '''
619
+ 1. Create scenario one and scenario two
620
+ 2. Add 1 request to scenario one
621
+ 3. Snapshot scenario one
622
+ 4. Move scenario one request to scenario two
623
+ 5. Snapshot scenario two
624
+ 5. Apply
625
+ 6. Expect scenario one to have 0 requests
626
+ 7. Expect scenario two to have 1 request
627
+ '''
628
+
629
+ @pytest.fixture(scope='class')
630
+ def created_scenario_one(self, runner: CliRunner):
631
+ create_result = runner.invoke(scenario, ['create', 'test1'])
632
+ assert create_result.exit_code == 0
633
+ return Scenario.last()
634
+
635
+ @pytest.fixture(scope='class')
636
+ def created_scenario_two(self, runner: CliRunner):
637
+ create_result = runner.invoke(scenario, ['create', 'test2'])
638
+ assert create_result.exit_code == 0
639
+ return Scenario.last()
640
+
641
+ @pytest.fixture(scope='class', autouse=True)
642
+ def created_scenario_request(self, runner: CliRunner, created_scenario_one: Scenario):
643
+ record_result = runner.invoke(record, ['--scenario-key', created_scenario_one.key(), DETERMINISTIC_GET_REQUEST_URL])
644
+ assert record_result.exit_code == 0
645
+
646
+ return created_scenario_one.requests[0]
647
+
648
+ @pytest.fixture(scope='class', autouse=True)
649
+ def put_event_one(self, runner: CliRunner, created_scenario_one: Scenario, created_scenario_request: Request):
650
+ snapshot_result = runner.invoke(scenario, ['snapshot', created_scenario_one.key()])
651
+ assert snapshot_result.exit_code == 0
652
+
653
+ log = Log()
654
+ events = log.events
655
+ return events[len(events) - 1]
656
+
657
+ @pytest.fixture(scope='class', autouse=True)
658
+ def put_event_two(self, runner: CliRunner, created_scenario_two: Scenario, created_scenario_request: Request):
659
+ created_scenario_request.update(scenario_id=created_scenario_two.id)
660
+
661
+ snapshot_result = runner.invoke(scenario, ['snapshot', created_scenario_two.key()])
662
+ assert snapshot_result.exit_code == 0
663
+
664
+ log = Log()
665
+ events = log.events
666
+ return events[len(events) - 1]
667
+
668
+ @pytest.fixture(scope='class')
669
+ def apply_result(self, runner: CliRunner):
670
+ apply_result = runner.invoke(snapshot, ['apply'])
671
+
672
+ return apply_result
673
+
674
+ def test_it_removes_request_from_scenario_one(self, created_scenario_one: Scenario, apply_result):
675
+ assert apply_result.exit_code == 0
676
+
677
+ created_scenario_one = Scenario.find(created_scenario_one.id)
678
+ assert created_scenario_one.requests_count == 0
679
+
680
+ def test_it_adds_request_to_scenario_two(self, created_scenario_two: Scenario, apply_result):
681
+ assert apply_result.exit_code == 0
682
+
683
+ created_scenario_two = Scenario.find(created_scenario_two.id)
684
+ assert created_scenario_two.requests_count == 1
685
+
686
+ def test_it_maintains_requests(self, created_scenario_two: Scenario, created_scenario_request):
687
+ created_scenario_two = Scenario.find(created_scenario_two.id)
688
+ assert len(created_scenario_two.requests) == 1
689
+
690
+ requests = created_scenario_two.requests
691
+
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
@@ -3,12 +3,14 @@ import pytest
3
3
  import time
4
4
 
5
5
  from click.testing import CliRunner
6
+ from typing import List
6
7
 
7
8
  from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
8
9
  from stoobly_agent.app.models.factories.resource.local_db.helpers.log_event import DELETE_ACTION, LogEvent
9
- from stoobly_agent.cli import record, request, snapshot
10
+ from stoobly_agent.cli import record, request, scenario, snapshot
10
11
  from stoobly_agent.lib.orm.request import Request
11
- from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, reset
12
+ from stoobly_agent.lib.orm.scenario import Scenario
13
+ from stoobly_agent.test.test_helper import assert_orm_request_equivalent, DETERMINISTIC_GET_REQUEST_URL, NON_DETERMINISTIC_GET_REQUEST_URL, reset
12
14
 
13
15
  @pytest.fixture(scope='module')
14
16
  def runner():
@@ -107,4 +109,71 @@ class TestPrune():
107
109
 
108
110
  apply_result = runner.invoke(snapshot, ['prune'])
109
111
  assert apply_result.exit_code == 0
110
- assert len(log.events) == 0
112
+ assert len(log.events) == 0
113
+
114
+ class TestWhenRemoveScenarioRequest():
115
+ '''
116
+ 1. Create scenario
117
+ 2. Add 2 requests to it
118
+ 3. Snapshot scenario
119
+ 4. Snapshot second request with action DELETE_ACTION
120
+ 5. Prune
121
+ 6. Apply
122
+ 7. Expect scenario to have 1 request, because scenario depends on the request, should not be able to prune
123
+ '''
124
+
125
+ @pytest.fixture(scope='class')
126
+ def created_scenario(self, runner: CliRunner):
127
+ create_result = runner.invoke(scenario, ['create', 'test'])
128
+ assert create_result.exit_code == 0
129
+ return Scenario.last()
130
+
131
+ @pytest.fixture(scope='class', autouse=True)
132
+ def created_scenario_requests(self, runner: CliRunner, created_scenario: Scenario):
133
+ record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), DETERMINISTIC_GET_REQUEST_URL])
134
+ assert record_result.exit_code == 0
135
+
136
+ record_result = runner.invoke(record, ['--scenario-key', created_scenario.key(), NON_DETERMINISTIC_GET_REQUEST_URL])
137
+ assert record_result.exit_code == 0
138
+
139
+ return created_scenario.requests
140
+
141
+ @pytest.fixture(scope='class', autouse=True)
142
+ def delete_event(self, runner: CliRunner, created_scenario: Scenario, created_scenario_requests: List[Request]):
143
+ snapshot_result = runner.invoke(scenario, ['snapshot', created_scenario.key()])
144
+ assert snapshot_result.exit_code == 0
145
+
146
+ time.sleep(0.5) # So events do not have the same uuid
147
+
148
+ created_request = created_scenario_requests[1]
149
+ snapshot_result = runner.invoke(request, ['snapshot', created_request.key(), '--action', DELETE_ACTION])
150
+ assert snapshot_result.exit_code == 0
151
+
152
+ log = Log()
153
+ events = log.events
154
+ return events[len(events) - 1]
155
+
156
+ @pytest.fixture(scope='class')
157
+ def apply_result(self, runner: CliRunner, created_scenario: Scenario):
158
+ prune_result = runner.invoke(snapshot, ['prune'])
159
+ assert prune_result.exit_code == 0
160
+
161
+ created_scenario = Scenario.find(created_scenario.id)
162
+ assert created_scenario.requests_count == 2
163
+ apply_result = runner.invoke(snapshot, ['apply'])
164
+
165
+ return apply_result
166
+
167
+ def test_it_updates_scenario(self, created_scenario: Scenario, apply_result):
168
+ assert apply_result.exit_code == 0
169
+
170
+ created_scenario = Scenario.find(created_scenario.id)
171
+ assert created_scenario.requests_count == 1
172
+
173
+ def test_it_maintains_requests(self, created_scenario: Scenario, created_scenario_requests: List[Request]):
174
+ created_scenario = Scenario.find(created_scenario.id)
175
+ assert len(created_scenario.requests) == 1
176
+
177
+ requests = created_scenario.requests
178
+
179
+ assert_orm_request_equivalent(requests[0], created_scenario_requests[0])
@@ -1 +1 @@
1
- 1.8.5
1
+ 1.9.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stoobly-agent
3
- Version: 1.9.0
3
+ Version: 1.9.2
4
4
  Summary: Record, mock, and test HTTP(s) requests. CLI agent for Stoobly
5
5
  License: Apache-2.0
6
6
  Author: Matt Le
@@ -1,4 +1,4 @@
1
- stoobly_agent/__init__.py,sha256=1zUUaHVU-BNUei6lrNgI-2kwbJnFfizP496P_Xekrwo,44
1
+ stoobly_agent/__init__.py,sha256=epZElDe2ihHM7JdgjopRIH49aMQpYH4SYIjmxXXMnY0,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
@@ -258,14 +258,14 @@ 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=0a3XRfrnbChCVrGpm5gqSlZ3zVK1S7nbWYwXTJawx58,9216
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
- stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py,sha256=XvUAqa9icAUPmktcAt30VnQspJVQZhA1K5n6ZVMRZIk,2686
265
- stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py,sha256=mv_79PgvG6Q7rwYXdV0wZMneb4eC_PPjBk612cQPLqo,4914
264
+ stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py,sha256=Tpuu7sZ4A2Vc5e5OAyU9pSKzbOpLpZoI-4E2Ty7n4Ac,2672
265
+ stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py,sha256=f5psNwbl_mXF8rLNogqgfiHJudu2C9N9F35DZ_zgn6o,4973
266
266
  stoobly_agent/app/models/factories/resource/local_db/helpers/search.py,sha256=A7KVcmxj9c3CT2rh26YH6khiEPkB_4U1UHhiYelNaws,782
267
267
  stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot.py,sha256=_8WPNE2NnvLlDbqnCfgnQveppDWfFZnqCBcX6THb1AU,742
268
- stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_service.py,sha256=fbqh5J5sMj0n_YFQFei2xhXLrSGU9vNmSX_uifA3U3M,1671
268
+ stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_service.py,sha256=szL70Vubqg2JbxZ-rnlrxiqEgyPIcKAO9SsyyFxQ6ow,2106
269
269
  stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_types.py,sha256=1pX0jrg13632g_yv_sF1ABzPR7LY7r2HC9g1a5zR1qI,379
270
270
  stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py,sha256=BajElzsTE115hWBT6v67eyNFnrHGz-sU6WBNCzWp7Fg,1237
271
271
  stoobly_agent/app/models/factories/resource/local_db/local_db_adapter.py,sha256=Zeem6abkSHXRXTvE3XwzOkUY6fbNm9wocPP2qSX3zQ8,1439
@@ -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=SQ5uBn2qGEaVIbgUVFWeTyqPg_wJVBjRte9-A6DTvzY,8293
290
+ stoobly_agent/app/models/helpers/apply.py,sha256=ev8QmDvV7flFo82JUA6kBkofYCJ_lNXQGVIpknB94g8,8501
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=HszD77W3X1UIz0DvBtbRUfuWSgc8-GS6xvWq4VDL-4w,9216
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=hc4VLnN50HBaWvFnrhQUJqdH-r3jBu9DHrpt8gbvkHY,3847
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=9PhSGOsvoyi-w3sxfYJGl9mdTqtOGaog7fMuHrdu-20,1353
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=7_aL5wVTKF68bESHvqeQ2RUeC-Fw9-zpSrl8EWuFTJw,603
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=0013aoiMZin-2YEtHzEmQspAPA3SUd_6XiItbX0U7Ok,4425
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,10 +688,10 @@ 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=p0Nyv66agEPOKY23lMNBZQNAXNL1zoYJz2N5ev4rvO0,21729
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=Hi8-rpQ9ryy1Jdl_KB9ERXgbLHLKmuFQZaxn5EVFj9I,3752
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
696
  stoobly_agent/test/app/models/adapters/orm/joined_request_string_adapter_test.py,sha256=a2IHTk3l7aiLyYF7vtqissrk0MFTF2wlUBiaKWyJKfU,2667
697
697
  stoobly_agent/test/app/models/adapters/orm/request/orm_mitmproxy_request_adapter_test.py,sha256=PbJsAaxPUEbF9vM7DX4z858biWf4qlGnvE8KBuy8SgY,2763
@@ -707,7 +707,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
707
707
  stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
708
708
  stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
709
709
  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=vBPve_ulWPaMP-stw3TjfbPykk0t0aSluYQaVmn0ZJA,5
710
+ stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=dxfL-Qxjo7CWAIdjoCBmI5kW-pkW-YJU0ao7JHCDD80,5
711
711
  stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
712
712
  stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
713
713
  stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
@@ -748,8 +748,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
748
748
  stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
749
749
  stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
750
750
  stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
751
- stoobly_agent-1.9.0.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
752
- stoobly_agent-1.9.0.dist-info/METADATA,sha256=XWWvk4MTuiKUjQ9RvRoI6r5yPRdV8Mgyzu1r1kQc9oc,3087
753
- stoobly_agent-1.9.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
754
- stoobly_agent-1.9.0.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
755
- stoobly_agent-1.9.0.dist-info/RECORD,,
751
+ stoobly_agent-1.9.2.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
752
+ stoobly_agent-1.9.2.dist-info/METADATA,sha256=qMOKulRLao6AQJC0ctx4h0IyYP6Fd-ACe5NtoyuIY1U,3087
753
+ stoobly_agent-1.9.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
754
+ stoobly_agent-1.9.2.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
755
+ stoobly_agent-1.9.2.dist-info/RECORD,,