stoobly-agent 0.33.7__py3-none-any.whl → 0.34.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 +1 -1
- stoobly_agent/app/cli/dev_tools_cli.py +1 -0
- stoobly_agent/app/cli/endpoint_cli.py +25 -2
- stoobly_agent/app/cli/helpers/endpoint_facade.py +22 -1
- stoobly_agent/app/cli/helpers/endpoints_import_context.py +57 -0
- stoobly_agent/app/cli/helpers/endpoints_import_service.py +152 -0
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +111 -6
- stoobly_agent/app/cli/helpers/replay_facade.py +4 -0
- stoobly_agent/app/cli/request_cli.py +2 -0
- stoobly_agent/app/cli/scenario_cli.py +2 -0
- stoobly_agent/app/cli/types/test.py +2 -0
- stoobly_agent/app/models/factories/resource/local_db/helpers/request_builder.py +1 -1
- stoobly_agent/app/models/types/endpoint.py +28 -2
- stoobly_agent/app/proxy/handle_mock_service.py +15 -3
- stoobly_agent/app/proxy/intercept_settings.py +39 -0
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +61 -0
- stoobly_agent/app/proxy/mock/types/__init__.py +10 -0
- stoobly_agent/app/proxy/replay/replay_request_service.py +11 -3
- stoobly_agent/app/proxy/run.py +6 -0
- stoobly_agent/app/proxy/test/context.py +12 -0
- stoobly_agent/app/proxy/test/context_abc.py +15 -0
- stoobly_agent/app/settings/intercept_settings.py +1 -1
- stoobly_agent/cli.py +13 -2
- stoobly_agent/config/constants/custom_headers.py +2 -0
- stoobly_agent/config/constants/env_vars.py +2 -0
- stoobly_agent/lib/api/api.py +4 -0
- stoobly_agent/lib/api/body_param_names_resource.py +36 -0
- stoobly_agent/lib/api/endpoints_resource.py +9 -2
- stoobly_agent/lib/api/header_names_resource.py +36 -0
- stoobly_agent/lib/api/interfaces/endpoints.py +2 -0
- stoobly_agent/lib/api/query_param_names_resource.py +36 -0
- stoobly_agent/lib/api/response_header_names_resource.py +36 -0
- stoobly_agent/lib/api/response_param_names_resource.py +36 -0
- stoobly_agent/public/0-es2015.a7e60cafc0868f87a771.js +1 -0
- stoobly_agent/public/0-es5.a7e60cafc0868f87a771.js +1 -0
- stoobly_agent/public/1-es2015.cb071776436f10954db5.js +1 -0
- stoobly_agent/public/1-es5.cb071776436f10954db5.js +1 -0
- stoobly_agent/public/10-es2015.bb1702aaf3968a2cb521.js +1 -0
- stoobly_agent/public/10-es5.bb1702aaf3968a2cb521.js +1 -0
- stoobly_agent/public/12-es2015.591ec692afb8f8d13842.js +1 -0
- stoobly_agent/public/12-es5.591ec692afb8f8d13842.js +1 -0
- stoobly_agent/public/13-es2015.f381e7d6ff361b669e12.js +1 -0
- stoobly_agent/public/13-es5.f381e7d6ff361b669e12.js +1 -0
- stoobly_agent/public/14-es2015.1ffb225a16d6292dbddd.js +1 -0
- stoobly_agent/public/14-es5.1ffb225a16d6292dbddd.js +1 -0
- stoobly_agent/public/15-es2015.2cf39bcaeb0ea2c52297.js +1 -0
- stoobly_agent/public/15-es5.2cf39bcaeb0ea2c52297.js +1 -0
- stoobly_agent/public/16-es2015.0e9422b274e642f95e41.js +1 -0
- stoobly_agent/public/16-es5.0e9422b274e642f95e41.js +1 -0
- stoobly_agent/public/17-es2015.7fb08367a22d1e34aed7.js +1 -0
- stoobly_agent/public/17-es5.7fb08367a22d1e34aed7.js +1 -0
- stoobly_agent/public/18-es2015.5caa4789d1c335e628f2.js +1 -0
- stoobly_agent/public/18-es5.5caa4789d1c335e628f2.js +1 -0
- stoobly_agent/public/19-es2015.8049aef58c329c500f9b.js +1 -0
- stoobly_agent/public/19-es5.8049aef58c329c500f9b.js +1 -0
- stoobly_agent/public/2-es2015.8f184ac63348ba447b1f.js +1 -0
- stoobly_agent/public/2-es5.8f184ac63348ba447b1f.js +1 -0
- stoobly_agent/public/20-es2015.473486aabfa4d4a6431b.js +1 -0
- stoobly_agent/public/20-es5.473486aabfa4d4a6431b.js +1 -0
- stoobly_agent/public/21-es2015.63ed4e6b242fbc047bd6.js +1 -0
- stoobly_agent/public/21-es5.63ed4e6b242fbc047bd6.js +1 -0
- stoobly_agent/public/22-es2015.df183804415330639987.js +1 -0
- stoobly_agent/public/22-es5.df183804415330639987.js +1 -0
- stoobly_agent/public/23-es2015.0da63b056fabde91bb0b.js +1 -0
- stoobly_agent/public/23-es5.0da63b056fabde91bb0b.js +1 -0
- stoobly_agent/public/28-es2015.9fe030e9d3b0e52239aa.js +1 -0
- stoobly_agent/public/28-es5.9fe030e9d3b0e52239aa.js +1 -0
- stoobly_agent/public/29-es2015.9b440f22de725732e5ab.js +1 -0
- stoobly_agent/public/29-es5.9b440f22de725732e5ab.js +1 -0
- stoobly_agent/public/30-es2015.6ad2a5126b0a27c1e7c6.js +1 -0
- stoobly_agent/public/30-es5.6ad2a5126b0a27c1e7c6.js +1 -0
- stoobly_agent/public/31-es2015.f4291150f35d54ff19ca.js +1 -0
- stoobly_agent/public/31-es5.f4291150f35d54ff19ca.js +1 -0
- stoobly_agent/public/32-es2015.c8f6ccb43bdba0adf199.js +1 -0
- stoobly_agent/public/32-es5.c8f6ccb43bdba0adf199.js +1 -0
- stoobly_agent/public/33-es2015.f985e93402ebf86322ef.js +1 -0
- stoobly_agent/public/33-es5.f985e93402ebf86322ef.js +1 -0
- stoobly_agent/public/34-es2015.0e1961fb3cf649a52d49.js +1 -0
- stoobly_agent/public/34-es5.0e1961fb3cf649a52d49.js +1 -0
- stoobly_agent/public/35-es2015.a4ae56a89c0324397624.js +1 -0
- stoobly_agent/public/35-es5.a4ae56a89c0324397624.js +1 -0
- stoobly_agent/public/36-es2015.b8fdd25590a79c820119.js +1 -0
- stoobly_agent/public/36-es5.b8fdd25590a79c820119.js +1 -0
- stoobly_agent/public/37-es2015.6567a0ce4cf87ad7287b.js +1 -0
- stoobly_agent/public/37-es5.6567a0ce4cf87ad7287b.js +1 -0
- stoobly_agent/public/38-es2015.6f6d751bff41d84d727a.js +1 -0
- stoobly_agent/public/38-es5.6f6d751bff41d84d727a.js +1 -0
- stoobly_agent/public/39-es2015.47f63177e7d4509a22fa.js +1 -0
- stoobly_agent/public/39-es5.47f63177e7d4509a22fa.js +1 -0
- stoobly_agent/public/3rdpartylicenses.txt +2418 -0
- stoobly_agent/public/4-es2015.182e1ce1811ef67571fb.js +1 -0
- stoobly_agent/public/4-es5.182e1ce1811ef67571fb.js +1 -0
- stoobly_agent/public/40-es2015.5333067cdc4077c7495a.js +1 -0
- stoobly_agent/public/40-es5.5333067cdc4077c7495a.js +1 -0
- stoobly_agent/public/41-es2015.7a2434380c81c11ff2c5.js +1 -0
- stoobly_agent/public/41-es5.7a2434380c81c11ff2c5.js +1 -0
- stoobly_agent/public/42-es2015.5dde662fe1e3b4e4bdd1.js +1 -0
- stoobly_agent/public/42-es5.5dde662fe1e3b4e4bdd1.js +1 -0
- stoobly_agent/public/43-es2015.608e917d689b9bb762cb.js +1 -0
- stoobly_agent/public/43-es5.608e917d689b9bb762cb.js +1 -0
- stoobly_agent/public/44-es2015.2ae5fea15b5e8c2681d3.js +1 -0
- stoobly_agent/public/44-es5.2ae5fea15b5e8c2681d3.js +1 -0
- stoobly_agent/public/45-es2015.e46f228c795174428515.js +1 -0
- stoobly_agent/public/45-es5.e46f228c795174428515.js +1 -0
- stoobly_agent/public/46-es2015.22a0d8e0b4bbfb513741.js +1 -0
- stoobly_agent/public/46-es5.22a0d8e0b4bbfb513741.js +1 -0
- stoobly_agent/public/47-es2015.3878e5d1d692107748f3.js +1 -0
- stoobly_agent/public/47-es5.3878e5d1d692107748f3.js +1 -0
- stoobly_agent/public/5-es2015.aba7173be56fc19b4b6f.js +1 -0
- stoobly_agent/public/5-es5.aba7173be56fc19b4b6f.js +1 -0
- stoobly_agent/public/6-es2015.5fb726c0555664300974.js +1 -0
- stoobly_agent/public/6-es5.5fb726c0555664300974.js +1 -0
- stoobly_agent/public/7-es2015.9b9ab4adf24d13bdc2f8.js +1 -0
- stoobly_agent/public/7-es5.9b9ab4adf24d13bdc2f8.js +1 -0
- stoobly_agent/public/8-es2015.cdd7dce2a24aaf9d974f.js +1 -0
- stoobly_agent/public/8-es5.cdd7dce2a24aaf9d974f.js +1 -0
- stoobly_agent/public/9-es2015.cdde98f2537997afbf0f.js +1 -0
- stoobly_agent/public/9-es5.cdde98f2537997afbf0f.js +1 -0
- stoobly_agent/public/CHANGELOG.md +58 -0
- stoobly_agent/public/README.md +264 -0
- stoobly_agent/public/_redirects +1 -0
- stoobly_agent/public/assets/img/demo/1.jpg +0 -0
- stoobly_agent/public/assets/img/demo/2.jpg +0 -0
- stoobly_agent/public/assets/img/demo/3.jpg +0 -0
- stoobly_agent/public/assets/img/demo/4.jpg +0 -0
- stoobly_agent/public/assets/img/demo/5.jpg +0 -0
- stoobly_agent/public/assets/img/demo/6.jpg +0 -0
- stoobly_agent/public/assets/img/demo/7.jpg +0 -0
- stoobly_agent/public/assets/img/demo/8.jpg +0 -0
- stoobly_agent/public/assets/img/demo/landscape.jpg +0 -0
- stoobly_agent/public/assets/img/demo/mountain-cinematic.jpg +0 -0
- stoobly_agent/public/assets/img/illustrations/checklist.svg +164 -0
- stoobly_agent/public/assets/img/illustrations/data_center.svg +150 -0
- stoobly_agent/public/assets/img/illustrations/idea.svg +213 -0
- stoobly_agent/public/assets/img/illustrations/it_support.svg +168 -0
- stoobly_agent/public/assets/img/illustrations/peak_mountain_3.svg +262 -0
- stoobly_agent/public/assets/img/illustrations/under_constructions_1.svg +282 -0
- stoobly_agent/public/assets/img/logo/colored.png +0 -0
- stoobly_agent/public/assets/img/logo/colored.svg +9 -0
- stoobly_agent/public/assets/img/logo/white.png +0 -0
- stoobly_agent/public/assets/img/logo/white.svg +8 -0
- stoobly_agent/public/common-es2015.6f230354b12587f9be81.js +1 -0
- stoobly_agent/public/common-es5.6f230354b12587f9be81.js +1 -0
- stoobly_agent/public/favicon.ico +0 -0
- stoobly_agent/public/index.html +118 -0
- stoobly_agent/public/main-es2015.081bbd2709f22e95933e.js +1 -0
- stoobly_agent/public/main-es5.081bbd2709f22e95933e.js +1 -0
- stoobly_agent/public/polyfills-es2015.8ce2adc69f283f6c4c5e.js +1 -0
- stoobly_agent/public/polyfills-es5.7530172ddcec11a10eb3.js +1 -0
- stoobly_agent/public/runtime-es2015.362e49d383fb724f5414.js +1 -0
- stoobly_agent/public/runtime-es5.362e49d383fb724f5414.js +1 -0
- stoobly_agent/public/styles.ca104d947fbb2eebbeca.css +6 -0
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_additional_props_test.py +34 -0
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_info_test.py +80 -3
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_oauth2_scopes_test.py +82 -1
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_servers_test.py +80 -3
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_test.py +617 -4
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py +95 -0
- {stoobly_agent-0.33.7.dist-info → stoobly_agent-0.34.1.dist-info}/METADATA +1 -1
- {stoobly_agent-0.33.7.dist-info → stoobly_agent-0.34.1.dist-info}/RECORD +164 -35
- {stoobly_agent-0.33.7.dist-info → stoobly_agent-0.34.1.dist-info}/LICENSE +0 -0
- {stoobly_agent-0.33.7.dist-info → stoobly_agent-0.34.1.dist-info}/WHEEL +0 -0
- {stoobly_agent-0.33.7.dist-info → stoobly_agent-0.34.1.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '0.
|
2
|
+
VERSION = '0.34.1'
|
@@ -5,6 +5,7 @@ import sys
|
|
5
5
|
from stoobly_agent import VERSION
|
6
6
|
from stoobly_agent.app.proxy.replay.body_parser_service import decode_response, is_traversable
|
7
7
|
from stoobly_agent.app.models.adapters.raw_http_response_adapter import RawHttpResponseAdapter
|
8
|
+
from stoobly_agent.lib.api.response_param_names_resource import ResponseParamNamesResource
|
8
9
|
from stoobly_agent.lib.orm.migrate_service import migrate as database_migrate, rollback as database_rollback
|
9
10
|
from stoobly_agent.lib.orm.replayed_response import ReplayedResponse
|
10
11
|
from stoobly_agent.lib.orm.request import Request
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import click
|
1
|
+
import click, sys
|
2
2
|
|
3
3
|
from stoobly_agent.app.models.types import OPENAPI_FORMAT
|
4
4
|
from stoobly_agent.app.settings import Settings
|
@@ -7,6 +7,7 @@ from stoobly_agent.lib.utils.conditional_decorator import ConditionalDecorator
|
|
7
7
|
|
8
8
|
from .helpers.endpoint_facade import EndpointFacade
|
9
9
|
from .helpers.endpoints_apply_context import EndpointsApplyContext
|
10
|
+
from .helpers.endpoints_import_context import EndpointsImportContext
|
10
11
|
from .helpers.feature_flags import local, remote
|
11
12
|
from .helpers.validations import validate_project_key, validate_scenario_key
|
12
13
|
|
@@ -46,7 +47,7 @@ def apply(**kwargs):
|
|
46
47
|
context.with_lifecycle_hooks_path(kwargs.get('lifecycle_hooks_path'))
|
47
48
|
context.with_source(kwargs.get('source_path'), kwargs.get('source_format'))
|
48
49
|
|
49
|
-
endpoint_handler = lambda endpoint: print(f"{bcolors.OKBLUE}Applying Endpoint
|
50
|
+
endpoint_handler = lambda endpoint: print(f"{bcolors.OKBLUE}Applying Endpoint {endpoint['method']} {endpoint['path']}{bcolors.ENDC}")
|
50
51
|
context.with_endpoint_handler(endpoint_handler)
|
51
52
|
|
52
53
|
request_handler = lambda request, endpoint: print(request.url)
|
@@ -54,3 +55,25 @@ def apply(**kwargs):
|
|
54
55
|
|
55
56
|
facade = EndpointFacade(settings)
|
56
57
|
facade.apply(context)
|
58
|
+
|
59
|
+
@endpoint.command(
|
60
|
+
"import",
|
61
|
+
help="Import endpoints"
|
62
|
+
)
|
63
|
+
@click.option('--source-format', required=True, type=click.Choice([OPENAPI_FORMAT]), help="Spec file format.")
|
64
|
+
@click.option('--source-path', help='Path to spec file.')
|
65
|
+
def import_endpoint(**kwargs):
|
66
|
+
context = EndpointsImportContext()
|
67
|
+
|
68
|
+
project_key = settings.proxy.intercept.project_key
|
69
|
+
if project_key:
|
70
|
+
project_key = validate_project_key(project_key)
|
71
|
+
context.with_project(project_key.id)
|
72
|
+
|
73
|
+
context.with_source(kwargs.get('source_path'), kwargs.get('source_format'))
|
74
|
+
|
75
|
+
endpoint_handler = lambda endpoint: print(f"{bcolors.OKBLUE}Importing Endpoint {endpoint['method']} {endpoint['path']}{bcolors.ENDC}")
|
76
|
+
context.with_endpoint_handler(endpoint_handler)
|
77
|
+
|
78
|
+
facade = EndpointFacade(settings)
|
79
|
+
facade.import_endpoints(context)
|
@@ -2,9 +2,17 @@ import pdb
|
|
2
2
|
|
3
3
|
from stoobly_agent.app.models.factories.resource.request import RequestResourceFactory
|
4
4
|
from stoobly_agent.app.settings import Settings
|
5
|
+
from stoobly_agent.lib.api.endpoints_resource import EndpointsResource
|
6
|
+
from stoobly_agent.lib.api.header_names_resource import HeaderNamesResource
|
7
|
+
from stoobly_agent.lib.api.body_param_names_resource import BodyParamNamesResource
|
8
|
+
from stoobly_agent.lib.api.query_param_names_resource import QueryParamNamesResource
|
9
|
+
from stoobly_agent.lib.api.response_param_names_resource import ResponseParamNamesResource
|
10
|
+
from stoobly_agent.lib.api.response_header_names_resource import ResponseHeaderNamesResource
|
5
11
|
|
6
12
|
from .endpoints_apply_context import EndpointsApplyContext
|
13
|
+
from .endpoints_import_context import EndpointsImportContext
|
7
14
|
from .endpoints_apply_service import apply_endpoints
|
15
|
+
from .endpoints_import_service import import_endpoints
|
8
16
|
from .synchronize_request_service import SynchronizeRequestService
|
9
17
|
|
10
18
|
class EndpointFacade():
|
@@ -22,4 +30,17 @@ class EndpointFacade():
|
|
22
30
|
)
|
23
31
|
context.with_request_handler(request_handler)
|
24
32
|
|
25
|
-
apply_endpoints(context)
|
33
|
+
apply_endpoints(context)
|
34
|
+
|
35
|
+
def import_endpoints(self, context: EndpointsImportContext):
|
36
|
+
api_url = self.__settings.remote.api_url
|
37
|
+
api_key = self.__settings.remote.api_key
|
38
|
+
|
39
|
+
context.with_endpoint_resource(EndpointsResource(api_url, api_key))
|
40
|
+
context.with_header_name_resource(HeaderNamesResource(api_url, api_key))
|
41
|
+
context.with_body_param_name_resource(BodyParamNamesResource(api_url, api_key))
|
42
|
+
context.with_query_param_name_resource(QueryParamNamesResource(api_url, api_key))
|
43
|
+
context.with_response_param_name_resource(ResponseParamNamesResource(api_url, api_key))
|
44
|
+
context.with_response_header_name_resource(ResponseHeaderNamesResource(api_url, api_key))
|
45
|
+
|
46
|
+
import_endpoints(context)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from typing import Callable, TypedDict
|
2
|
+
|
3
|
+
from .openapi_endpoint_adapter import OpenApiEndpointAdapter
|
4
|
+
from stoobly_agent.app.models.types import OPENAPI_FORMAT
|
5
|
+
from stoobly_agent.lib.api.body_param_names_resource import BodyParamNamesResource
|
6
|
+
from stoobly_agent.lib.api.endpoints_resource import EndpointsResource
|
7
|
+
from stoobly_agent.lib.api.header_names_resource import HeaderNamesResource
|
8
|
+
from stoobly_agent.lib.api.interfaces.endpoints import EndpointShowResponse
|
9
|
+
from stoobly_agent.lib.api.query_param_names_resource import QueryParamNamesResource
|
10
|
+
from stoobly_agent.lib.api.response_param_names_resource import ResponseParamNamesResource
|
11
|
+
from stoobly_agent.lib.api.response_header_names_resource import ResponseHeaderNamesResource
|
12
|
+
|
13
|
+
class EndpointsImportContext:
|
14
|
+
def __init__(self):
|
15
|
+
self.endpoints = []
|
16
|
+
self.endpoint_handlers = []
|
17
|
+
self.resources = {}
|
18
|
+
self.project_id = None
|
19
|
+
|
20
|
+
def with_endpoint_handler(self, handler: Callable[[EndpointShowResponse], None]):
|
21
|
+
self.endpoint_handlers.append(handler)
|
22
|
+
return self
|
23
|
+
|
24
|
+
def with_project(self, project_id: int):
|
25
|
+
self.project_id = project_id
|
26
|
+
return self
|
27
|
+
|
28
|
+
def with_endpoint_resource(self, resource: EndpointsResource):
|
29
|
+
self.resources['endpoint'] = resource
|
30
|
+
return self
|
31
|
+
|
32
|
+
def with_header_name_resource(self, resource: HeaderNamesResource):
|
33
|
+
self.resources['header_name'] = resource
|
34
|
+
return self
|
35
|
+
|
36
|
+
def with_body_param_name_resource(self, resource: BodyParamNamesResource):
|
37
|
+
self.resources['body_param_name'] = resource
|
38
|
+
return self
|
39
|
+
|
40
|
+
def with_query_param_name_resource(self, resource: QueryParamNamesResource):
|
41
|
+
self.resources['query_param_name'] = resource
|
42
|
+
return self
|
43
|
+
|
44
|
+
def with_response_param_name_resource(self, resource: ResponseParamNamesResource):
|
45
|
+
self.resources['response_param_name'] = resource
|
46
|
+
return self
|
47
|
+
|
48
|
+
def with_response_header_name_resource(self, resource: ResponseHeaderNamesResource):
|
49
|
+
self.resources['response_header_name'] = resource
|
50
|
+
return self
|
51
|
+
|
52
|
+
def with_source(self, path: str, format: str):
|
53
|
+
if format == OPENAPI_FORMAT:
|
54
|
+
endpoint_adapter = OpenApiEndpointAdapter()
|
55
|
+
self.endpoints += endpoint_adapter.adapt_from_file(path)
|
56
|
+
|
57
|
+
return self
|
@@ -0,0 +1,152 @@
|
|
1
|
+
import json
|
2
|
+
import requests
|
3
|
+
import sys
|
4
|
+
from typing import Dict
|
5
|
+
|
6
|
+
from .endpoints_import_context import EndpointsImportContext
|
7
|
+
from stoobly_agent.app.models.types import ENDPOINT_COMPONENT_NAMES
|
8
|
+
from stoobly_agent.lib.api.body_param_names_resource import BodyParamNamesResource
|
9
|
+
from stoobly_agent.lib.api.endpoints_resource import EndpointsResource
|
10
|
+
from stoobly_agent.lib.api.header_names_resource import HeaderNamesResource
|
11
|
+
from stoobly_agent.lib.api.interfaces.endpoints import BodyParamName
|
12
|
+
from stoobly_agent.lib.api.interfaces.endpoints import EndpointShowResponse
|
13
|
+
from stoobly_agent.lib.api.interfaces.endpoints import RequestComponentName
|
14
|
+
from stoobly_agent.lib.api.query_param_names_resource import QueryParamNamesResource
|
15
|
+
from stoobly_agent.lib.api.response_header_names_resource import ResponseHeaderNamesResource
|
16
|
+
from stoobly_agent.lib.api.response_param_names_resource import ResponseParamNamesResource
|
17
|
+
from stoobly_agent.lib.logger import bcolors
|
18
|
+
|
19
|
+
def import_endpoints(context: EndpointsImportContext):
|
20
|
+
for endpoint in context.endpoints:
|
21
|
+
for endpoint_handler in context.endpoint_handlers:
|
22
|
+
endpoint_handler(endpoint)
|
23
|
+
|
24
|
+
try:
|
25
|
+
res = import_endpoint(context.project_id, context.resources['endpoint'], endpoint)
|
26
|
+
except requests.HTTPError as e:
|
27
|
+
error_handler(e, f"Failed to import endpoint: {endpoint['method']} {endpoint['path']}")
|
28
|
+
return
|
29
|
+
|
30
|
+
if res.status_code == 409: # Endpoint already created
|
31
|
+
continue
|
32
|
+
|
33
|
+
endpoint_id = res.json()['id']
|
34
|
+
try:
|
35
|
+
res = import_component_names(context.project_id, endpoint_id, endpoint, context.resources)
|
36
|
+
except requests.HTTPError as e:
|
37
|
+
error_handler(e, f"Failed to import endpoint: {endpoint['method']} {endpoint['path']}")
|
38
|
+
|
39
|
+
try:
|
40
|
+
cleanup_endpoint(context.project_id, endpoint_id, context.resources['endpoint'])
|
41
|
+
except requests.HTTPError as e:
|
42
|
+
error_handler(e, f"Failed to delete endpoint: {endpoint['method']} {endpoint['path']}, id {endpoint_id}")
|
43
|
+
return
|
44
|
+
|
45
|
+
def import_endpoint(project_id: int, endpoint_resource: EndpointsResource, endpoint: EndpointShowResponse):
|
46
|
+
res: requests.Response = endpoint_resource.create(**{
|
47
|
+
'host': endpoint.get('host'),
|
48
|
+
'method': endpoint.get('method'),
|
49
|
+
'path_segments': json.dumps(endpoint.get('path_segment_names', [])),
|
50
|
+
'path': endpoint.get('path'),
|
51
|
+
'port': endpoint.get('port'),
|
52
|
+
'project_id': project_id,
|
53
|
+
})
|
54
|
+
|
55
|
+
if res.ok or res.status_code == 409: # Endpoint already created
|
56
|
+
return res
|
57
|
+
|
58
|
+
res.raise_for_status()
|
59
|
+
|
60
|
+
def import_header_name(project_id: int, endpoint_id: int, header_name_resource: HeaderNamesResource, header_name: RequestComponentName):
|
61
|
+
res: requests.Response = header_name_resource.create(endpoint_id, {
|
62
|
+
'name': header_name.get('name'),
|
63
|
+
'is_deterministic': header_name.get('is_deterministic', True),
|
64
|
+
'is_required': header_name.get('is_required', False),
|
65
|
+
'endpoint_id': endpoint_id,
|
66
|
+
'project_id': project_id,
|
67
|
+
})
|
68
|
+
|
69
|
+
res.raise_for_status()
|
70
|
+
|
71
|
+
def import_body_param_name(project_id: int, endpoint_id: int, body_param_name_resource: BodyParamNamesResource, body_param_name: BodyParamName):
|
72
|
+
res: requests.Response = body_param_name_resource.create(endpoint_id, {
|
73
|
+
'name': body_param_name.get('name'),
|
74
|
+
'is_deterministic': body_param_name.get('is_deterministic', True),
|
75
|
+
'is_required': body_param_name.get('is_required', False),
|
76
|
+
'inferred_type': body_param_name.get('inferred_type'),
|
77
|
+
'query': body_param_name.get('query'),
|
78
|
+
'endpoint_id': endpoint_id,
|
79
|
+
'project_id': project_id,
|
80
|
+
})
|
81
|
+
|
82
|
+
res.raise_for_status()
|
83
|
+
|
84
|
+
def import_query_param_name(project_id: int, endpoint_id: int, query_param_name_resource: QueryParamNamesResource, query_param_name: RequestComponentName):
|
85
|
+
res: requests.Response = query_param_name_resource.create(endpoint_id, {
|
86
|
+
'name': query_param_name.get('name'),
|
87
|
+
'is_deterministic': query_param_name.get('is_deterministic', True),
|
88
|
+
'is_required': query_param_name.get('is_required', False),
|
89
|
+
'endpoint_id': endpoint_id,
|
90
|
+
'project_id': project_id,
|
91
|
+
})
|
92
|
+
|
93
|
+
res.raise_for_status()
|
94
|
+
|
95
|
+
def import_response_param_name(project_id: int, endpoint_id: int, response_param_name_resource: ResponseParamNamesResource, response_param_name: RequestComponentName):
|
96
|
+
res: requests.Response = response_param_name_resource.create(endpoint_id, {
|
97
|
+
'name': response_param_name.get('name'),
|
98
|
+
'is_deterministic': response_param_name.get('is_deterministic', True),
|
99
|
+
'is_required': response_param_name.get('is_required', False),
|
100
|
+
'inferred_type': response_param_name.get('inferred_type'),
|
101
|
+
'query': response_param_name.get('query'),
|
102
|
+
'endpoint_id': endpoint_id,
|
103
|
+
'project_id': project_id,
|
104
|
+
})
|
105
|
+
|
106
|
+
res.raise_for_status()
|
107
|
+
|
108
|
+
def import_response_header_name(project_id: int, endpoint_id: int, response_header_name_resource: ResponseHeaderNamesResource, response_header_name: RequestComponentName):
|
109
|
+
res: requests.Response = response_header_name_resource.create(endpoint_id, {
|
110
|
+
'name': response_header_name.get('name'),
|
111
|
+
'is_deterministic': response_header_name.get('is_deterministic', True),
|
112
|
+
'is_required': response_header_name.get('is_required', False),
|
113
|
+
'endpoint_id': endpoint_id,
|
114
|
+
'project_id': project_id,
|
115
|
+
})
|
116
|
+
|
117
|
+
res.raise_for_status()
|
118
|
+
|
119
|
+
component_name_import_dispatch = {
|
120
|
+
ENDPOINT_COMPONENT_NAMES[0]: import_header_name,
|
121
|
+
ENDPOINT_COMPONENT_NAMES[1]: import_body_param_name,
|
122
|
+
ENDPOINT_COMPONENT_NAMES[2]: import_query_param_name,
|
123
|
+
ENDPOINT_COMPONENT_NAMES[3]: import_response_header_name,
|
124
|
+
ENDPOINT_COMPONENT_NAMES[4]: import_response_param_name
|
125
|
+
}
|
126
|
+
|
127
|
+
def process_import(component_name: str, *args):
|
128
|
+
return component_name_import_dispatch[component_name](*args)
|
129
|
+
|
130
|
+
def import_component_names(project_id: int, endpoint_id: int, endpoint: EndpointShowResponse, resources: Dict[str, EndpointsResource]):
|
131
|
+
for component_name in ENDPOINT_COMPONENT_NAMES:
|
132
|
+
for component in endpoint.get(f'{component_name}s', {}):
|
133
|
+
resource = resources[component_name]
|
134
|
+
process_import(component_name, project_id, endpoint_id, resource, component)
|
135
|
+
|
136
|
+
def cleanup_endpoint(project_id: int, endpoint_id: int, resource: EndpointsResource):
|
137
|
+
print(f"{bcolors.OKBLUE}Cleaning up partial import...{bcolors.ENDC}")
|
138
|
+
res: requests.Response = resource.destroy(endpoint_id, **{
|
139
|
+
'project_id': project_id
|
140
|
+
})
|
141
|
+
|
142
|
+
res.raise_for_status()
|
143
|
+
|
144
|
+
def error_handler(error: requests.HTTPError, message = ''):
|
145
|
+
if message:
|
146
|
+
print(
|
147
|
+
f"{bcolors.FAIL}{message}{bcolors.ENDC}",
|
148
|
+
file=sys.stderr
|
149
|
+
)
|
150
|
+
|
151
|
+
print(error.response.text, file=sys.stderr)
|
152
|
+
print(f"Error {error.response.status_code} {error.response.reason}", file=sys.stderr)
|
@@ -73,19 +73,32 @@ class OpenApiEndpointAdapter():
|
|
73
73
|
endpoint: EndpointShowResponse = {}
|
74
74
|
endpoint['id'] = endpoint_counter
|
75
75
|
endpoint['method'] = http_method.upper()
|
76
|
-
endpoint['host'] = parsed_url.netloc
|
76
|
+
endpoint['host'] = '-' if parsed_url.netloc == '' else parsed_url.netloc
|
77
77
|
|
78
78
|
joined_path = self.__urljoin(parsed_url.path, path_name)
|
79
79
|
split_parts = joined_path.split('/')
|
80
80
|
pattern_path = []
|
81
|
+
segment_names = []
|
81
82
|
for part in split_parts:
|
82
83
|
sanitized_part = part
|
84
|
+
segment_name = part
|
83
85
|
if part.startswith('{') and part.endswith('}'):
|
84
86
|
sanitized_part = '%'
|
87
|
+
segment_name = f":{part[1:-1]}"
|
85
88
|
pattern_path.append(sanitized_part)
|
89
|
+
segment_names.append(segment_name)
|
86
90
|
pattern_path_str = '/'.join(pattern_path)
|
87
91
|
endpoint['match_pattern'] = pattern_path_str
|
88
92
|
endpoint['path'] = joined_path
|
93
|
+
|
94
|
+
endpoint['path_segment_names'] = []
|
95
|
+
for segment_name in segment_names:
|
96
|
+
if segment_name == "":
|
97
|
+
continue
|
98
|
+
path_component_name: RequestComponentName = {}
|
99
|
+
path_component_name['name'] = segment_name
|
100
|
+
path_component_name['type'] = "alias" if segment_name.startswith(':') else "static"
|
101
|
+
endpoint['path_segment_names'].append(path_component_name)
|
89
102
|
|
90
103
|
endpoint['port'] = str(parsed_url.port)
|
91
104
|
if endpoint['port'] is None or endpoint['port'] == 'None':
|
@@ -94,7 +107,7 @@ class OpenApiEndpointAdapter():
|
|
94
107
|
elif parsed_url.scheme == 'http':
|
95
108
|
endpoint['port'] = '80'
|
96
109
|
else:
|
97
|
-
endpoint['port'] = ''
|
110
|
+
endpoint['port'] = '0'
|
98
111
|
|
99
112
|
alias_counter = 0
|
100
113
|
header_param_counter = 0
|
@@ -142,7 +155,10 @@ class OpenApiEndpointAdapter():
|
|
142
155
|
param_value = self.__open_api_to_default_python_type(open_api_type)
|
143
156
|
|
144
157
|
literal_query_param = {
|
145
|
-
parameter['name']: {
|
158
|
+
parameter['name']: {
|
159
|
+
'value': param_value,
|
160
|
+
'required': parameter.get('required', False),
|
161
|
+
}
|
146
162
|
}
|
147
163
|
|
148
164
|
if not endpoint.get('literal_query_params'):
|
@@ -238,6 +254,11 @@ class OpenApiEndpointAdapter():
|
|
238
254
|
else:
|
239
255
|
self.__convert_literal_component_param(endpoint, required_body_params, [literal_body_params], 'body_param_name', 'literal_body_params')
|
240
256
|
|
257
|
+
# Responses -> construct lists of response header and response param name resources
|
258
|
+
responses = operation.get('responses', {})
|
259
|
+
if responses:
|
260
|
+
self.__parse_responses(endpoint, responses, components)
|
261
|
+
|
241
262
|
endpoints.append(endpoint)
|
242
263
|
|
243
264
|
return endpoints
|
@@ -391,11 +412,30 @@ class OpenApiEndpointAdapter():
|
|
391
412
|
component_name = component_data[1]
|
392
413
|
component = components.get(component_type, {})
|
393
414
|
|
415
|
+
# If component_type is 'headers'
|
416
|
+
# Example: '#components/headers/X-RateLimit-Limit'
|
417
|
+
if component_type == "headers":
|
418
|
+
# In this case, literal_body_params represents a header rather than request or response body params
|
419
|
+
literal_body_params['name'] = component_name
|
420
|
+
header_example = component.get('example')
|
421
|
+
if header_example:
|
422
|
+
literal_body_params['values'].append(header_example)
|
423
|
+
literal_body_params['is_required'] = component.get('is_required', False)
|
424
|
+
literal_body_params['is_deterministic'] = True
|
425
|
+
return literal_body_params
|
426
|
+
|
394
427
|
# Example: {'type': 'object', 'required': ['name'], 'properties': {'name': {'type': 'string'}, 'tag': {'type': 'string'}}}
|
395
428
|
body_spec = component.content()[component_name]
|
396
429
|
required_body_params += body_spec.get('required', [])
|
397
430
|
|
398
|
-
param_properties =
|
431
|
+
param_properties = {}
|
432
|
+
schema_type = body_spec.get('type')
|
433
|
+
if schema_type:
|
434
|
+
if schema_type == 'object':
|
435
|
+
param_properties = body_spec.get('properties', {})
|
436
|
+
elif schema_type == 'array':
|
437
|
+
param_properties = {'tmp': body_spec['items']}
|
438
|
+
|
399
439
|
all_of = body_spec.get('allOf')
|
400
440
|
any_of = body_spec.get('anyOf')
|
401
441
|
one_of = body_spec.get('oneOf')
|
@@ -421,7 +461,7 @@ class OpenApiEndpointAdapter():
|
|
421
461
|
# elif any_of or one_of:
|
422
462
|
|
423
463
|
return param_properties
|
424
|
-
|
464
|
+
|
425
465
|
def __convert_literal_component_param(self, endpoint: EndpointShowResponse,
|
426
466
|
required_component_params: List[str], literal_component_params: Union[dict, list],
|
427
467
|
component_name: str, literal_component_name: str) -> None:
|
@@ -444,7 +484,11 @@ class OpenApiEndpointAdapter():
|
|
444
484
|
else:
|
445
485
|
param['is_required'] = False
|
446
486
|
|
447
|
-
endpoint
|
487
|
+
if not endpoint.get(component_name + 's'):
|
488
|
+
endpoint[component_name + 's'] = built_params_list
|
489
|
+
else:
|
490
|
+
endpoint[component_name + 's'].extend(built_params_list)
|
491
|
+
|
448
492
|
del endpoint[literal_component_name]
|
449
493
|
|
450
494
|
# urllib.parse.urljoin() doesn't work for some of our edge cases
|
@@ -557,3 +601,64 @@ class OpenApiEndpointAdapter():
|
|
557
601
|
|
558
602
|
return result
|
559
603
|
|
604
|
+
def __parse_responses(self, endpoint: EndpointShowResponse, responses: Spec, components: Spec):
|
605
|
+
for response_code, response_definition in responses.items():
|
606
|
+
# Construct response param name components
|
607
|
+
literal_response_params = {}
|
608
|
+
response_body_array = False
|
609
|
+
required_response_params = []
|
610
|
+
response_content = response_definition.get('content', {})
|
611
|
+
for mimetype, media_type in response_content.items():
|
612
|
+
param_properties = {}
|
613
|
+
schema = media_type['schema']
|
614
|
+
|
615
|
+
if '$ref' in schema:
|
616
|
+
reference = schema['$ref']
|
617
|
+
self.__dereference(components, reference, required_response_params, literal_response_params)
|
618
|
+
else:
|
619
|
+
schema_type = schema.get('type')
|
620
|
+
if schema_type:
|
621
|
+
if schema_type == 'object':
|
622
|
+
param_properties = schema.get('properties', {})
|
623
|
+
elif schema_type == 'array':
|
624
|
+
response_body_array = True
|
625
|
+
param_properties = {'tmp': schema['items']}
|
626
|
+
|
627
|
+
for property_key, property_value in param_properties.items():
|
628
|
+
if property_key in required_response_params:
|
629
|
+
param_properties[property_key]['required'] = True
|
630
|
+
|
631
|
+
self.__extract_param_properties(components, None, required_response_params, param_properties, literal_response_params)
|
632
|
+
|
633
|
+
if literal_response_params:
|
634
|
+
endpoint['literal_response_params'] = literal_response_params
|
635
|
+
|
636
|
+
# Only support first media type
|
637
|
+
break
|
638
|
+
|
639
|
+
literal_response_params = endpoint.get('literal_response_params')
|
640
|
+
if literal_response_params:
|
641
|
+
if not response_body_array:
|
642
|
+
self.__convert_literal_component_param(endpoint, required_response_params, literal_response_params, 'response_param_name', 'literal_response_params')
|
643
|
+
else:
|
644
|
+
self.__convert_literal_component_param(endpoint, required_response_params, [literal_response_params], 'response_param_name', 'literal_response_params')
|
645
|
+
|
646
|
+
# Construct response header name components
|
647
|
+
response_headers = response_definition.get('headers', {})
|
648
|
+
for header_name, header_definition in response_headers.items():
|
649
|
+
response_header_name: RequestComponentName = {}
|
650
|
+
response_header_name['name'] = header_name
|
651
|
+
|
652
|
+
if '$ref' in header_definition:
|
653
|
+
reference = header_definition['$ref']
|
654
|
+
self.__dereference(components, reference, [], response_header_name)
|
655
|
+
else:
|
656
|
+
header_example = header_definition.get('example')
|
657
|
+
if header_example:
|
658
|
+
response_header_name['values'].append(header_example)
|
659
|
+
response_header_name['is_required'] = header_definition.get('is_required', False)
|
660
|
+
response_header_name['is_deterministic'] = True
|
661
|
+
|
662
|
+
if not endpoint.get('response_header_names'):
|
663
|
+
endpoint['response_header_names'] = []
|
664
|
+
endpoint['response_header_names'].append(response_header_name)
|
@@ -17,7 +17,9 @@ class ReplayCliOptions(TypedDict):
|
|
17
17
|
lifecycle_hooks_path: str
|
18
18
|
on_response: Callable
|
19
19
|
project_key: str
|
20
|
+
public_directory_path: str
|
20
21
|
record: bool
|
22
|
+
response_fixtures_path: str
|
21
23
|
scenario_key: str
|
22
24
|
scheme: str
|
23
25
|
trace: Trace
|
@@ -57,7 +59,9 @@ class ReplayFacade():
|
|
57
59
|
'host': cli_options.get('host'),
|
58
60
|
'lifecycle_hooks_path': cli_options.get('lifecycle_hooks_path'),
|
59
61
|
'overwrite': cli_options.get('overwrite'),
|
62
|
+
'public_directory_path': cli_options.get('public_directory_path'),
|
60
63
|
'request_origin': request_origin.CLI,
|
64
|
+
'response_fixtures_path': cli_options.get('response_fixtures_path'),
|
61
65
|
'save': cli_options.get('save'),
|
62
66
|
'scheme': cli_options.get('scheme'),
|
63
67
|
'trace_context': trace_context,
|
@@ -134,8 +134,10 @@ if not is_remote:
|
|
134
134
|
Configure which tests to print. Defaults to {test_output_level.PASSED}.
|
135
135
|
'''
|
136
136
|
)
|
137
|
+
@click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
|
137
138
|
@ConditionalDecorator(lambda f: click.option('--remote-project-key', help='Use remote project for endpoint definitions.')(f), is_remote and is_local)
|
138
139
|
@ConditionalDecorator(lambda f: click.option('--report-key', help='Save to report.')(f), is_remote)
|
140
|
+
@click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
|
139
141
|
@ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='Saves test results.')(f), is_remote)
|
140
142
|
@click.option('--scheme', type=click.Choice(['http', 'https']), help='Rewrite request scheme.')
|
141
143
|
@click.option('--strategy', default=test_strategy.DIFF, type=click.Choice([test_strategy.CONTRACT, test_strategy.CUSTOM, test_strategy.DIFF, test_strategy.FUZZY]), help='How to test responses.')
|
@@ -158,8 +158,10 @@ if is_local:
|
|
158
158
|
Configure which tests to print. Defaults to {test_output_level.PASSED}.
|
159
159
|
'''
|
160
160
|
)
|
161
|
+
@click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
|
161
162
|
@ConditionalDecorator(lambda f: click.option('--remote-project-key', help='Use remote project for endpoint definitions.')(f), is_remote)
|
162
163
|
@ConditionalDecorator(lambda f: click.option('--report-key', help='Save results to report.')(f), is_remote)
|
164
|
+
@click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
|
163
165
|
@ConditionalDecorator(lambda f: click.option('--save', is_flag=True, default=False, help='Save results.')(f), is_remote)
|
164
166
|
@click.option('--scheme', help='Rewrite request scheme.')
|
165
167
|
@click.option(
|
@@ -15,8 +15,10 @@ class TestOptions(TypedDict):
|
|
15
15
|
lifecycle_hooks_path: str
|
16
16
|
log_level: logger.LogLevel
|
17
17
|
output_level: test_output_level.TestOutputLevel
|
18
|
+
public_directory_path: str
|
18
19
|
remote_project_key: str
|
19
20
|
report_key: str
|
21
|
+
response_fixtures_path: str
|
20
22
|
save: str
|
21
23
|
scheme: str
|
22
24
|
strategy: test_strategy.TestStrategy
|
@@ -1,7 +1,33 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import TypedDict
|
2
2
|
|
3
3
|
OPENAPI_FORMAT = 'openapi'
|
4
4
|
|
5
|
+
ENDPOINT_COMPONENT_NAMES = [
|
6
|
+
"header_name",
|
7
|
+
"body_param_name",
|
8
|
+
"query_param_name",
|
9
|
+
"response_header_name",
|
10
|
+
"response_param_name"
|
11
|
+
]
|
12
|
+
|
5
13
|
class EndpointCreateParams(TypedDict):
|
6
|
-
|
14
|
+
host: str
|
15
|
+
method: str
|
16
|
+
path_segments: str
|
7
17
|
path: str
|
18
|
+
port: str
|
19
|
+
project_id: str
|
20
|
+
|
21
|
+
class HeaderNameCreateParams(TypedDict):
|
22
|
+
name: str
|
23
|
+
is_required: bool
|
24
|
+
is_deterministic: bool
|
25
|
+
project_id: str
|
26
|
+
endpoint_id: int
|
27
|
+
|
28
|
+
class ParamNameCreateParams(TypedDict):
|
29
|
+
name: str
|
30
|
+
project_id: str
|
31
|
+
endpoint_id: int
|
32
|
+
inferred_type: str
|
33
|
+
query: str
|
@@ -14,6 +14,7 @@ from stoobly_agent.lib.logger import Logger
|
|
14
14
|
|
15
15
|
from .constants import custom_response_codes
|
16
16
|
from .mock.context import MockContext
|
17
|
+
from .mock.eval_fixtures_service import eval_fixtures
|
17
18
|
from .mock.eval_request_service import inject_eval_request
|
18
19
|
from .utils.allowed_request_service import get_active_mode_policy
|
19
20
|
from .utils.request_handler import reverse_proxy
|
@@ -65,14 +66,14 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
|
|
65
66
|
if handle_failure:
|
66
67
|
res = handle_failure(context)
|
67
68
|
elif policy == mock_policy.ALL:
|
68
|
-
res = eval_request_with_retry(
|
69
|
+
res = eval_request_with_retry(context, eval_request, **options)
|
69
70
|
|
70
71
|
context.with_response(res)
|
71
72
|
|
72
73
|
if handle_success:
|
73
74
|
res = handle_success(context) or res
|
74
75
|
elif policy == mock_policy.FOUND:
|
75
|
-
res = eval_request_with_retry(
|
76
|
+
res = eval_request_with_retry(context, eval_request, **options)
|
76
77
|
|
77
78
|
context.with_response(res)
|
78
79
|
|
@@ -93,7 +94,8 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
|
|
93
94
|
|
94
95
|
return pass_on(context.flow, res)
|
95
96
|
|
96
|
-
def eval_request_with_retry(
|
97
|
+
def eval_request_with_retry(context: MockContext, eval_request, **options: MockOptions):
|
98
|
+
request = context.flow.request
|
97
99
|
infer = bool(options.get('infer'))
|
98
100
|
ignored_components = options['ignored_components'] if 'ignored_components' in options else []
|
99
101
|
|
@@ -103,6 +105,16 @@ def eval_request_with_retry(eval_request, request, **options: MockOptions):
|
|
103
105
|
ignored_components.append(res.content)
|
104
106
|
res = eval_request(request, ignored_components, infer=infer, retry=1)
|
105
107
|
|
108
|
+
if res.status_code == custom_response_codes.NOT_FOUND:
|
109
|
+
intercept_settings = context.intercept_settings
|
110
|
+
fixture = eval_fixtures(
|
111
|
+
request,
|
112
|
+
public_directory_path=intercept_settings.public_directory_path,
|
113
|
+
response_fixtures=intercept_settings.response_fixtures
|
114
|
+
)
|
115
|
+
if fixture:
|
116
|
+
res = fixture
|
117
|
+
|
106
118
|
return res
|
107
119
|
|
108
120
|
def handle_request_mock(context: MockContext):
|