stoobly-agent 1.5.0__py3-none-any.whl → 1.5.1__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.5.0'
2
+ VERSION = '1.5.1'
@@ -58,9 +58,9 @@ def handle_intercept_active_update(new_settings: Settings, context: Context = No
58
58
  scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
59
59
  elif _mode == intercept_mode.MOCK:
60
60
  # When mock is stopped, clear request access counts
61
- from stoobly_agent.app.models.factories.resource.local_db.helpers.tiebreak_scenario_request import reset
61
+ from stoobly_agent.app.models.factories.resource.local_db.helpers.tiebreak_scenario_request import reset_sessions
62
62
 
63
- reset()
63
+ reset_sessions()
64
64
 
65
65
  def handle_scenario_update(new_settings: Settings, context = None):
66
66
  new_scenario_key = __scenario_key(new_settings.proxy)
@@ -20,7 +20,7 @@ def generate_session_id(query: dict):
20
20
 
21
21
  return hashlib.md5(b'.'.join(toks)).hexdigest()
22
22
 
23
- def reset():
23
+ def reset_sessions():
24
24
  Cache.instance().clear(f".+\.{PREFIX}")
25
25
 
26
26
  def tiebreak_scenario_request(session_id: str, requests: List[Request]):
@@ -10,7 +10,7 @@ from stoobly_agent.app.models.types import RequestCreateParams, RequestDestroyPa
10
10
  from stoobly_agent.app.proxy.mock.custom_not_found_response_builder import CustomNotFoundResponseBuilder
11
11
  from stoobly_agent.app.proxy.mock.ignored_components_response_builder import IgnoreComponentsResponseBuilder
12
12
  from stoobly_agent.app.proxy.record.joined_request import JoinedRequest
13
- from stoobly_agent.config.constants import custom_headers
13
+ from stoobly_agent.config.constants import custom_headers, query_params as request_query_params
14
14
  from stoobly_agent.lib.orm import ORM
15
15
  from stoobly_agent.lib.orm.request import Request
16
16
  from stoobly_agent.lib.orm.response import Response
@@ -73,7 +73,6 @@ class LocalDBRequestAdapter(LocalDBAdapter):
73
73
  def response(self, **query_params: RequestColumns) -> requests.Response:
74
74
  self.__adapt_scenario_id(query_params)
75
75
 
76
- endpoint_promise = query_params.get('endpoint_promise')
77
76
  request = None
78
77
 
79
78
  if not query_params.get('request_id'):
@@ -85,16 +84,19 @@ class LocalDBRequestAdapter(LocalDBAdapter):
85
84
  requests = self.__request_orm.where_for(**request_columns).get()
86
85
 
87
86
  if 'scenario_id' in query_params:
88
- # TODO: Would need an additional ID to distinguish different scenario sessions
89
- session_id = generate_session_id(request_columns)
90
-
91
87
  if len(requests) > 1:
92
- request = tiebreak_scenario_request(session_id, requests)
88
+ session_id = query_params.get(request_query_params.SESSION_ID)
89
+ request_session_id_components = { **request_columns }
90
+
91
+ if session_id:
92
+ request_session_id_components[request_query_params.SESSION_ID] = session_id
93
+
94
+ # When multiple requests are matched for a scenario, return them in sequence
95
+ request_session_id = generate_session_id(request_session_id_components)
96
+ request = tiebreak_scenario_request(request_session_id, requests)
97
+ access_request(request_session_id, request.id)
93
98
  else:
94
99
  request = requests.last()
95
-
96
- if request:
97
- access_request(session_id, request.id)
98
100
  else:
99
101
  request = requests.last()
100
102
  else:
@@ -104,6 +106,7 @@ class LocalDBRequestAdapter(LocalDBAdapter):
104
106
  request = None
105
107
 
106
108
  if not request:
109
+ endpoint_promise = query_params.get(request_query_params.ENDPOINT_PROMISE)
107
110
  return self.__handle_request_not_found(endpoint_promise)
108
111
 
109
112
  response_record = request.response
@@ -315,8 +318,8 @@ class LocalDBRequestAdapter(LocalDBAdapter):
315
318
  return candidates.get()
316
319
 
317
320
  def __filter_request_response_columns(self, request_columns: RequestCreateParams):
318
- if request_columns.get('endpoint_promise'):
319
- del request_columns['endpoint_promise']
321
+ if request_columns.get(request_query_params.ENDPOINT_PROMISE):
322
+ del request_columns[request_query_params.ENDPOINT_PROMISE]
320
323
 
321
324
  if request_columns.get('infer'):
322
325
  del request_columns['infer']
@@ -327,6 +330,9 @@ class LocalDBRequestAdapter(LocalDBAdapter):
327
330
  if request_columns.get('retry'):
328
331
  del request_columns['retry']
329
332
 
333
+ if request_columns.get(request_query_params.SESSION_ID):
334
+ del request_columns[request_query_params.SESSION_ID]
335
+
330
336
  def __request(self, request_id: str):
331
337
  if self.validate_uuid(request_id):
332
338
  return self.__request_orm.find_by(uuid=request_id)
@@ -29,5 +29,4 @@ class RequestShowParams(TypedDict):
29
29
  headers: bool
30
30
  project_id: str
31
31
  query_params: bool
32
- response: bool
33
-
32
+ response: bool
@@ -3,7 +3,7 @@ import pdb
3
3
  import requests
4
4
  import time
5
5
 
6
- from mitmproxy.http import Request as MitmproxyRequest
6
+ from mitmproxy.http import HTTPFlow as MitmproxyHTTPFlow, Request as MitmproxyRequest
7
7
  from typing import Callable, TypedDict
8
8
 
9
9
  from stoobly_agent.app.models.request_model import RequestModel
@@ -18,7 +18,7 @@ from .mock.eval_fixtures_service import eval_fixtures
18
18
  from .mock.eval_request_service import inject_eval_request
19
19
  from .utils.allowed_request_service import get_active_mode_policy
20
20
  from .utils.request_handler import reverse_proxy
21
- from .utils.response_handler import bad_request, pass_on
21
+ from .utils.response_handler import bad_request, enable_cors, pass_on
22
22
  from .utils.rewrite import rewrite_request, rewrite_response
23
23
 
24
24
  LOG_ID = 'Mock'
@@ -44,60 +44,68 @@ def handle_request_mock_generic_without_rewrite(context: MockContext, **options:
44
44
  # @param settings [Dict]
45
45
  #
46
46
  def handle_request_mock_generic(context: MockContext, **options: MockOptions):
47
+ handle_error = options['error'] if 'error' in options and callable(options['error']) else None
48
+ handle_failure = options['failure'] if 'failure' in options and callable(options['failure']) else None
49
+ handle_success = options['success'] if 'success' in options and callable(options['success']) else None
47
50
  intercept_settings = context.intercept_settings
48
51
  request: MitmproxyRequest = context.flow.request
49
- handle_success = options['success'] if 'success' in options and callable(options['success']) else None
50
- handle_failure = options['failure'] if 'failure' in options and callable(options['failure']) else None
52
+ res = None
51
53
 
52
54
  policy = get_active_mode_policy(request, intercept_settings)
53
55
  if policy == mock_policy.NONE:
56
+ if handle_error:
57
+ res = handle_error(context)
58
+
59
+ return pass_on(context.flow, res)
60
+
61
+ if policy not in [mock_policy.ALL, mock_policy.FOUND]:
62
+ if handle_error:
63
+ res = handle_error(context)
64
+
65
+ return bad_request(
66
+ context.flow,
67
+ "Valid env MOCK_POLICY: %s, Got: %s" %
68
+ ([mock_policy.ALL, mock_policy.FOUND, mock_policy.NONE], policy)
69
+ )
70
+
71
+ if not options.get('no_rewrite'):
72
+ __rewrite_request(context)
73
+
74
+ __mock_hook(lifecycle_hooks.BEFORE_MOCK, context)
75
+
76
+ # If ignore rules are set, then ignore specified request parameters
77
+ ignore_rules = intercept_settings.ignore_rules
78
+ if len(ignore_rules) > 0:
79
+ request_facade = MitmproxyRequestFacade(request)
80
+ _ignore_rules = request_facade.select_parameter_rules(ignore_rules)
81
+ ignored_components = rewrite_rules_to_ignored_components(_ignore_rules)
82
+ options['ignored_components'] += ignored_components if 'ignored_components' in options else ignored_components
83
+
84
+ request_model = RequestModel(intercept_settings.settings)
85
+ eval_request = inject_eval_request(request_model, intercept_settings)
86
+
87
+ if policy == mock_policy.ALL:
88
+ res = eval_request_with_retry(context, eval_request, **options)
89
+
90
+ context.with_response(res)
91
+ elif policy == mock_policy.FOUND:
92
+ res = eval_request_with_retry(context, eval_request, **options)
93
+
94
+ context.with_response(res)
95
+
96
+ if res.status_code in [custom_response_codes.NOT_FOUND, custom_response_codes.IGNORE_COMPONENTS]:
97
+ try:
98
+ res = __handle_found_policy(context)
99
+ except RuntimeError:
100
+ # Do nothing, return custom error response
101
+ pass
102
+
103
+ if res.status_code == custom_response_codes.NOT_FOUND:
54
104
  if handle_failure:
55
- res = handle_failure(context)
105
+ res = handle_failure(context) or res
56
106
  else:
57
- if not options.get('no_rewrite'):
58
- __rewrite_request(context)
59
-
60
- __mock_hook(lifecycle_hooks.BEFORE_MOCK, context)
61
-
62
- # If ignore rules are set, then ignore specified request parameters
63
- ignore_rules = intercept_settings.ignore_rules
64
- if len(ignore_rules) > 0:
65
- request_facade = MitmproxyRequestFacade(request)
66
- _ignore_rules = request_facade.select_parameter_rules(ignore_rules)
67
- ignored_components = rewrite_rules_to_ignored_components(_ignore_rules)
68
- options['ignored_components'] += ignored_components if 'ignored_components' in options else ignored_components
69
-
70
- request_model = RequestModel(intercept_settings.settings)
71
- eval_request = inject_eval_request(request_model, intercept_settings)
72
-
73
- if policy == mock_policy.ALL:
74
- res = eval_request_with_retry(context, eval_request, **options)
75
-
76
- context.with_response(res)
77
-
78
- if handle_success:
79
- res = handle_success(context) or res
80
- elif policy == mock_policy.FOUND:
81
- res = eval_request_with_retry(context, eval_request, **options)
82
-
83
- context.with_response(res)
84
-
85
- if res.status_code in [custom_response_codes.NOT_FOUND, custom_response_codes.IGNORE_COMPONENTS]:
86
- if handle_failure:
87
- try:
88
- res = handle_failure(context)
89
- except RuntimeError:
90
- # Do nothing, return custom error response
91
- pass
92
- else:
93
- if handle_success:
94
- res = handle_success(context) or res
95
- else:
96
- return bad_request(
97
- context.flow,
98
- "Valid env MOCK_POLICY: %s, Got: %s" %
99
- ([mock_policy.ALL, mock_policy.FOUND, mock_policy.NONE], policy)
100
- )
107
+ if handle_success:
108
+ res = handle_success(context) or res
101
109
 
102
110
  return pass_on(context.flow, res)
103
111
 
@@ -147,13 +155,19 @@ def handle_response_mock(context: MockContext):
147
155
  __rewrite_response(context)
148
156
  __mock_hook(lifecycle_hooks.AFTER_MOCK, context)
149
157
 
150
- def __handle_mock_success(context: MockContext) -> None:
151
- if os.environ.get(env_vars.AGENT_SIMULATE_LATENCY):
152
- response = context.response
153
- start_time = context.start_time
154
- __simulate_latency(response.headers.get(custom_headers.RESPONSE_LATENCY), start_time)
155
-
156
158
  def __handle_mock_failure(context: MockContext) -> None:
159
+ flow = context.flow
160
+ request = flow.request
161
+
162
+ if request.method.upper() != 'OPTIONS':
163
+ return False
164
+
165
+ # Default OPTIONS request to allow CORS
166
+ enable_cors(flow)
167
+
168
+ return True
169
+
170
+ def __handle_found_policy(context: MockContext) -> None:
157
171
  req = context.flow.request
158
172
  intercept_settings = context.intercept_settings
159
173
  upstream_url = intercept_settings.upstream_url
@@ -169,6 +183,12 @@ def __handle_mock_failure(context: MockContext) -> None:
169
183
 
170
184
  reverse_proxy(req, upstream_url, {})
171
185
 
186
+ def __handle_mock_success(context: MockContext) -> None:
187
+ if os.environ.get(env_vars.AGENT_SIMULATE_LATENCY):
188
+ response = context.response
189
+ start_time = context.start_time
190
+ __simulate_latency(response.headers.get(custom_headers.RESPONSE_LATENCY), start_time)
191
+
172
192
  def __rewrite_request(context: MockContext):
173
193
  # Rewrite request with paramter rules for mock
174
194
 
@@ -62,6 +62,7 @@ def handle_response_test(context: ReplayContext) -> None:
62
62
  # At this point, the request may already been rewritten during replay, do not rewrite again
63
63
  handle_request_mock_generic_without_rewrite(
64
64
  MockContext(flow, intercept_settings),
65
+ error=lambda mock_context: __handle_mock_error(TestContext(context, mock_context)),
65
66
  failure=lambda mock_context: __handle_mock_failure(TestContext(context, mock_context)),
66
67
  #infer=intercept_settings.test_strategy == test_strategy.FUZZY, # For fuzzy testing we can use an inferred response
67
68
  success=lambda mock_context: __handle_mock_success(TestContext(context, mock_context))
@@ -71,6 +72,22 @@ def __decorate_test_id(flow: MitmproxyHTTPFlow, test_response: TestShowResponse)
71
72
  if test_response.get('id'):
72
73
  flow.response.headers[custom_headers.TEST_ID] = str(test_response['id'])
73
74
 
75
+ def __handle_mock_error(test_context: TestContext):
76
+ Logger.instance().warn(f"{LOG_ID}:TestStatus: Mock not enabled")
77
+
78
+ intercept_settings = test_context.intercept_settings
79
+
80
+ if intercept_settings.request_origin == request_origin.CLI:
81
+ return build_response(False, 'No test found')
82
+
83
+ def __handle_mock_failure(test_context: TestContext) -> None:
84
+ Logger.instance().warn(f"{LOG_ID}:TestStatus: No test found")
85
+
86
+ intercept_settings = test_context.intercept_settings
87
+
88
+ if intercept_settings.request_origin == request_origin.CLI:
89
+ return build_response(False, 'No test found')
90
+
74
91
  def __handle_mock_success(test_context: TestContext) -> None:
75
92
  flow: MitmproxyHTTPFlow = test_context.flow
76
93
 
@@ -138,14 +155,6 @@ def __handle_mock_success(test_context: TestContext) -> None:
138
155
 
139
156
  return flow.response
140
157
 
141
- def __handle_mock_failure(test_context: TestContext) -> None:
142
- Logger.instance().warn(f"{LOG_ID}:TestStatus: No test found")
143
-
144
- intercept_settings = test_context.intercept_settings
145
-
146
- if intercept_settings.request_origin == request_origin.CLI:
147
- return build_response(False, 'No test found')
148
-
149
158
  def __override_response(flow: MitmproxyHTTPFlow, content: bytes):
150
159
  headers = { 'Content-Type': 'text/plain' }
151
160
  headers[custom_headers.CONTENT_TYPE] = custom_headers.CONTENT_TYPE_TEST_RESULTS
@@ -12,4 +12,9 @@ class CustomNotFoundResponseBuilder():
12
12
  def build(self):
13
13
  self.__response.status_code = custom_response_codes.NOT_FOUND
14
14
  self.__response.raw = BytesIO('Request not found'.encode())
15
+ self.__response.headers = {
16
+ 'Access-Control-Allow-Origin': '*',
17
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS, POST, PATCH, PUT, DELETE',
18
+ 'Content-Type': 'text/plain',
19
+ }
15
20
  return self.__response
@@ -40,8 +40,9 @@ def eval_fixtures(request: MitmproxyRequest, **options: Options) -> Union[Respon
40
40
 
41
41
  if not fixture_path:
42
42
  fixture_path = _fixture_path
43
- if not os.path.isfile(fixture_path):
44
- return
43
+
44
+ if not os.path.isfile(fixture_path):
45
+ return
45
46
  else:
46
47
  fixture_path = fixture.get('path')
47
48
  if not fixture_path or not os.path.isfile(fixture_path):
@@ -5,16 +5,17 @@ import re
5
5
  from mitmproxy.http import Request as MitmproxyRequest
6
6
  from requests import Response
7
7
  from typing import List, TypedDict, Union
8
- from stoobly_agent.config.constants import custom_headers
9
8
 
10
- from stoobly_agent.lib.api.param_builder import ParamBuilder
11
- from stoobly_agent.lib.api.interfaces.requests import RequestResponseShowQueryParams
12
- from stoobly_agent.lib.logger import Logger
13
9
  from stoobly_agent.app.models.request_model import RequestModel
14
10
  from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
11
+ from stoobly_agent.app.proxy.mock.custom_not_found_response_builder import CustomNotFoundResponseBuilder
15
12
  from stoobly_agent.app.settings import Settings
16
13
  from stoobly_agent.app.settings.constants import request_component
17
14
  from stoobly_agent.app.settings.match_rule import MatchRule
15
+ from stoobly_agent.config.constants import custom_headers, query_params as request_query_params
16
+ from stoobly_agent.lib.api.param_builder import ParamBuilder
17
+ from stoobly_agent.lib.api.interfaces.requests import RequestResponseShowQueryParams
18
+ from stoobly_agent.lib.logger import Logger
18
19
 
19
20
  from .hashed_request_decorator import HashedRequestDecorator
20
21
  from .search_endpoint import inject_search_endpoint
@@ -40,10 +41,6 @@ def inject_eval_request(
40
41
  request_model, intercept_settings, request, ignored_components or [], **options
41
42
  )
42
43
 
43
- ###
44
- #
45
- # @param settings [Settings.mode.mock | Settings.mode.record]
46
- #
47
44
  def eval_request(
48
45
  request_model: RequestModel,
49
46
  intercept_settings: InterceptSettings,
@@ -52,9 +49,14 @@ def eval_request(
52
49
  **options: EvalRequestOptions
53
50
  ) -> Response:
54
51
  query_params_builder = ParamBuilder({})
55
- query_params_builder.with_resource_scoping(intercept_settings.project_key, intercept_settings.scenario_key)
56
52
 
57
- # Tease out API returning ignored components on not found
53
+ try:
54
+ query_params_builder.with_resource_scoping(intercept_settings.project_key, intercept_settings.scenario_key)
55
+ except:
56
+ # If project_key or scenario_key are invalid, assume custom not found
57
+ return CustomNotFoundResponseBuilder().build()
58
+
59
+ # Tease out API returning ignored components on custom not found
58
60
  if request_model.is_local and not options.get('retry'):
59
61
  remote_project_key = intercept_settings.parsed_remote_project_key
60
62
 
@@ -63,7 +65,7 @@ def eval_request(
63
65
  remote_project_id = remote_project_key.id
64
66
  endpoint_promise = lambda: search_endpoint(remote_project_id, request.method, request.url, ignored_components=1)
65
67
 
66
- query_params_builder.with_param('endpoint_promise', endpoint_promise)
68
+ query_params_builder.with_param(request_query_params.ENDPOINT_PROMISE, endpoint_promise)
67
69
 
68
70
  ignored_components = __build_ignored_components(ignored_components_list or [])
69
71
  query_params_builder.with_params(__build_request_params(request, ignored_components))
@@ -142,6 +144,9 @@ def __build_optional_params(request: MitmproxyRequest, options: EvalRequestOptio
142
144
  if custom_headers.MOCK_REQUEST_ID in headers:
143
145
  optional_params['request_id'] = headers[custom_headers.MOCK_REQUEST_ID]
144
146
 
147
+ if custom_headers.SESSION_ID in headers:
148
+ optional_params[request_query_params.SESSION_ID] = headers[custom_headers.SESSION_ID]
149
+
145
150
  return optional_params
146
151
 
147
152
  def __filter_by_match_rules(request: MitmproxyRequest, match_rules: List[MatchRule], query_params: RequestResponseShowQueryParams):
@@ -36,6 +36,17 @@ def bad_request(flow: MitmproxyHTTPFlow, message: str):
36
36
  {'Content-Type': 'text/plain'} # (optional) headers
37
37
  )
38
38
 
39
+ def enable_cors(flow: MitmproxyHTTPFlow):
40
+ flow.response = MitmproxyResponse.make(
41
+ 200,
42
+ '',
43
+ {
44
+ 'Access-Control-Allow-Origin': '*',
45
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS, POST, PATCH, PUT, DELETE',
46
+ 'Access-Control-Allow-Headers': '*'
47
+ }
48
+ )
49
+
39
50
  # Without deleting this header, causes parsing issues when reading response
40
51
  def disable_transfer_encoding(response: MitmproxyResponse) -> None:
41
52
  header_name = 'Transfer-Encoding'
@@ -19,6 +19,7 @@ RESPONSE_ID = 'X-Stoobly-Response-Id'
19
19
  RESPONSE_LATENCY = 'X-Stoobly-Request-Response-Latency'
20
20
  RESPONSE_PROXY_MODE = 'X-Stoobly-Response-Proxy-Mode'
21
21
  SCENARIO_KEY = 'X-Stoobly-Scenario-Key'
22
+ SESSION_ID = 'X-Stoobly-Session-Id'
22
23
  SERVICE_URL = 'X-Stoobly-Service-Url'
23
24
  TEST_FILTER = 'X-Stoobly-Test-Filter'
24
25
  TEST_ID = 'X-Stoobly-Test-Id'
@@ -0,0 +1,2 @@
1
+ ENDPOINT_PROMISE = 'endpoint_promise'
2
+ SESSION_ID = 'session_id'
@@ -5,7 +5,7 @@ import time
5
5
  from typing import List
6
6
 
7
7
  from stoobly_agent.app.models.factories.resource.local_db.helpers.tiebreak_scenario_request import (
8
- access_request, generate_session_id, reset as reset_tiebreak, tiebreak_scenario_request
8
+ access_request, generate_session_id, reset_sessions, tiebreak_scenario_request
9
9
  )
10
10
  from stoobly_agent.lib.cache import Cache
11
11
  from stoobly_agent.lib.orm.request import Request
@@ -92,7 +92,7 @@ class TestTiebreakScenarioRequest():
92
92
  request = tiebreak_scenario_request(session_id, requests)
93
93
  assert request.id == 2
94
94
 
95
- class TestWhenReset():
95
+ class TestWhenResetSessions():
96
96
  @pytest.fixture(scope='class')
97
97
  def created_request_one(self):
98
98
  return RequestMock(1)
@@ -105,7 +105,7 @@ class TestTiebreakScenarioRequest():
105
105
  def test_it_resets(self, cache: Cache, created_request_one: Request):
106
106
  cache.write('persists', 1)
107
107
  access_request('1', created_request_one.id)
108
- reset_tiebreak()
108
+ reset_sessions()
109
109
 
110
110
  assert cache.read('persists') != None
111
- assert len(cache.read_all()) == 1
111
+ assert len(cache.read_all()) == 1
@@ -0,0 +1,5 @@
1
+ from stoobly_agent.app.proxy.context import InterceptContext
2
+ from stoobly_agent.config.constants.custom_headers import MOCK_REQUEST_ID
3
+
4
+ def handle_before_response(context: InterceptContext):
5
+ print(context.flow.response.headers[MOCK_REQUEST_ID], end='')
@@ -0,0 +1,62 @@
1
+ import pdb
2
+ import pytest
3
+
4
+ from click.testing import CliRunner
5
+ from pathlib import Path
6
+
7
+ from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, NON_DETERMINISTIC_GET_REQUEST_URL
8
+
9
+ from stoobly_agent.config.constants.custom_headers import SESSION_ID
10
+ from stoobly_agent.cli import mock, record, scenario
11
+ from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey
12
+ from stoobly_agent.lib.orm.request import Request
13
+
14
+ @pytest.fixture(scope='module')
15
+ def runner():
16
+ return CliRunner()
17
+
18
+ class TestMocking():
19
+ @pytest.fixture(scope='class')
20
+ def lifecycle_hooks_path(self):
21
+ return str(Path(__file__).parent / 'mock_scenario_lifecycle_hooks.py')
22
+
23
+ class TestScenario():
24
+ @pytest.fixture(scope='class')
25
+ def scenario_key(self, runner: CliRunner):
26
+ res = runner.invoke(scenario, ['create', '--select', 'key', '--without-headers', 'test-scenario'])
27
+ assert res.exit_code == 0
28
+ return ScenarioKey(res.stdout.strip()).raw
29
+
30
+ @pytest.fixture(autouse=True, scope='class')
31
+ def recorded_request1(self, runner: CliRunner, scenario_key: str):
32
+ record_result = runner.invoke(record, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
33
+ assert record_result.exit_code == 0
34
+ return Request.last()
35
+
36
+ @pytest.fixture(autouse=True, scope='class')
37
+ def recorded_request2(self, runner: CliRunner, scenario_key: str):
38
+ record_result = runner.invoke(record, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
39
+ assert record_result.exit_code == 0
40
+ return Request.last()
41
+
42
+ def test_it_does_not_mocks(self, runner: CliRunner, scenario_key: str):
43
+ mock_result = runner.invoke(mock, ['--scenario-key', scenario_key, NON_DETERMINISTIC_GET_REQUEST_URL])
44
+ assert mock_result.exit_code == 1
45
+
46
+ def test_it_mocks(self, runner: CliRunner, scenario_key: str):
47
+ mock_result = runner.invoke(mock, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
48
+ assert mock_result.exit_code == 0
49
+
50
+ def test_it_mocks_in_order(
51
+ self, runner: CliRunner, lifecycle_hooks_path: str, recorded_request1: Request, recorded_request2: Request, scenario_key: str
52
+ ):
53
+ session_id = 'test'
54
+ args = [
55
+ '--lifecycle-hooks-path', lifecycle_hooks_path, '--scenario-key', scenario_key, '--output', '/dev/null',
56
+ '-H', f"{SESSION_ID}: {session_id}", DETERMINISTIC_GET_REQUEST_URL
57
+ ]
58
+ mock_result = runner.invoke(mock, args)
59
+ assert int(mock_result.stdout) == recorded_request1.id
60
+
61
+ mock_result = runner.invoke(mock, args)
62
+ assert int(mock_result.stdout) == recorded_request2.id
@@ -8,8 +8,7 @@ from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, reset
8
8
 
9
9
  from stoobly_agent.config.constants import mode
10
10
  from stoobly_agent.app.settings.constants import request_component
11
- from stoobly_agent.cli import config, mock, record, scenario
12
- from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey
11
+ from stoobly_agent.cli import config, mock, record
13
12
  from stoobly_agent.lib.orm.request import Request
14
13
 
15
14
  @pytest.fixture(scope='module')
@@ -143,10 +142,3 @@ class TestMocking():
143
142
  mock_result = runner.invoke(mock, [self.url])
144
143
  assert mock_result.exit_code == 0
145
144
 
146
- class TestScenario():
147
-
148
- @pytest.fixture
149
- def scenario_key(self, runner: CliRunner):
150
- res = runner.invoke(scenario, ['create', '--select', 'key', '--without-headers', 'test-scenario'])
151
- assert res.exit_code == 0
152
- return ScenarioKey(res.stdout.strip())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stoobly-agent
3
- Version: 1.5.0
3
+ Version: 1.5.1
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=B9xjv1tEfJArVbunhXpYOoMq78yC8Szpxo5TjlWi0y4,44
1
+ stoobly_agent/__init__.py,sha256=7y5_V-Rf7rYs6CEsRG2wWBw1jEbmStksKk8cCeaQQVk,44
2
2
  stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  stoobly_agent/app/api/__init__.py,sha256=ctkB8KR-eXO0SFhj602huHiyvQ3PslFWd8fkcufgrAI,1000
4
4
  stoobly_agent/app/api/application_http_request_handler.py,sha256=ZgCfmdr5um-OFAiuRHdBxFqPIeph9WqYkVg-oVQWw-E,5543
@@ -36,7 +36,7 @@ stoobly_agent/app/cli/helpers/endpoints_apply_service.py,sha256=9GYuUlo-nNn6z7jm
36
36
  stoobly_agent/app/cli/helpers/endpoints_import_context.py,sha256=w-iRZpNh3tX8GfafEe_6QMWCBiGNNSqvJKsfAXpnrLM,2209
37
37
  stoobly_agent/app/cli/helpers/endpoints_import_service.py,sha256=FSMy3TboX6hYtYt91jk97gD8a3PpSv_RR2pl3L9-0Zs,8322
38
38
  stoobly_agent/app/cli/helpers/feature_flags.py,sha256=neIdXHhDMv8Zq1zrfoZ28NDXkamUgDiwnu-7AS3LshE,558
39
- stoobly_agent/app/cli/helpers/handle_config_update_service.py,sha256=jGBt005zkHd7TbJhICz30wPpRGqjSFwxxygjLE2CP_g,5898
39
+ stoobly_agent/app/cli/helpers/handle_config_update_service.py,sha256=0wGSvGP0t3tCFXMU872UJbyuenzwXwFw-ZAt_4oX0f8,5916
40
40
  stoobly_agent/app/cli/helpers/handle_mock_service.py,sha256=9rHhWVXniNWsAaR1jw5GgabiK5_YApq700P3F9w3gD8,701
41
41
  stoobly_agent/app/cli/helpers/handle_replay_service.py,sha256=MfCXVWoGXsuX-nt87kxLv9rScwQ_1Ec6B0m5exMAzx8,3955
42
42
  stoobly_agent/app/cli/helpers/handle_test_service.py,sha256=nlso1XXjPKoPayN13FXsE_cAhhxa8VuNCW3Md7ufqiY,7212
@@ -258,12 +258,12 @@ stoobly_agent/app/models/factories/resource/local_db/helpers/search.py,sha256=A7
258
258
  stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot.py,sha256=8GNlt33L4zV8yIkkhmb2iLSMYegSiP91HrPyJkE9daE,733
259
259
  stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_service.py,sha256=fbqh5J5sMj0n_YFQFei2xhXLrSGU9vNmSX_uifA3U3M,1671
260
260
  stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_types.py,sha256=1pX0jrg13632g_yv_sF1ABzPR7LY7r2HC9g1a5zR1qI,379
261
- stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py,sha256=8MKvVPfkjcJPrgle_eAE_FvcdmmGIzwrDwxi4gZmzG4,1227
261
+ stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py,sha256=4enljvFTDImj8ZqZBfUVo1rhIUV2aFOCSqX5CQzLUlU,1236
262
262
  stoobly_agent/app/models/factories/resource/local_db/local_db_adapter.py,sha256=uQ5wQ_n0iM8sjY5mkdqED7w4VUKFKjHQGjez1B6ruyg,384
263
263
  stoobly_agent/app/models/factories/resource/local_db/orm_request_builder.py,sha256=4UjFY26ln2Drlg71R4AKY5vUZYbQ28HYQolJv3SBDqE,1581
264
264
  stoobly_agent/app/models/factories/resource/local_db/query_param_adapter.py,sha256=GutK2RLOPvlCWDsj1PBwJFRNT7EwweQzjObvrA2cIaM,4067
265
265
  stoobly_agent/app/models/factories/resource/local_db/replayed_response_adapter.py,sha256=vUN9ARmRVrxKHpyzWB5w-XX-quOBMFpWOKkyBxTn6mM,3634
266
- stoobly_agent/app/models/factories/resource/local_db/request_adapter.py,sha256=v3BvyHZfIZaWYTqoCusM12VdsbMlcU9VSjEx9JNlXk0,12505
266
+ stoobly_agent/app/models/factories/resource/local_db/request_adapter.py,sha256=wtNj8H3pylwToc4Q0TDfl6v6rgN6ae48GcF8WZBfd40,12992
267
267
  stoobly_agent/app/models/factories/resource/local_db/response_adapter.py,sha256=g2plyjBm5NUu1W6SVZavM2PgUYW_DuynFt_MREFhPVI,3228
268
268
  stoobly_agent/app/models/factories/resource/local_db/response_header_adapter.py,sha256=lSehvihNQgX6moFY0XLDyLkTeEweCRAwQ16QSdPsvEg,3070
269
269
  stoobly_agent/app/models/factories/resource/local_db/scenario_adapter.py,sha256=K6ITkYTUeEPNGL4_skL80K5G1kgqLtlM3IwpwXsleMw,3883
@@ -292,7 +292,7 @@ stoobly_agent/app/models/schemas/request.py,sha256=WVo-v0Di-WX5sYk5fQ5piS3uo4ROz
292
292
  stoobly_agent/app/models/types/__init__.py,sha256=TdT8MVvcJ1i4VJl-JN60FJBEQeYsKClyjuOFzIsGdgI,161
293
293
  stoobly_agent/app/models/types/endpoint.py,sha256=7e60mWQs-pNFO7KmhG9wreiF9pBYB-tct-kqQi5MIVo,588
294
294
  stoobly_agent/app/models/types/replayed_response.py,sha256=VbeX2yAPWACQBc7ePQ9PfU5Ot_Vq5aKFIGrPTvMQ8Xw,403
295
- stoobly_agent/app/models/types/request.py,sha256=zA5VJ316yoMTfSoHu9ab1wbOMzS1h6G2DAhjHb80mWI,705
295
+ stoobly_agent/app/models/types/request.py,sha256=TLfgNSo7atZvCjdI_wdMRrH1qdxeO98JRNPaPjMEZHo,703
296
296
  stoobly_agent/app/models/types/request_components.py,sha256=6qEiadFnJWaTYfnxLblIOyRytLXx5FQ9yUzkWnHr0IM,471
297
297
  stoobly_agent/app/models/types/response.py,sha256=OQNvHnyLvRSJF3E4A1GaROlZekKvflzsuTZH6hDx1tY,107
298
298
  stoobly_agent/app/models/types/scenario.py,sha256=865pzF2vlhpslWf_77KTcb3XmF0lO8_LJy7qzJJNWmQ,184
@@ -300,10 +300,10 @@ stoobly_agent/app/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
300
300
  stoobly_agent/app/proxy/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
301
301
  stoobly_agent/app/proxy/constants/custom_response_codes.py,sha256=1CaApt_6W7GrxvN8_Ozbf_SEodVEQaNZRR2sMYpI0U8,40
302
302
  stoobly_agent/app/proxy/context.py,sha256=m6iu6QSZsim8meZS8H8DsZeXyjfoC-MRMHhQD1MFKM0,532
303
- stoobly_agent/app/proxy/handle_mock_service.py,sha256=TzJXeupNT74uEUfqMYPTj8l6UJgp6-Hj7qewkPqMzoQ,8422
303
+ stoobly_agent/app/proxy/handle_mock_service.py,sha256=2EHaQOcXhbeeKly-robt4tqBY3l5lkuJ4_ZkwW9r2Nw,8859
304
304
  stoobly_agent/app/proxy/handle_record_service.py,sha256=pd_3W7YxS52KYcHqzHGDIDMXQF0oLnfmnOFFQYFNWNI,3795
305
305
  stoobly_agent/app/proxy/handle_replay_service.py,sha256=kBUYd8kj8UPrsYHoBXmq06DPLjCnHo8K-QsKzhDIKcw,2526
306
- stoobly_agent/app/proxy/handle_test_service.py,sha256=Ot4YlLavDXBQp-jKjfTHZf8uoE9rCesXcG6PwwH4w6g,8014
306
+ stoobly_agent/app/proxy/handle_test_service.py,sha256=WkMPbM4argVtl-TQB7VdQIvB8cOwURAahxFX5Vkqwws,8405
307
307
  stoobly_agent/app/proxy/hot_reload.py,sha256=bcxdN0WM5l7_zY2XYLV2wF7xCpGdZILi4dVsjqAVgig,983
308
308
  stoobly_agent/app/proxy/intercept_handler.py,sha256=XlFwO501lsAgWvIwjnNmbsdXfBd2WuNG2O8L7eeiAjc,4180
309
309
  stoobly_agent/app/proxy/intercept_settings.py,sha256=KoDoUjrwvX491NHgW2M2CvaEUHOIFejiPUX4icAPZIw,11890
@@ -317,9 +317,9 @@ stoobly_agent/app/proxy/mitmproxy/response_body_facade.py,sha256=vIu6cuCSiZDocvn
317
317
  stoobly_agent/app/proxy/mitmproxy/response_facade.py,sha256=0wCSzUULUhDDV93QXUgzMNxiiSZwcu-kUB2C5z1Ck0Y,4311
318
318
  stoobly_agent/app/proxy/mock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
319
319
  stoobly_agent/app/proxy/mock/context.py,sha256=vDo5_3WBL73mVFnsmQWvcxvPg5nWtRJbigSrE3zGc-o,794
320
- stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py,sha256=Bwy7qsPzcIUw5FTaUGeaOnsTneuCCbGkTJU-wsmN3NE,415
321
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=cGoGRvn-Y8b6b2fGDODyEjcb2NK-zwk9EfXSVmSCDbA,3850
322
- stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=uH0cwrn9q5Z3a-rtCKjypDwKLkoGMgnMDEVL3laO0qI,6967
320
+ stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py,sha256=xHjA2CNhW5A4NyPkDzws6DY5YQeJqF6b3Y3k0iJKNss,611
321
+ stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=hc4VLnN50HBaWvFnrhQUJqdH-r3jBu9DHrpt8gbvkHY,3847
322
+ stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=EKHZT_sKZhGcGFCDa3XOSzYkSJIfJJUg4164BTxKUeY,7365
323
323
  stoobly_agent/app/proxy/mock/hashed_request_decorator.py,sha256=h1ma90fdaYI9LBWpMWMqWBz-RjNwI628O4VuS_uUBX4,5061
324
324
  stoobly_agent/app/proxy/mock/ignored_components_response_builder.py,sha256=E32_E1eSdmPn2SeM_e1jWnqu4xh5w_SnmOs32Shx99E,501
325
325
  stoobly_agent/app/proxy/mock/request_hasher.py,sha256=WeOeksmW_b5Ql0Xb0fL2WMH3MVHrm-9GYWYoKYS1teg,3755
@@ -375,7 +375,7 @@ stoobly_agent/app/proxy/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
375
375
  stoobly_agent/app/proxy/utils/allowed_request_service.py,sha256=dk4xnHzCgxyvD2Ih6JDbp4_9Y7s2ar-3lpM93CW--XA,3632
376
376
  stoobly_agent/app/proxy/utils/publish_change_service.py,sha256=vSpFt0Ox9fceJlalHji61N6StdkEZwAvVXhHXO6fwEI,1259
377
377
  stoobly_agent/app/proxy/utils/request_handler.py,sha256=VscAXf2_F1C1yrt2gNImPkatosuGj356pARTd_WnTUE,846
378
- stoobly_agent/app/proxy/utils/response_handler.py,sha256=FP_4p0MQZGJdJdhdUrLH2qelq8yNKW4AQSr74ccxCj4,1429
378
+ stoobly_agent/app/proxy/utils/response_handler.py,sha256=t5spjycvPDOZi0YU39gpavnYLhb7C29HnRe9RmzGCtU,1749
379
379
  stoobly_agent/app/proxy/utils/rewrite.py,sha256=-Lf5H8aF63G37beoaG3ehr5LIAQfsKTWQvMCeNL34I4,2459
380
380
  stoobly_agent/app/proxy/utils/rewrite_rules_to_ignored_components_service.py,sha256=TEWakpZ7C468PW8lVJsRwqkRElpaCR5EOgNNzyGAleY,1043
381
381
  stoobly_agent/app/settings/__init__.py,sha256=Fm9Y4e0g_wQyPULavyv9IJopINTml7RYck6mJAPtEHY,6071
@@ -410,7 +410,7 @@ stoobly_agent/cli.py,sha256=nZDwOnhcNcZlr2hlznWVDnidPMe3LkVv88HOjamAqmA,10255
410
410
  stoobly_agent/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
411
411
  stoobly_agent/config/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
412
412
  stoobly_agent/config/constants/alias_resolve_strategy.py,sha256=_R1tVqFnyGxCraVS5-dhSskaDj_X8-NthsY7i_bEt9M,119
413
- stoobly_agent/config/constants/custom_headers.py,sha256=IJZzZN77ZpbTr62EkXatm1r-I1_i4HEErO_LlQZLRU0,1279
413
+ stoobly_agent/config/constants/custom_headers.py,sha256=cZZ3nae2fpnk_uUXFNYjrPp7Cdtgv0ytRw-WdHzYMZg,1315
414
414
  stoobly_agent/config/constants/env_vars.py,sha256=HAR_ZIdXXbpWQgCDaRR5RtpVyGXCsMLr_Fh8n6S12K0,1344
415
415
  stoobly_agent/config/constants/headers.py,sha256=Hfv7R8_NPXAGaMiZPqywGZDnr0qcVUyfenPb4g465rE,169
416
416
  stoobly_agent/config/constants/intercept_policy.py,sha256=5hIgOft8PQmCRdOHb5OEvEj10tU66DIQF3GYltlWyM8,25
@@ -418,6 +418,7 @@ stoobly_agent/config/constants/lifecycle_hooks.py,sha256=aobclZmcND_mUnFKkUgpxgw
418
418
  stoobly_agent/config/constants/mitmproxy.py,sha256=AhaxRGMb7OC4DBqNvyBP0OVQSByCeA-itefzmMoIS14,116
419
419
  stoobly_agent/config/constants/mock_policy.py,sha256=oS3eKL5Ezpnm_4zvztcLvvtKppxm7eKvOOBHYKyKpeU,133
420
420
  stoobly_agent/config/constants/mode.py,sha256=J35vV1LPQRhD2ob6DaJ7mLGJrMhv-CVnC6IHWd7b_GM,160
421
+ stoobly_agent/config/constants/query_params.py,sha256=b-QQGKTqf-HePt0X4ltFksf5ETiIFPgBvh-kFrF7kUk,63
421
422
  stoobly_agent/config/constants/record_policy.py,sha256=n5h5Mw0-RcwEGH18koE3-Krg-ek2rRMDOHdqMxJd4Gg,181
422
423
  stoobly_agent/config/constants/replay_policy.py,sha256=FgSUaVb52wknIbTu27cWxvB35ZhxP0g1KNc6Twhtjmc,117
423
424
  stoobly_agent/config/constants/request_origin.py,sha256=zLdLbGb8TQOK5TvsPdD17SAW_OupbM3Y9mKnv7C3BRU,39
@@ -692,7 +693,7 @@ stoobly_agent/test/app/models/adapters/python/request/stoobly_adapter_test.py,sh
692
693
  stoobly_agent/test/app/models/adapters/python/response/mitmproxy_response_adapter_test.py,sha256=dEUhIH5kekZsJDwOmWz0eHA2dWZ7QPYmgq_Na9ce3HA,1176
693
694
  stoobly_agent/test/app/models/adapters/python/response/raw_response_adapter_test.py,sha256=oJ0l2FZEH3-JANcpX4N83GKxqgVkaGRKIS7rsY8O7mM,1199
694
695
  stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sha256=FbGtjO_K_L2lZw1i6b1LlZoUyhisYqVST7f9T0PmpFk,3501
695
- stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=5IFGVGn3ogvRT0fzZgXzhrYlSfSlr8IR1p_XExaegMQ,3399
696
+ stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
696
697
  stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
697
698
  stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
698
699
  stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=H03AltWPfSHjh1ZxrubymxIKuEIY-kfbLLU7yetbTaw,5
@@ -714,7 +715,9 @@ stoobly_agent/test/app/test/matchers/contract_test.py,sha256=fMV8BX4JfbIOD5i7ZV7
714
715
  stoobly_agent/test/app/test/matchers/diff_test.py,sha256=fNbzAz3MptieVIrMInxGlNa4FsBh-VQ2m7-OsrNm9sw,10266
715
716
  stoobly_agent/test/app/test/matchers/fuzzy_test.py,sha256=wkySpK00dFZLlXmW7xE7Q6UPJYLiq8aCnIHt4Nm4WEk,3231
716
717
  stoobly_agent/test/cli/lifecycle_hooks_test.py,sha256=zjLI8zteszWXgQuyEYM47MtI8KgLbEFVYJTaJdyBqOc,2491
717
- stoobly_agent/test/cli/mock_test.py,sha256=zhIPJFb4AxRR6ltHd_Su_BKMwQtZLy0Jx6VnkEELfO8,5983
718
+ stoobly_agent/test/cli/mock_scenario_lifecycle_hooks.py,sha256=e57A3FWHt1ZgaWKhbl4Hs2Y4ux8tKMu9nxbqPCj6w54,254
719
+ stoobly_agent/test/cli/mock_scenario_test.py,sha256=tHT-p8Vm8APP2KOlyfZ-5z_AxZO9TS4sDgui3X2qkaE,2620
720
+ stoobly_agent/test/cli/mock_test.py,sha256=zYAFhHkR-1oafZzzL_4aPOWzM2nXWRHGt36yTAuG8Rw,5636
718
721
  stoobly_agent/test/cli/record_test.py,sha256=CG7prrooTgkW8RYaiDRYD3LuuDIR8dz8jaP9RNRrF4o,15814
719
722
  stoobly_agent/test/config/data_dir_test.py,sha256=sszUW6MKGFCS9JGhYL-E91hBzfI2jMaHRJMifNjZ00M,2659
720
723
  stoobly_agent/test/mock_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -734,8 +737,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=x7
734
737
  stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
735
738
  stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
736
739
  stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
737
- stoobly_agent-1.5.0.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
738
- stoobly_agent-1.5.0.dist-info/METADATA,sha256=7g-q1O6IbKxg8zRdiyxSRGKBND6LLDeWTR9CBcDSzII,3087
739
- stoobly_agent-1.5.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
740
- stoobly_agent-1.5.0.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
741
- stoobly_agent-1.5.0.dist-info/RECORD,,
740
+ stoobly_agent-1.5.1.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
741
+ stoobly_agent-1.5.1.dist-info/METADATA,sha256=9Iio88ZqJsyCuENaWJPg_Ghxdd53RZtYsQzuqCJDR-E,3087
742
+ stoobly_agent-1.5.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
743
+ stoobly_agent-1.5.1.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
744
+ stoobly_agent-1.5.1.dist-info/RECORD,,