stoobly-agent 1.10.0__py3-none-any.whl → 1.10.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 +1 -1
- stoobly_agent/__main__.py +10 -0
- stoobly_agent/app/api/application_http_request_handler.py +5 -2
- stoobly_agent/app/cli/ca_cert_cli.py +9 -5
- stoobly_agent/app/cli/helpers/replay_facade.py +2 -2
- stoobly_agent/app/cli/intercept_cli.py +5 -5
- stoobly_agent/app/cli/request_cli.py +2 -2
- stoobly_agent/app/cli/scaffold/app.py +14 -5
- stoobly_agent/app/cli/scaffold/app_command.py +0 -4
- stoobly_agent/app/cli/scaffold/app_config.py +49 -2
- stoobly_agent/app/cli/scaffold/app_create_command.py +145 -76
- stoobly_agent/app/cli/scaffold/constants.py +9 -4
- stoobly_agent/app/cli/scaffold/docker/constants.py +3 -1
- stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py +4 -4
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +31 -54
- stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py +3 -0
- stoobly_agent/app/cli/scaffold/docker/template_files.py +112 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +30 -47
- stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py +3 -2
- stoobly_agent/app/cli/scaffold/docker/workflow/detached_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/dns_decorator.py +2 -3
- stoobly_agent/app/cli/scaffold/docker/workflow/local_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/run_command.py +423 -0
- stoobly_agent/app/cli/scaffold/local/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/local/service/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/local/service/builder.py +72 -0
- stoobly_agent/app/cli/scaffold/local/workflow/__init__.py +0 -0
- stoobly_agent/app/cli/scaffold/local/workflow/builder.py +35 -0
- stoobly_agent/app/cli/scaffold/local/workflow/run_command.py +339 -0
- stoobly_agent/app/cli/scaffold/service_command.py +9 -1
- stoobly_agent/app/cli/scaffold/service_config.py +9 -25
- stoobly_agent/app/cli/scaffold/service_create_command.py +18 -6
- stoobly_agent/app/cli/scaffold/service_docker_compose.py +3 -3
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +10 -7
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +2 -2
- stoobly_agent/app/cli/scaffold/templates/app/build/.docker-compose.base.yml +4 -4
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/record/configure +28 -0
- stoobly_agent/app/cli/scaffold/templates/app/build/test/configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/.docker-compose.base.yml +4 -4
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/configure +3 -0
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/build/record/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/record/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/record/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/build/test/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/test/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/build/test/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/mock/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/mock/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/mock/.run +19 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/record/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/record/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/record/.run +19 -0
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/services/entrypoint/test/.run +19 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/scaffold/.up +0 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/mock/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/mock/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/mock/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.configure +25 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/record/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/test/.configure +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/test/.init +5 -1
- stoobly_agent/app/cli/scaffold/templates/build/workflows/test/.run +14 -0
- stoobly_agent/app/cli/scaffold/templates/constants.py +35 -19
- stoobly_agent/app/cli/scaffold/templates/factory.py +34 -18
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.run +21 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.run +21 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/configure +21 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/run +3 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/configure +5 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/run +3 -0
- stoobly_agent/app/cli/scaffold/workflow_command.py +18 -4
- stoobly_agent/app/cli/scaffold/workflow_copy_command.py +5 -4
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +31 -29
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +18 -151
- stoobly_agent/app/cli/scaffold_cli.py +134 -182
- stoobly_agent/app/cli/scenario_cli.py +2 -2
- stoobly_agent/app/cli/types/test.py +2 -2
- stoobly_agent/app/cli/types/workflow_run_command.py +52 -3
- stoobly_agent/app/proxy/handle_mock_service.py +1 -1
- stoobly_agent/app/proxy/intercept_settings.py +6 -26
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +177 -27
- stoobly_agent/app/proxy/mock/types/__init__.py +22 -1
- stoobly_agent/app/proxy/record/upload_request_service.py +3 -6
- stoobly_agent/app/proxy/replay/body_parser_service.py +8 -5
- stoobly_agent/app/proxy/replay/multipart.py +15 -13
- stoobly_agent/app/proxy/replay/replay_request_service.py +2 -2
- stoobly_agent/app/proxy/run.py +3 -0
- stoobly_agent/app/proxy/test/context.py +0 -4
- stoobly_agent/app/proxy/test/context_abc.py +0 -5
- stoobly_agent/app/proxy/utils/publish_change_service.py +20 -23
- stoobly_agent/app/settings/__init__.py +10 -7
- stoobly_agent/cli.py +61 -16
- stoobly_agent/config/data_dir.py +1 -8
- stoobly_agent/public/12-es2015.618ecfd5f735b801b50f.js +1 -0
- stoobly_agent/public/12-es5.618ecfd5f735b801b50f.js +1 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/main-es2015.5a9aa16433404c3f423a.js +1 -0
- stoobly_agent/public/main-es5.5a9aa16433404c3f423a.js +1 -0
- stoobly_agent/public/runtime-es2015.77bcd31efed9e5d5d431.js +1 -0
- stoobly_agent/public/runtime-es5.77bcd31efed9e5d5d431.js +1 -0
- stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +17 -6
- stoobly_agent/test/app/cli/scaffold/docker/cli_invoker.py +177 -0
- stoobly_agent/test/app/cli/scaffold/{cli_test.py → docker/cli_test.py} +4 -11
- stoobly_agent/test/app/cli/scaffold/{e2e_test.py → docker/e2e_test.py} +42 -27
- stoobly_agent/test/app/cli/scaffold/local/__init__.py +0 -0
- stoobly_agent/test/app/cli/scaffold/{cli_invoker.py → local/cli_invoker.py} +38 -32
- stoobly_agent/test/app/cli/scaffold/local/e2e_test.py +342 -0
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py +903 -2
- stoobly_agent/test/app/proxy/replay/body_parser_service_test.py +95 -3
- stoobly_agent/test/config/data_dir_test.py +2 -7
- stoobly_agent/test/test_helper.py +16 -5
- {stoobly_agent-1.10.0.dist-info → stoobly_agent-1.10.2.dist-info}/METADATA +4 -2
- {stoobly_agent-1.10.0.dist-info → stoobly_agent-1.10.2.dist-info}/RECORD +157 -129
- {stoobly_agent-1.10.0.dist-info → stoobly_agent-1.10.2.dist-info}/WHEEL +1 -1
- stoobly_agent/app/cli/helpers/shell.py +0 -26
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/bin/configure +0 -3
- stoobly_agent/app/cli/scaffold/templates/app/build/record/bin/configure +0 -3
- stoobly_agent/app/cli/scaffold/templates/app/build/test/bin/configure +0 -3
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/bin/configure +0 -3
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/bin/configure +0 -3
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/bin/configure +0 -3
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/configure +0 -13
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +0 -47
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure +0 -13
- stoobly_agent/public/12-es2015.be58ed0ef449008b932e.js +0 -1
- stoobly_agent/public/12-es5.be58ed0ef449008b932e.js +0 -1
- stoobly_agent/public/main-es2015.089b46f303768fbe864f.js +0 -1
- stoobly_agent/public/main-es5.089b46f303768fbe864f.js +0 -1
- stoobly_agent/public/runtime-es2015.f8c814b38b27708e91c1.js +0 -1
- stoobly_agent/public/runtime-es5.f8c814b38b27708e91c1.js +0 -1
- /stoobly_agent/app/cli/scaffold/templates/app/build/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/mock/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/record/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/build/test/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/gateway/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/gateway/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/{.docker-compose.exec.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/{.docker-compose.mock.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/{.docker-compose.record.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/{.docker-compose.test.yml → .docker-compose.yml} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/mock/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/record/{bin/init → init} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/test/{bin/init → init} +0 -0
- {stoobly_agent-1.10.0.dist-info → stoobly_agent-1.10.2.dist-info}/entry_points.txt +0 -0
- {stoobly_agent-1.10.0.dist-info → stoobly_agent-1.10.2.dist-info/licenses}/LICENSE +0 -0
@@ -1,8 +1,10 @@
|
|
1
|
+
import json
|
1
2
|
import os
|
2
3
|
import pdb
|
3
4
|
import pytest
|
4
5
|
import requests
|
5
6
|
import shutil
|
7
|
+
import yaml
|
6
8
|
|
7
9
|
from click.testing import CliRunner
|
8
10
|
from mitmproxy.http import Request as MitmproxyRequest
|
@@ -88,7 +90,14 @@ class TestEvalFixturesService():
|
|
88
90
|
|
89
91
|
@pytest.fixture()
|
90
92
|
def fixtures_response(self, mitmproxy_request: MitmproxyRequest, response_fixtures: Fixtures):
|
91
|
-
|
93
|
+
# Convert response_fixtures to a temporary YAML file
|
94
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
95
|
+
fixtures_file = os.path.join(tmp_dir_path, 'test_response_fixtures.yml')
|
96
|
+
|
97
|
+
with open(fixtures_file, 'w') as f:
|
98
|
+
yaml.dump(response_fixtures, f)
|
99
|
+
|
100
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures_path=fixtures_file)
|
92
101
|
assert res != None
|
93
102
|
return res
|
94
103
|
|
@@ -177,4 +186,896 @@ class TestEvalFixturesService():
|
|
177
186
|
assert public_directory_response.status_code == 200
|
178
187
|
|
179
188
|
def test_default_it_headers(self, public_directory_default_response: requests.Response):
|
180
|
-
assert public_directory_default_response.headers['Content-Type'] == 'application/json'
|
189
|
+
assert public_directory_default_response.headers['Content-Type'] == 'application/json'
|
190
|
+
|
191
|
+
class TestMultiplePublicDirectories():
|
192
|
+
@pytest.fixture(scope='class')
|
193
|
+
def request_method(self):
|
194
|
+
return 'GET'
|
195
|
+
|
196
|
+
@pytest.fixture(scope='class')
|
197
|
+
def request_url(self):
|
198
|
+
return 'https://petstore.swagger.io'
|
199
|
+
|
200
|
+
@pytest.fixture(scope='class')
|
201
|
+
def api_request_url(self):
|
202
|
+
return 'https://api.example.com'
|
203
|
+
|
204
|
+
@pytest.fixture(scope='class')
|
205
|
+
def created_request(
|
206
|
+
self, settings: Settings, request_method: str, request_url: str
|
207
|
+
):
|
208
|
+
status = RequestBuilder(
|
209
|
+
method=request_method,
|
210
|
+
request_headers={'accept': 'text/html;q=0.1,application/json;q=0.9'},
|
211
|
+
response_body='',
|
212
|
+
status_code=200,
|
213
|
+
url=request_url,
|
214
|
+
).with_settings(settings).build()[1]
|
215
|
+
assert status == 200
|
216
|
+
|
217
|
+
return Request.last()
|
218
|
+
|
219
|
+
@pytest.fixture(scope='class')
|
220
|
+
def api_created_request(
|
221
|
+
self, settings: Settings, request_method: str, api_request_url: str
|
222
|
+
):
|
223
|
+
status = RequestBuilder(
|
224
|
+
method=request_method,
|
225
|
+
request_headers={'accept': 'text/html;q=0.1,application/json;q=0.9'},
|
226
|
+
response_body='',
|
227
|
+
status_code=200,
|
228
|
+
url=api_request_url,
|
229
|
+
).with_settings(settings).build()[1]
|
230
|
+
assert status == 200
|
231
|
+
|
232
|
+
return Request.last()
|
233
|
+
|
234
|
+
@pytest.fixture()
|
235
|
+
def mitmproxy_request(self, created_request: Request) -> MitmproxyRequest:
|
236
|
+
return MitmproxyRequestAdapter(created_request).adapt()
|
237
|
+
|
238
|
+
@pytest.fixture()
|
239
|
+
def api_mitmproxy_request(self, api_created_request: Request) -> MitmproxyRequest:
|
240
|
+
return MitmproxyRequestAdapter(api_created_request).adapt()
|
241
|
+
|
242
|
+
@pytest.fixture(scope='class')
|
243
|
+
def main_public_directory(self):
|
244
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
245
|
+
public_dir_path = os.path.join(tmp_dir_path, 'main_public')
|
246
|
+
if not os.path.exists(public_dir_path):
|
247
|
+
os.mkdir(public_dir_path)
|
248
|
+
return public_dir_path
|
249
|
+
|
250
|
+
@pytest.fixture(scope='class')
|
251
|
+
def api_public_directory(self):
|
252
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
253
|
+
public_dir_path = os.path.join(tmp_dir_path, 'api_public')
|
254
|
+
if not os.path.exists(public_dir_path):
|
255
|
+
os.mkdir(public_dir_path)
|
256
|
+
return public_dir_path
|
257
|
+
|
258
|
+
@pytest.fixture(scope='class')
|
259
|
+
def fallback_public_directory(self):
|
260
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
261
|
+
public_dir_path = os.path.join(tmp_dir_path, 'fallback_public')
|
262
|
+
if not os.path.exists(public_dir_path):
|
263
|
+
os.mkdir(public_dir_path)
|
264
|
+
return public_dir_path
|
265
|
+
|
266
|
+
@pytest.fixture(scope='class')
|
267
|
+
def main_file_contents(self):
|
268
|
+
return b'Main Site Content'
|
269
|
+
|
270
|
+
@pytest.fixture(scope='class')
|
271
|
+
def api_file_contents(self):
|
272
|
+
return b'API Documentation'
|
273
|
+
|
274
|
+
@pytest.fixture(scope='class')
|
275
|
+
def fallback_file_contents(self):
|
276
|
+
return b'Fallback Content'
|
277
|
+
|
278
|
+
@pytest.fixture(autouse=True, scope='class')
|
279
|
+
def setup_files(self, main_public_directory: str, api_public_directory: str, fallback_public_directory: str,
|
280
|
+
main_file_contents: bytes, api_file_contents: bytes, fallback_file_contents: bytes):
|
281
|
+
# Create index.html in main directory
|
282
|
+
main_path = os.path.join(main_public_directory, 'index.html')
|
283
|
+
with open(main_path, 'wb') as fp:
|
284
|
+
fp.write(main_file_contents)
|
285
|
+
|
286
|
+
# Create index.html in API directory
|
287
|
+
api_path = os.path.join(api_public_directory, 'index.html')
|
288
|
+
with open(api_path, 'wb') as fp:
|
289
|
+
fp.write(api_file_contents)
|
290
|
+
|
291
|
+
# Create index.html in fallback directory
|
292
|
+
fallback_path = os.path.join(fallback_public_directory, 'index.html')
|
293
|
+
with open(fallback_path, 'wb') as fp:
|
294
|
+
fp.write(fallback_file_contents)
|
295
|
+
|
296
|
+
def test_origin_specific_routing(self, api_mitmproxy_request: MitmproxyRequest,
|
297
|
+
main_public_directory: str, api_public_directory: str, fallback_public_directory: str,
|
298
|
+
api_file_contents: bytes):
|
299
|
+
"""Test that origin-specific paths are used when origin matches."""
|
300
|
+
# Multiple paths with origin specification
|
301
|
+
public_paths = f"{main_public_directory}:https://petstore\\.swagger\\.io,{api_public_directory}:https://api\\.example\\.com,{fallback_public_directory}"
|
302
|
+
|
303
|
+
res: requests.Response = eval_fixtures(api_mitmproxy_request, public_directory_path=public_paths)
|
304
|
+
assert res is not None
|
305
|
+
assert res.raw.read() == api_file_contents
|
306
|
+
|
307
|
+
def test_fallback_routing(self, mitmproxy_request: MitmproxyRequest,
|
308
|
+
main_public_directory: str, api_public_directory: str, fallback_public_directory: str,
|
309
|
+
main_file_contents: bytes):
|
310
|
+
"""Test that origin-specific paths are used when origin matches."""
|
311
|
+
# Multiple paths with origin specification - https://petstore.swagger.io should match first path
|
312
|
+
public_paths = f"{main_public_directory}:https://petstore\\.swagger\\.io,{api_public_directory}:https://api\\.example\\.com,{fallback_public_directory}"
|
313
|
+
|
314
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, public_directory_path=public_paths)
|
315
|
+
assert res is not None
|
316
|
+
assert res.raw.read() == main_file_contents
|
317
|
+
|
318
|
+
def test_wildcard_origin_matching(self, settings: Settings, main_public_directory: str, api_public_directory: str,
|
319
|
+
fallback_public_directory: str, api_file_contents: bytes):
|
320
|
+
"""Test wildcard origin matching (*.example.com)."""
|
321
|
+
# Create request for subdomain
|
322
|
+
status = RequestBuilder(
|
323
|
+
method='GET',
|
324
|
+
request_headers={'accept': 'text/html'},
|
325
|
+
response_body='',
|
326
|
+
status_code=200,
|
327
|
+
url='https://sub.example.com',
|
328
|
+
).with_settings(settings).build()[1]
|
329
|
+
assert status == 200
|
330
|
+
|
331
|
+
subdomain_request = Request.last()
|
332
|
+
subdomain_mitmproxy_request = MitmproxyRequestAdapter(subdomain_request).adapt()
|
333
|
+
|
334
|
+
# Use regex wildcard pattern
|
335
|
+
public_paths = f"{main_public_directory}:https://petstore\\.swagger\\.io,{api_public_directory}:https://.*\\.example\\.com,{fallback_public_directory}"
|
336
|
+
|
337
|
+
res: requests.Response = eval_fixtures(subdomain_mitmproxy_request, public_directory_path=public_paths)
|
338
|
+
assert res is not None
|
339
|
+
assert res.raw.read() == api_file_contents
|
340
|
+
|
341
|
+
def test_no_origin_match_uses_fallback(self, settings: Settings, main_public_directory: str,
|
342
|
+
api_public_directory: str, fallback_public_directory: str,
|
343
|
+
fallback_file_contents: bytes):
|
344
|
+
"""Test that requests with no matching origin use fallback path."""
|
345
|
+
# Create request for unmatched origin
|
346
|
+
status = RequestBuilder(
|
347
|
+
method='GET',
|
348
|
+
request_headers={'accept': 'text/html'},
|
349
|
+
response_body='',
|
350
|
+
status_code=200,
|
351
|
+
url='https://unknown.com',
|
352
|
+
).with_settings(settings).build()[1]
|
353
|
+
assert status == 200
|
354
|
+
|
355
|
+
unknown_request = Request.last()
|
356
|
+
unknown_mitmproxy_request = MitmproxyRequestAdapter(unknown_request).adapt()
|
357
|
+
|
358
|
+
public_paths = f"{main_public_directory}:https://petstore\\.swagger\\.io,{api_public_directory}:https://api\\.example\\.com,{fallback_public_directory}"
|
359
|
+
|
360
|
+
res: requests.Response = eval_fixtures(unknown_mitmproxy_request, public_directory_path=public_paths)
|
361
|
+
assert res is not None
|
362
|
+
assert res.raw.read() == fallback_file_contents
|
363
|
+
|
364
|
+
def test_multiple_fallback_paths(self, settings: Settings, fallback_public_directory: str,
|
365
|
+
fallback_file_contents: bytes):
|
366
|
+
"""Test multiple fallback paths (no origin specified)."""
|
367
|
+
# Create request for unknown origin
|
368
|
+
status = RequestBuilder(
|
369
|
+
method='GET',
|
370
|
+
request_headers={'accept': 'text/html'},
|
371
|
+
response_body='',
|
372
|
+
status_code=200,
|
373
|
+
url='https://unknown.com',
|
374
|
+
).with_settings(settings).build()[1]
|
375
|
+
assert status == 200
|
376
|
+
|
377
|
+
unknown_request = Request.last()
|
378
|
+
unknown_mitmproxy_request = MitmproxyRequestAdapter(unknown_request).adapt()
|
379
|
+
|
380
|
+
# Multiple fallback paths (no origin specified)
|
381
|
+
public_paths = f"/nonexistent/path,{fallback_public_directory}"
|
382
|
+
|
383
|
+
res: requests.Response = eval_fixtures(unknown_mitmproxy_request, public_directory_path=public_paths)
|
384
|
+
assert res is not None
|
385
|
+
assert res.raw.read() == fallback_file_contents
|
386
|
+
|
387
|
+
def test_full_url_origin_parsing_public_directories(self, settings: Settings):
|
388
|
+
"""Test parsing of full URL origins (scheme://hostname:port) for public directories."""
|
389
|
+
# Create test directories
|
390
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
391
|
+
api_dir = os.path.join(tmp_dir_path, 'full_url_api')
|
392
|
+
fallback_dir = os.path.join(tmp_dir_path, 'full_url_fallback')
|
393
|
+
os.makedirs(api_dir, exist_ok=True)
|
394
|
+
os.makedirs(fallback_dir, exist_ok=True)
|
395
|
+
|
396
|
+
# Create test files
|
397
|
+
api_file = os.path.join(api_dir, 'index.html')
|
398
|
+
fallback_file = os.path.join(fallback_dir, 'index.html')
|
399
|
+
|
400
|
+
with open(api_file, 'w') as f:
|
401
|
+
f.write('API Content')
|
402
|
+
with open(fallback_file, 'w') as f:
|
403
|
+
f.write('Fallback Content')
|
404
|
+
|
405
|
+
# Test full URL with port
|
406
|
+
status = RequestBuilder(
|
407
|
+
method='GET',
|
408
|
+
request_headers={'accept': 'text/html'},
|
409
|
+
response_body='',
|
410
|
+
status_code=200,
|
411
|
+
url='https://api.example.com:8080/',
|
412
|
+
).with_settings(settings).build()[1]
|
413
|
+
assert status == 200
|
414
|
+
|
415
|
+
request = Request.last()
|
416
|
+
mitmproxy_request = MitmproxyRequestAdapter(request).adapt()
|
417
|
+
|
418
|
+
# Test with regex pattern for scheme:hostname:port (request origin is https://api.example.com:8080)
|
419
|
+
public_paths = f"{api_dir}:https://api\\.example\\.com:8080,{fallback_dir}"
|
420
|
+
|
421
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, public_directory_path=public_paths)
|
422
|
+
assert res is not None
|
423
|
+
assert res.raw.read() == b'API Content'
|
424
|
+
|
425
|
+
def test_hostname_port_origin_parsing_public_directories(self, settings: Settings):
|
426
|
+
"""Test parsing of hostname:port origins for public directories."""
|
427
|
+
# Create test directories
|
428
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
429
|
+
api_dir = os.path.join(tmp_dir_path, 'hostname_port_api')
|
430
|
+
fallback_dir = os.path.join(tmp_dir_path, 'hostname_port_fallback')
|
431
|
+
os.makedirs(api_dir, exist_ok=True)
|
432
|
+
os.makedirs(fallback_dir, exist_ok=True)
|
433
|
+
|
434
|
+
# Create test files
|
435
|
+
api_file = os.path.join(api_dir, 'index.html')
|
436
|
+
fallback_file = os.path.join(fallback_dir, 'index.html')
|
437
|
+
|
438
|
+
with open(api_file, 'w') as f:
|
439
|
+
f.write('Hostname Port Content')
|
440
|
+
with open(fallback_file, 'w') as f:
|
441
|
+
f.write('Fallback Content')
|
442
|
+
|
443
|
+
# Test hostname:port format
|
444
|
+
status = RequestBuilder(
|
445
|
+
method='GET',
|
446
|
+
request_headers={'accept': 'text/html'},
|
447
|
+
response_body='',
|
448
|
+
status_code=200,
|
449
|
+
url='https://api.example.com:9000/',
|
450
|
+
).with_settings(settings).build()[1]
|
451
|
+
assert status == 200
|
452
|
+
|
453
|
+
request = Request.last()
|
454
|
+
mitmproxy_request = MitmproxyRequestAdapter(request).adapt()
|
455
|
+
|
456
|
+
# Test with scheme:hostname:port origin format
|
457
|
+
public_paths = f"{api_dir}:https://api\\.example\\.com:9000,{fallback_dir}"
|
458
|
+
|
459
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, public_directory_path=public_paths)
|
460
|
+
assert res is not None
|
461
|
+
assert res.raw.read() == b'Hostname Port Content'
|
462
|
+
|
463
|
+
class TestMultipleResponseFixtures():
|
464
|
+
@pytest.fixture(scope='class')
|
465
|
+
def request_method(self):
|
466
|
+
return 'GET'
|
467
|
+
|
468
|
+
@pytest.fixture(scope='class')
|
469
|
+
def api_request_url(self):
|
470
|
+
return 'https://api.example.com/users'
|
471
|
+
|
472
|
+
@pytest.fixture(scope='class')
|
473
|
+
def main_request_url(self):
|
474
|
+
return 'https://petstore.swagger.io/users'
|
475
|
+
|
476
|
+
@pytest.fixture(scope='class')
|
477
|
+
def api_created_request(self, settings: Settings, request_method: str, api_request_url: str):
|
478
|
+
status = RequestBuilder(
|
479
|
+
method=request_method,
|
480
|
+
request_headers={'accept': 'application/json'},
|
481
|
+
response_body='',
|
482
|
+
status_code=200,
|
483
|
+
url=api_request_url,
|
484
|
+
).with_settings(settings).build()[1]
|
485
|
+
assert status == 200
|
486
|
+
return Request.last()
|
487
|
+
|
488
|
+
@pytest.fixture(scope='class')
|
489
|
+
def main_created_request(self, settings: Settings, request_method: str, main_request_url: str):
|
490
|
+
status = RequestBuilder(
|
491
|
+
method=request_method,
|
492
|
+
request_headers={'accept': 'application/json'},
|
493
|
+
response_body='',
|
494
|
+
status_code=200,
|
495
|
+
url=main_request_url,
|
496
|
+
).with_settings(settings).build()[1]
|
497
|
+
assert status == 200
|
498
|
+
return Request.last()
|
499
|
+
|
500
|
+
@pytest.fixture()
|
501
|
+
def api_mitmproxy_request(self, api_created_request: Request) -> MitmproxyRequest:
|
502
|
+
return MitmproxyRequestAdapter(api_created_request).adapt()
|
503
|
+
|
504
|
+
@pytest.fixture()
|
505
|
+
def main_mitmproxy_request(self, main_created_request: Request) -> MitmproxyRequest:
|
506
|
+
return MitmproxyRequestAdapter(main_created_request).adapt()
|
507
|
+
|
508
|
+
@pytest.fixture(scope='class')
|
509
|
+
def api_fixture_file_path(self):
|
510
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
511
|
+
file_path = os.path.join(tmp_dir_path, 'api_users.json')
|
512
|
+
with open(file_path, 'w') as fp:
|
513
|
+
json.dump({"users": [{"id": 1, "name": "API User"}]}, fp)
|
514
|
+
return file_path
|
515
|
+
|
516
|
+
@pytest.fixture(scope='class')
|
517
|
+
def main_fixture_file_path(self):
|
518
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
519
|
+
file_path = os.path.join(tmp_dir_path, 'main_users.json')
|
520
|
+
with open(file_path, 'w') as fp:
|
521
|
+
json.dump({"users": [{"id": 1, "name": "Main User"}]}, fp)
|
522
|
+
return file_path
|
523
|
+
|
524
|
+
@pytest.fixture(scope='class')
|
525
|
+
def fallback_fixture_file_path(self):
|
526
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
527
|
+
file_path = os.path.join(tmp_dir_path, 'fallback_users.json')
|
528
|
+
with open(file_path, 'w') as fp:
|
529
|
+
json.dump({"users": [{"id": 1, "name": "Fallback User"}]}, fp)
|
530
|
+
return file_path
|
531
|
+
|
532
|
+
@pytest.fixture(scope='class')
|
533
|
+
def origin_specific_fixtures(self, api_fixture_file_path: str, main_fixture_file_path: str):
|
534
|
+
"""Response fixtures with origin-specific routing."""
|
535
|
+
return {
|
536
|
+
'_origins': {
|
537
|
+
'api.example.com': {
|
538
|
+
'GET': {
|
539
|
+
'/users': {
|
540
|
+
'path': api_fixture_file_path,
|
541
|
+
'status_code': 200
|
542
|
+
}
|
543
|
+
}
|
544
|
+
},
|
545
|
+
'petstore.swagger.io': {
|
546
|
+
'GET': {
|
547
|
+
'/users': {
|
548
|
+
'path': main_fixture_file_path,
|
549
|
+
'status_code': 200
|
550
|
+
}
|
551
|
+
}
|
552
|
+
}
|
553
|
+
}
|
554
|
+
}
|
555
|
+
|
556
|
+
@pytest.fixture(scope='class')
|
557
|
+
def mixed_fixtures(self, api_fixture_file_path: str, fallback_fixture_file_path: str):
|
558
|
+
"""Response fixtures with both origin-specific and fallback routes."""
|
559
|
+
return {
|
560
|
+
'_origins': {
|
561
|
+
'api.example.com': {
|
562
|
+
'GET': {
|
563
|
+
'/users': {
|
564
|
+
'path': api_fixture_file_path,
|
565
|
+
'status_code': 200
|
566
|
+
}
|
567
|
+
}
|
568
|
+
}
|
569
|
+
},
|
570
|
+
'GET': {
|
571
|
+
'/users': {
|
572
|
+
'path': fallback_fixture_file_path,
|
573
|
+
'status_code': 200
|
574
|
+
}
|
575
|
+
}
|
576
|
+
}
|
577
|
+
|
578
|
+
def test_origin_specific_routing(self, api_mitmproxy_request: MitmproxyRequest,
|
579
|
+
api_fixture_file_path: str, main_fixture_file_path: str):
|
580
|
+
"""Test that origin-specific fixtures are used when origin matches."""
|
581
|
+
# Create separate fixture files for each origin
|
582
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
583
|
+
api_fixtures_file = os.path.join(tmp_dir_path, 'api_origin_fixtures.yml')
|
584
|
+
main_fixtures_file = os.path.join(tmp_dir_path, 'main_origin_fixtures.yml')
|
585
|
+
|
586
|
+
# API-specific fixtures
|
587
|
+
api_fixtures_content = {
|
588
|
+
'GET': {
|
589
|
+
'/users': {
|
590
|
+
'path': api_fixture_file_path,
|
591
|
+
'status_code': 200
|
592
|
+
}
|
593
|
+
}
|
594
|
+
}
|
595
|
+
|
596
|
+
# Main-specific fixtures
|
597
|
+
main_fixtures_content = {
|
598
|
+
'GET': {
|
599
|
+
'/users': {
|
600
|
+
'path': main_fixture_file_path,
|
601
|
+
'status_code': 200
|
602
|
+
}
|
603
|
+
}
|
604
|
+
}
|
605
|
+
|
606
|
+
with open(api_fixtures_file, 'w') as f:
|
607
|
+
yaml.dump(api_fixtures_content, f)
|
608
|
+
|
609
|
+
with open(main_fixtures_file, 'w') as f:
|
610
|
+
yaml.dump(main_fixtures_content, f)
|
611
|
+
|
612
|
+
# Use response_fixtures_path with origin specification
|
613
|
+
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com,{main_fixtures_file}:https://petstore\\.swagger\\.io"
|
614
|
+
|
615
|
+
res: requests.Response = eval_fixtures(api_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
616
|
+
assert res is not None
|
617
|
+
assert res.status_code == 200
|
618
|
+
|
619
|
+
# Check that API-specific content is returned
|
620
|
+
content = json.loads(res.raw.read().decode())
|
621
|
+
assert content['users'][0]['name'] == 'API User'
|
622
|
+
|
623
|
+
def test_different_origin_routing(self, main_mitmproxy_request: MitmproxyRequest,
|
624
|
+
api_fixture_file_path: str, main_fixture_file_path: str):
|
625
|
+
"""Test that different origin gets different fixtures."""
|
626
|
+
# Create separate fixture files for each origin
|
627
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
628
|
+
api_fixtures_file = os.path.join(tmp_dir_path, 'api_diff_fixtures.yml')
|
629
|
+
main_fixtures_file = os.path.join(tmp_dir_path, 'main_diff_fixtures.yml')
|
630
|
+
|
631
|
+
# API-specific fixtures
|
632
|
+
api_fixtures_content = {
|
633
|
+
'GET': {
|
634
|
+
'/users': {
|
635
|
+
'path': api_fixture_file_path,
|
636
|
+
'status_code': 200
|
637
|
+
}
|
638
|
+
}
|
639
|
+
}
|
640
|
+
|
641
|
+
# Main-specific fixtures
|
642
|
+
main_fixtures_content = {
|
643
|
+
'GET': {
|
644
|
+
'/users': {
|
645
|
+
'path': main_fixture_file_path,
|
646
|
+
'status_code': 200
|
647
|
+
}
|
648
|
+
}
|
649
|
+
}
|
650
|
+
|
651
|
+
with open(api_fixtures_file, 'w') as f:
|
652
|
+
yaml.dump(api_fixtures_content, f)
|
653
|
+
|
654
|
+
with open(main_fixtures_file, 'w') as f:
|
655
|
+
yaml.dump(main_fixtures_content, f)
|
656
|
+
|
657
|
+
# Use response_fixtures_path with origin specification
|
658
|
+
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com,{main_fixtures_file}:https://petstore\\.swagger\\.io"
|
659
|
+
|
660
|
+
res: requests.Response = eval_fixtures(main_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
661
|
+
assert res is not None
|
662
|
+
assert res.status_code == 200
|
663
|
+
|
664
|
+
# Check that main-specific content is returned
|
665
|
+
content = json.loads(res.raw.read().decode())
|
666
|
+
assert content['users'][0]['name'] == 'Main User'
|
667
|
+
|
668
|
+
def test_fallback_routing(self, main_mitmproxy_request: MitmproxyRequest, fallback_fixture_file_path: str):
|
669
|
+
"""Test that fallback fixtures are used when no origin-specific match."""
|
670
|
+
# Create a fallback-only fixtures file
|
671
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
672
|
+
fallback_fixtures_file = os.path.join(tmp_dir_path, 'fallback_only_fixtures.yml')
|
673
|
+
|
674
|
+
fallback_fixtures_content = {
|
675
|
+
'GET': {
|
676
|
+
'/users': {
|
677
|
+
'path': fallback_fixture_file_path,
|
678
|
+
'status_code': 200
|
679
|
+
}
|
680
|
+
}
|
681
|
+
}
|
682
|
+
|
683
|
+
with open(fallback_fixtures_file, 'w') as f:
|
684
|
+
yaml.dump(fallback_fixtures_content, f)
|
685
|
+
|
686
|
+
# Use a fixture file without origin specification (fallback)
|
687
|
+
res: requests.Response = eval_fixtures(main_mitmproxy_request, response_fixtures_path=fallback_fixtures_file)
|
688
|
+
assert res is not None
|
689
|
+
assert res.status_code == 200
|
690
|
+
|
691
|
+
# Check that fallback content is returned
|
692
|
+
content = json.loads(res.raw.read().decode())
|
693
|
+
assert content['users'][0]['name'] == 'Fallback User'
|
694
|
+
|
695
|
+
def test_origin_match_takes_precedence(self, api_mitmproxy_request: MitmproxyRequest,
|
696
|
+
api_fixture_file_path: str, fallback_fixture_file_path: str):
|
697
|
+
"""Test that origin-specific fixtures take precedence over fallback."""
|
698
|
+
# Create separate fixture files
|
699
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
700
|
+
api_fixtures_file = os.path.join(tmp_dir_path, 'api_precedence_fixtures.yml')
|
701
|
+
fallback_fixtures_file = os.path.join(tmp_dir_path, 'fallback_precedence_fixtures.yml')
|
702
|
+
|
703
|
+
# API-specific fixtures
|
704
|
+
api_fixtures_content = {
|
705
|
+
'GET': {
|
706
|
+
'/users': {
|
707
|
+
'path': api_fixture_file_path,
|
708
|
+
'status_code': 200
|
709
|
+
}
|
710
|
+
}
|
711
|
+
}
|
712
|
+
|
713
|
+
# Fallback fixtures
|
714
|
+
fallback_fixtures_content = {
|
715
|
+
'GET': {
|
716
|
+
'/users': {
|
717
|
+
'path': fallback_fixture_file_path,
|
718
|
+
'status_code': 200
|
719
|
+
}
|
720
|
+
}
|
721
|
+
}
|
722
|
+
|
723
|
+
with open(api_fixtures_file, 'w') as f:
|
724
|
+
yaml.dump(api_fixtures_content, f)
|
725
|
+
|
726
|
+
with open(fallback_fixtures_file, 'w') as f:
|
727
|
+
yaml.dump(fallback_fixtures_content, f)
|
728
|
+
|
729
|
+
# Use response_fixtures_path with origin-specific first, fallback second
|
730
|
+
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com,{fallback_fixtures_file}"
|
731
|
+
|
732
|
+
res: requests.Response = eval_fixtures(api_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
733
|
+
assert res is not None
|
734
|
+
assert res.status_code == 200
|
735
|
+
|
736
|
+
# Check that API-specific content is returned, not fallback
|
737
|
+
content = json.loads(res.raw.read().decode())
|
738
|
+
assert content['users'][0]['name'] == 'API User'
|
739
|
+
|
740
|
+
def test_wildcard_origin_matching(self, settings: Settings, api_fixture_file_path: str):
|
741
|
+
"""Test wildcard origin matching for subdomains."""
|
742
|
+
# Create request for subdomain
|
743
|
+
status = RequestBuilder(
|
744
|
+
method='GET',
|
745
|
+
request_headers={'accept': 'application/json'},
|
746
|
+
response_body='',
|
747
|
+
status_code=200,
|
748
|
+
url='https://sub.example.com/users',
|
749
|
+
).with_settings(settings).build()[1]
|
750
|
+
assert status == 200
|
751
|
+
|
752
|
+
subdomain_request = Request.last()
|
753
|
+
subdomain_mitmproxy_request = MitmproxyRequestAdapter(subdomain_request).adapt()
|
754
|
+
|
755
|
+
# Create wildcard fixtures file
|
756
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
757
|
+
wildcard_fixtures_file = os.path.join(tmp_dir_path, 'wildcard_fixtures.yml')
|
758
|
+
|
759
|
+
wildcard_fixtures_content = {
|
760
|
+
'GET': {
|
761
|
+
'/users': {
|
762
|
+
'path': api_fixture_file_path,
|
763
|
+
'status_code': 200
|
764
|
+
}
|
765
|
+
}
|
766
|
+
}
|
767
|
+
|
768
|
+
with open(wildcard_fixtures_file, 'w') as f:
|
769
|
+
yaml.dump(wildcard_fixtures_content, f)
|
770
|
+
|
771
|
+
# Use regex wildcard pattern in response_fixtures_path
|
772
|
+
fixtures_paths = f"{wildcard_fixtures_file}:https://.*\\.example\\.com"
|
773
|
+
|
774
|
+
res: requests.Response = eval_fixtures(subdomain_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
775
|
+
assert res is not None
|
776
|
+
assert res.status_code == 200
|
777
|
+
|
778
|
+
# Check that API content is returned for wildcard match
|
779
|
+
content = json.loads(res.raw.read().decode())
|
780
|
+
assert content['users'][0]['name'] == 'API User'
|
781
|
+
|
782
|
+
def test_response_fixtures_path_loading(self, api_mitmproxy_request: MitmproxyRequest, api_fixture_file_path: str):
|
783
|
+
"""Test that response fixtures are loaded from response_fixtures_path parameter."""
|
784
|
+
# Create a temporary fixtures file
|
785
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
786
|
+
fixtures_file_path = os.path.join(tmp_dir_path, 'test_fixtures.yml')
|
787
|
+
|
788
|
+
fixtures_content = {
|
789
|
+
'GET': {
|
790
|
+
'/users': {
|
791
|
+
'path': api_fixture_file_path,
|
792
|
+
'status_code': 200
|
793
|
+
}
|
794
|
+
}
|
795
|
+
}
|
796
|
+
|
797
|
+
with open(fixtures_file_path, 'w') as fp:
|
798
|
+
yaml.dump(fixtures_content, fp)
|
799
|
+
|
800
|
+
# Test loading fixtures via response_fixtures_path
|
801
|
+
res: requests.Response = eval_fixtures(api_mitmproxy_request, response_fixtures_path=fixtures_file_path)
|
802
|
+
assert res is not None
|
803
|
+
assert res.status_code == 200
|
804
|
+
|
805
|
+
# Check that correct content is returned
|
806
|
+
content = json.loads(res.raw.read().decode())
|
807
|
+
assert content['users'][0]['name'] == 'API User'
|
808
|
+
|
809
|
+
def test_response_fixtures_path_with_origin(self, api_mitmproxy_request: MitmproxyRequest,
|
810
|
+
main_mitmproxy_request: MitmproxyRequest,
|
811
|
+
api_fixture_file_path: str, main_fixture_file_path: str):
|
812
|
+
"""Test that response_fixtures_path supports origin-specific routing."""
|
813
|
+
# Create temporary fixtures files
|
814
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
815
|
+
api_fixtures_file = os.path.join(tmp_dir_path, 'api_fixtures.yml')
|
816
|
+
main_fixtures_file = os.path.join(tmp_dir_path, 'main_fixtures.yml')
|
817
|
+
|
818
|
+
api_fixtures_content = {
|
819
|
+
'GET': {
|
820
|
+
'/users': {
|
821
|
+
'path': api_fixture_file_path,
|
822
|
+
'status_code': 200
|
823
|
+
}
|
824
|
+
}
|
825
|
+
}
|
826
|
+
|
827
|
+
main_fixtures_content = {
|
828
|
+
'GET': {
|
829
|
+
'/users': {
|
830
|
+
'path': main_fixture_file_path,
|
831
|
+
'status_code': 200
|
832
|
+
}
|
833
|
+
}
|
834
|
+
}
|
835
|
+
|
836
|
+
with open(api_fixtures_file, 'w') as fp:
|
837
|
+
yaml.dump(api_fixtures_content, fp)
|
838
|
+
|
839
|
+
with open(main_fixtures_file, 'w') as fp:
|
840
|
+
yaml.dump(main_fixtures_content, fp)
|
841
|
+
|
842
|
+
# Test with comma-separated paths with origins
|
843
|
+
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com,{main_fixtures_file}:https://petstore\\.swagger\\.io"
|
844
|
+
|
845
|
+
# Test API request
|
846
|
+
api_res: requests.Response = eval_fixtures(api_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
847
|
+
assert api_res is not None
|
848
|
+
assert api_res.status_code == 200
|
849
|
+
api_content = json.loads(api_res.raw.read().decode())
|
850
|
+
assert api_content['users'][0]['name'] == 'API User'
|
851
|
+
|
852
|
+
# Test main request
|
853
|
+
main_res: requests.Response = eval_fixtures(main_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
854
|
+
assert main_res is not None
|
855
|
+
assert main_res.status_code == 200
|
856
|
+
main_content = json.loads(main_res.raw.read().decode())
|
857
|
+
assert main_content['users'][0]['name'] == 'Main User'
|
858
|
+
|
859
|
+
def test_lazy_evaluation_stops_at_first_match(self, api_mitmproxy_request: MitmproxyRequest, api_fixture_file_path: str):
|
860
|
+
"""Test that lazy evaluation stops at the first matching fixture and doesn't process subsequent files."""
|
861
|
+
# Create multiple fixtures files
|
862
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
863
|
+
first_fixtures_file = os.path.join(tmp_dir_path, 'first_fixtures.yml')
|
864
|
+
second_fixtures_file = os.path.join(tmp_dir_path, 'second_fixtures.yml')
|
865
|
+
invalid_fixtures_file = os.path.join(tmp_dir_path, 'invalid_fixtures.yml')
|
866
|
+
|
867
|
+
# First file - should match and be used
|
868
|
+
first_fixtures_content = {
|
869
|
+
'GET': {
|
870
|
+
'/users': {
|
871
|
+
'path': api_fixture_file_path,
|
872
|
+
'status_code': 201 # Different status to verify this file is used
|
873
|
+
}
|
874
|
+
}
|
875
|
+
}
|
876
|
+
|
877
|
+
# Second file - should not be processed since first file matches
|
878
|
+
second_fixtures_content = {
|
879
|
+
'GET': {
|
880
|
+
'/users': {
|
881
|
+
'path': api_fixture_file_path,
|
882
|
+
'status_code': 202
|
883
|
+
}
|
884
|
+
}
|
885
|
+
}
|
886
|
+
|
887
|
+
with open(first_fixtures_file, 'w') as fp:
|
888
|
+
yaml.dump(first_fixtures_content, fp)
|
889
|
+
|
890
|
+
with open(second_fixtures_file, 'w') as fp:
|
891
|
+
yaml.dump(second_fixtures_content, fp)
|
892
|
+
|
893
|
+
# Create an invalid YAML file that would cause an error if processed
|
894
|
+
with open(invalid_fixtures_file, 'w') as fp:
|
895
|
+
fp.write("invalid: yaml: content: [unclosed")
|
896
|
+
|
897
|
+
# Test with multiple paths - first should match, others should be ignored
|
898
|
+
fixtures_paths = f"{first_fixtures_file}:https://api\\.example\\.com,{second_fixtures_file}:https://api\\.example\\.com,{invalid_fixtures_file}:https://api\\.example\\.com"
|
899
|
+
|
900
|
+
# Should use first file and return status 201
|
901
|
+
res: requests.Response = eval_fixtures(api_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
902
|
+
assert res is not None
|
903
|
+
assert res.status_code == 201 # Should be from first file, not second
|
904
|
+
|
905
|
+
# Verify content is from the expected file
|
906
|
+
content = json.loads(res.raw.read().decode())
|
907
|
+
assert content['users'][0]['name'] == 'API User'
|
908
|
+
|
909
|
+
def test_origin_mismatch_skips_file(self, api_mitmproxy_request: MitmproxyRequest,
|
910
|
+
main_mitmproxy_request: MitmproxyRequest,
|
911
|
+
api_fixture_file_path: str, main_fixture_file_path: str):
|
912
|
+
"""Test that files with non-matching origins are skipped without being loaded."""
|
913
|
+
# Create fixtures files
|
914
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
915
|
+
wrong_origin_file = os.path.join(tmp_dir_path, 'wrong_origin.yml')
|
916
|
+
correct_origin_file = os.path.join(tmp_dir_path, 'correct_origin.yml')
|
917
|
+
|
918
|
+
# File with wrong origin - should be skipped
|
919
|
+
wrong_origin_content = {
|
920
|
+
'GET': {
|
921
|
+
'/users': {
|
922
|
+
'path': main_fixture_file_path, # Different content
|
923
|
+
'status_code': 404 # Different status to verify this isn't used
|
924
|
+
}
|
925
|
+
}
|
926
|
+
}
|
927
|
+
|
928
|
+
# File with correct origin - should be used
|
929
|
+
correct_origin_content = {
|
930
|
+
'GET': {
|
931
|
+
'/users': {
|
932
|
+
'path': api_fixture_file_path,
|
933
|
+
'status_code': 200
|
934
|
+
}
|
935
|
+
}
|
936
|
+
}
|
937
|
+
|
938
|
+
with open(wrong_origin_file, 'w') as fp:
|
939
|
+
yaml.dump(wrong_origin_content, fp)
|
940
|
+
|
941
|
+
with open(correct_origin_file, 'w') as fp:
|
942
|
+
yaml.dump(correct_origin_content, fp)
|
943
|
+
|
944
|
+
# Test with API request - wrong origin should be skipped, correct origin should be used
|
945
|
+
fixtures_paths = f"{wrong_origin_file}:https://petstore\\.swagger\\.io,{correct_origin_file}:https://api\\.example\\.com"
|
946
|
+
|
947
|
+
res: requests.Response = eval_fixtures(api_mitmproxy_request, response_fixtures_path=fixtures_paths)
|
948
|
+
assert res is not None
|
949
|
+
assert res.status_code == 200 # Should be from correct origin file, not wrong origin
|
950
|
+
|
951
|
+
# Verify content is from the API file (correct origin), not main file (wrong origin)
|
952
|
+
content = json.loads(res.raw.read().decode())
|
953
|
+
assert content['users'][0]['name'] == 'API User'
|
954
|
+
|
955
|
+
class TestFullOriginFormatParsing():
|
956
|
+
@pytest.fixture(scope='class')
|
957
|
+
def tmp_dir_path(self):
|
958
|
+
return DataDir.instance().tmp_dir_path
|
959
|
+
|
960
|
+
def test_full_url_origin_parsing_response_fixtures(self, settings: Settings, tmp_dir_path: str):
|
961
|
+
"""Test parsing of full URL origins for response fixtures."""
|
962
|
+
# Create test fixture files
|
963
|
+
api_fixture_file = os.path.join(tmp_dir_path, 'full_url_api_fixture.json')
|
964
|
+
with open(api_fixture_file, 'w') as f:
|
965
|
+
json.dump({"message": "Full URL API Response"}, f)
|
966
|
+
|
967
|
+
api_fixtures_file = os.path.join(tmp_dir_path, 'full_url_api_fixtures.yml')
|
968
|
+
fixtures_content = {
|
969
|
+
'GET': {
|
970
|
+
'/api/data': {
|
971
|
+
'path': api_fixture_file,
|
972
|
+
'status_code': 200
|
973
|
+
}
|
974
|
+
}
|
975
|
+
}
|
976
|
+
with open(api_fixtures_file, 'w') as f:
|
977
|
+
yaml.dump(fixtures_content, f)
|
978
|
+
|
979
|
+
# Test full URL with port
|
980
|
+
status = RequestBuilder(
|
981
|
+
method='GET',
|
982
|
+
request_headers={'accept': 'application/json'},
|
983
|
+
response_body='',
|
984
|
+
status_code=200,
|
985
|
+
url='https://api.example.com:8080/api/data',
|
986
|
+
).with_settings(settings).build()[1]
|
987
|
+
assert status == 200
|
988
|
+
|
989
|
+
request = Request.last()
|
990
|
+
mitmproxy_request = MitmproxyRequestAdapter(request).adapt()
|
991
|
+
|
992
|
+
# Test with regex pattern for scheme:hostname:port (request origin is https://api.example.com:8080)
|
993
|
+
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com:8080"
|
994
|
+
|
995
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures_path=fixtures_paths)
|
996
|
+
assert res is not None
|
997
|
+
assert res.status_code == 200
|
998
|
+
content = json.loads(res.raw.read().decode())
|
999
|
+
assert content['message'] == 'Full URL API Response'
|
1000
|
+
|
1001
|
+
def test_origin_matching_with_different_schemes(self, settings: Settings, tmp_dir_path: str):
|
1002
|
+
"""Test that origin matching works regardless of scheme differences."""
|
1003
|
+
# Create test fixture files
|
1004
|
+
api_fixture_file = os.path.join(tmp_dir_path, 'scheme_test_fixture.json')
|
1005
|
+
with open(api_fixture_file, 'w') as f:
|
1006
|
+
json.dump({"message": "Scheme Test Response"}, f)
|
1007
|
+
|
1008
|
+
api_fixtures_file = os.path.join(tmp_dir_path, 'scheme_test_fixtures.yml')
|
1009
|
+
fixtures_content = {
|
1010
|
+
'GET': {
|
1011
|
+
'/test': {
|
1012
|
+
'path': api_fixture_file,
|
1013
|
+
'status_code': 200
|
1014
|
+
}
|
1015
|
+
}
|
1016
|
+
}
|
1017
|
+
with open(api_fixtures_file, 'w') as f:
|
1018
|
+
yaml.dump(fixtures_content, f)
|
1019
|
+
|
1020
|
+
# Test HTTP request
|
1021
|
+
status = RequestBuilder(
|
1022
|
+
method='GET',
|
1023
|
+
request_headers={'accept': 'application/json'},
|
1024
|
+
response_body='',
|
1025
|
+
status_code=200,
|
1026
|
+
url='http://api.example.com:8080/test', # HTTP request
|
1027
|
+
).with_settings(settings).build()[1]
|
1028
|
+
assert status == 200
|
1029
|
+
|
1030
|
+
request = Request.last()
|
1031
|
+
mitmproxy_request = MitmproxyRequestAdapter(request).adapt()
|
1032
|
+
|
1033
|
+
# Test with regex pattern for scheme:hostname:port (request origin is http://api.example.com:8080)
|
1034
|
+
fixtures_paths = f"{api_fixtures_file}:http://api\\.example\\.com:8080"
|
1035
|
+
|
1036
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures_path=fixtures_paths)
|
1037
|
+
assert res is not None
|
1038
|
+
assert res.status_code == 200
|
1039
|
+
content = json.loads(res.raw.read().decode())
|
1040
|
+
assert content['message'] == 'Scheme Test Response'
|
1041
|
+
|
1042
|
+
def test_wildcard_matching_with_full_origins(self, settings: Settings, tmp_dir_path: str):
|
1043
|
+
"""Test wildcard matching with full origin URLs."""
|
1044
|
+
# Create test fixture files
|
1045
|
+
wildcard_fixture_file = os.path.join(tmp_dir_path, 'wildcard_test_fixture.json')
|
1046
|
+
with open(wildcard_fixture_file, 'w') as f:
|
1047
|
+
json.dump({"message": "Wildcard Match Response"}, f)
|
1048
|
+
|
1049
|
+
wildcard_fixtures_file = os.path.join(tmp_dir_path, 'wildcard_test_fixtures.yml')
|
1050
|
+
fixtures_content = {
|
1051
|
+
'GET': {
|
1052
|
+
'/wildcard': {
|
1053
|
+
'path': wildcard_fixture_file,
|
1054
|
+
'status_code': 200
|
1055
|
+
}
|
1056
|
+
}
|
1057
|
+
}
|
1058
|
+
with open(wildcard_fixtures_file, 'w') as f:
|
1059
|
+
yaml.dump(fixtures_content, f)
|
1060
|
+
|
1061
|
+
# Test subdomain request
|
1062
|
+
status = RequestBuilder(
|
1063
|
+
method='GET',
|
1064
|
+
request_headers={'accept': 'application/json'},
|
1065
|
+
response_body='',
|
1066
|
+
status_code=200,
|
1067
|
+
url='https://sub.example.com:8080/wildcard',
|
1068
|
+
).with_settings(settings).build()[1]
|
1069
|
+
assert status == 200
|
1070
|
+
|
1071
|
+
request = Request.last()
|
1072
|
+
mitmproxy_request = MitmproxyRequestAdapter(request).adapt()
|
1073
|
+
|
1074
|
+
# Test with regex wildcard origin pattern
|
1075
|
+
fixtures_paths = f"{wildcard_fixtures_file}:https://.*\\.example\\.com:8080"
|
1076
|
+
|
1077
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures_path=fixtures_paths)
|
1078
|
+
assert res is not None
|
1079
|
+
assert res.status_code == 200
|
1080
|
+
content = json.loads(res.raw.read().decode())
|
1081
|
+
assert content['message'] == 'Wildcard Match Response'
|