proxy-mock 2.10.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.
- proxy_mock/__init__.py +10 -0
- proxy_mock/any_catcher.py +8 -0
- proxy_mock/api/__init__.py +0 -0
- proxy_mock/api/deps.py +5 -0
- proxy_mock/api/routes/__init__.py +0 -0
- proxy_mock/api/routes/configure.py +50 -0
- proxy_mock/api/routes/service.py +24 -0
- proxy_mock/api/routes/storage.py +51 -0
- proxy_mock/api/routes/traffic.py +79 -0
- proxy_mock/api/schemas.py +10 -0
- proxy_mock/app/__init__.py +3 -0
- proxy_mock/app/factory.py +97 -0
- proxy_mock/app/state.py +0 -0
- proxy_mock/client/__init__.py +4 -0
- proxy_mock/client/async_client.py +237 -0
- proxy_mock/client/client.py +174 -0
- proxy_mock/client/route.py +93 -0
- proxy_mock/client/service_endpoints.py +14 -0
- proxy_mock/core/__init__.py +0 -0
- proxy_mock/core/logging.py +16 -0
- proxy_mock/core/serializers.py +32 -0
- proxy_mock/core/settings.py +50 -0
- proxy_mock/core/time.py +6 -0
- proxy_mock/domain/__init__.py +20 -0
- proxy_mock/domain/constants.py +16 -0
- proxy_mock/domain/models.py +94 -0
- proxy_mock/repositories/__init__.py +0 -0
- proxy_mock/repositories/mock_storage.py +76 -0
- proxy_mock/repositories/traffic_store.py +69 -0
- proxy_mock/services/__init__.py +0 -0
- proxy_mock/services/cache_service.py +46 -0
- proxy_mock/services/mock_service.py +102 -0
- proxy_mock/services/proxy_service.py +104 -0
- proxy_mock/services/request_parser.py +30 -0
- proxy_mock/services/response_factory.py +25 -0
- proxy_mock/services/rule_engine.py +84 -0
- proxy_mock/services/traffic_service.py +26 -0
- proxy_mock/utils.py +145 -0
- proxy_mock-2.10.1.dist-info/METADATA +352 -0
- proxy_mock-2.10.1.dist-info/RECORD +42 -0
- proxy_mock-2.10.1.dist-info/WHEEL +4 -0
- proxy_mock-2.10.1.dist-info/licenses/LICENSE +21 -0
proxy_mock/__init__.py
ADDED
|
File without changes
|
proxy_mock/api/deps.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Request
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from proxy_mock.domain.constants import ParseError
|
|
9
|
+
from proxy_mock.domain.models import ConfigureMockRequestSchema
|
|
10
|
+
from proxy_mock.services.mock_service import mock_initialization, patch_mock_data, return_mock_data
|
|
11
|
+
from proxy_mock.services.request_parser import parse_configure
|
|
12
|
+
|
|
13
|
+
router = APIRouter()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.post("/configure_mock")
|
|
17
|
+
async def configure_mock(request: Request):
|
|
18
|
+
try:
|
|
19
|
+
parsed_data = await parse_configure(request)
|
|
20
|
+
except ParseError as err:
|
|
21
|
+
return JSONResponse({"success": False, "error": err.detail}, err.code)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
validate_data = ConfigureMockRequestSchema.model_validate(parsed_data).model_dump()
|
|
25
|
+
except ValidationError as err:
|
|
26
|
+
return JSONResponse({"success": False, "error": json.loads(err.json())}, 422)
|
|
27
|
+
|
|
28
|
+
response = await mock_initialization(request.app, validate_data)
|
|
29
|
+
return JSONResponse(response, 201)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.patch("/configure_mock")
|
|
33
|
+
async def patch_mock_configuration(request: Request):
|
|
34
|
+
try:
|
|
35
|
+
parsed_data = await parse_configure(request)
|
|
36
|
+
except ParseError as err:
|
|
37
|
+
return JSONResponse({"success": False, "error": err.detail}, err.code)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
validate_data = ConfigureMockRequestSchema.model_validate(parsed_data).model_dump(exclude_unset=True)
|
|
41
|
+
except ValidationError as err:
|
|
42
|
+
return JSONResponse({"success": False, "error": json.loads(err.json())}, 422)
|
|
43
|
+
|
|
44
|
+
saved_data = await return_mock_data(validate_data["path"])
|
|
45
|
+
if not saved_data:
|
|
46
|
+
return JSONResponse({"success": False, "error": f"There is no mock for {validate_data['path']}"}, 400)
|
|
47
|
+
|
|
48
|
+
mock_data = deepcopy(await patch_mock_data(saved_data, validate_data))
|
|
49
|
+
response = await mock_initialization(request.app, mock_data)
|
|
50
|
+
return JSONResponse(response, 200)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Request
|
|
4
|
+
from fastapi.responses import JSONResponse
|
|
5
|
+
|
|
6
|
+
from proxy_mock.services.mock_service import count_mocks
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get("/proxy_mock")
|
|
12
|
+
async def get_proxy_mock(request: Request):
|
|
13
|
+
app = request.app
|
|
14
|
+
return JSONResponse(
|
|
15
|
+
{
|
|
16
|
+
"success": True,
|
|
17
|
+
"version": app.state.version,
|
|
18
|
+
"python_version": platform.python_version(),
|
|
19
|
+
"mocks_count": await count_mocks(),
|
|
20
|
+
"traffic_count": await app.state.traffic_store.count(),
|
|
21
|
+
"traffic_max_items": app.state.traffic_store.max_items,
|
|
22
|
+
},
|
|
23
|
+
200,
|
|
24
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
|
|
4
|
+
from proxy_mock.core.serializers import convert_bytes_to_str
|
|
5
|
+
from proxy_mock.services.mock_service import (
|
|
6
|
+
cleanup_storage,
|
|
7
|
+
delete_mock_data,
|
|
8
|
+
remove_runtime_routes,
|
|
9
|
+
return_mock_data,
|
|
10
|
+
return_storage,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
router = APIRouter()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.get("/storage")
|
|
17
|
+
async def get_storage(request: Request):
|
|
18
|
+
path = request.query_params.get("path")
|
|
19
|
+
data = await return_mock_data(path) if path else await return_storage()
|
|
20
|
+
return JSONResponse({"success": True, "data": convert_bytes_to_str(data)}, 200)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.delete("/storage")
|
|
24
|
+
async def delete_storage(request: Request):
|
|
25
|
+
"""Delete mocks. Without `path` all of them; with `path` only that one (`404` if missing)."""
|
|
26
|
+
path = request.query_params.get("path")
|
|
27
|
+
|
|
28
|
+
if not path:
|
|
29
|
+
result = await cleanup_storage(request.app)
|
|
30
|
+
return JSONResponse({"success": result, "data": convert_bytes_to_str(await return_storage())}, 200)
|
|
31
|
+
|
|
32
|
+
remove_runtime_routes(request.app, path)
|
|
33
|
+
deleted = await delete_mock_data(path)
|
|
34
|
+
if not deleted:
|
|
35
|
+
return JSONResponse({"success": False, "error": f"No mock found for {path}"}, 404)
|
|
36
|
+
|
|
37
|
+
return JSONResponse({"success": True, "data": convert_bytes_to_str(await return_storage())}, 200)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.post("/storage/clean")
|
|
41
|
+
async def clean_storage(request: Request):
|
|
42
|
+
"""Deprecated alias for deleting mocks. The RESTful variant is `DELETE /storage`."""
|
|
43
|
+
path = request.query_params.get("path")
|
|
44
|
+
|
|
45
|
+
if path:
|
|
46
|
+
remove_runtime_routes(request.app, path)
|
|
47
|
+
result = await delete_mock_data(path)
|
|
48
|
+
else:
|
|
49
|
+
result = await cleanup_storage(request.app)
|
|
50
|
+
|
|
51
|
+
return JSONResponse({"success": result, "data": convert_bytes_to_str(await return_storage())}, 200)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from aiocache import Cache
|
|
4
|
+
from fastapi import APIRouter, Request
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from proxy_mock.domain.models import TrafficSettingsSchema
|
|
9
|
+
|
|
10
|
+
router = APIRouter()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _traffic_settings(request: Request) -> dict:
|
|
14
|
+
return {
|
|
15
|
+
"record_unknown_traffic": request.app.state.record_unknown_traffic,
|
|
16
|
+
"max_items": request.app.state.traffic_store.max_items,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router.get("/traffic")
|
|
21
|
+
async def get_traffic(request: Request):
|
|
22
|
+
path = request.query_params.get("path")
|
|
23
|
+
method = request.query_params.get("method")
|
|
24
|
+
limit_raw = request.query_params.get("limit")
|
|
25
|
+
limit = int(limit_raw) if limit_raw and limit_raw.isdigit() else None
|
|
26
|
+
|
|
27
|
+
data = await request.app.state.traffic_store.list(path=path, method=method, limit=limit)
|
|
28
|
+
return JSONResponse({"success": True, "count": len(data), "data": data}, 200)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.post("/traffic/clean")
|
|
32
|
+
async def clean_traffic(request: Request):
|
|
33
|
+
await request.app.state.traffic_store.clear()
|
|
34
|
+
return JSONResponse({"success": True, "data": []}, 200)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get("/traffic/settings")
|
|
38
|
+
async def get_traffic_settings(request: Request):
|
|
39
|
+
return JSONResponse({"success": True, "data": _traffic_settings(request)}, 200)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router.post("/traffic/settings")
|
|
43
|
+
async def set_traffic_settings(request: Request):
|
|
44
|
+
"""Traffic recording settings, changeable without restarting the service.
|
|
45
|
+
|
|
46
|
+
The update is partial: only the supplied fields are applied.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
payload = await request.json()
|
|
50
|
+
except ValueError:
|
|
51
|
+
return JSONResponse({"success": False, "error": "The request body must be valid JSON"}, 400)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
settings = TrafficSettingsSchema.model_validate(payload)
|
|
55
|
+
except ValidationError as err:
|
|
56
|
+
return JSONResponse({"success": False, "error": json.loads(err.json())}, 422)
|
|
57
|
+
|
|
58
|
+
changes = settings.model_dump(exclude_unset=True, exclude_none=True)
|
|
59
|
+
if not changes:
|
|
60
|
+
return JSONResponse(
|
|
61
|
+
{"success": False, "error": "Provide at least one field: record_unknown_traffic, max_items"}, 422
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if "record_unknown_traffic" in changes:
|
|
65
|
+
request.app.state.record_unknown_traffic = changes["record_unknown_traffic"]
|
|
66
|
+
|
|
67
|
+
if "max_items" in changes:
|
|
68
|
+
await request.app.state.traffic_store.set_max_items(changes["max_items"])
|
|
69
|
+
|
|
70
|
+
request.app.logger.info(f"Traffic settings updated: {changes}")
|
|
71
|
+
|
|
72
|
+
return JSONResponse({"success": True, "data": _traffic_settings(request)}, 200)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@router.post("/cache/clean")
|
|
76
|
+
async def clean_cache(request: Request):
|
|
77
|
+
await request.app.state.cache.close()
|
|
78
|
+
request.app.state.cache = Cache(Cache.MEMORY, namespace="mocks")
|
|
79
|
+
return JSONResponse({"success": True}, 200)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
|
|
4
|
+
import httpx2
|
|
5
|
+
from aiocache import Cache
|
|
6
|
+
from fastapi import FastAPI, Request
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
|
|
9
|
+
from proxy_mock.api.routes.configure import router as configure_router
|
|
10
|
+
from proxy_mock.api.routes.service import router as service_router
|
|
11
|
+
from proxy_mock.api.routes.storage import router as storage_router
|
|
12
|
+
from proxy_mock.api.routes.traffic import router as traffic_router
|
|
13
|
+
from proxy_mock.core.logging import app_logger
|
|
14
|
+
from proxy_mock.core.settings import get_version_from_pyproject, record_unknown_traffic_default
|
|
15
|
+
from proxy_mock.repositories.traffic_store import TrafficStore
|
|
16
|
+
from proxy_mock.services.traffic_service import new_traffic_data
|
|
17
|
+
from proxy_mock.utils import log_request
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _proxy_timeout() -> float:
|
|
21
|
+
raw = os.getenv("PROXY_MOCK_PROXY_TIMEOUT", "").strip()
|
|
22
|
+
try:
|
|
23
|
+
value = float(raw)
|
|
24
|
+
return value if value > 0 else 30.0
|
|
25
|
+
except ValueError:
|
|
26
|
+
return 30.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@asynccontextmanager
|
|
30
|
+
async def app_lifespan(app: FastAPI):
|
|
31
|
+
app.state.http_client = httpx2.AsyncClient(timeout=httpx2.Timeout(_proxy_timeout()))
|
|
32
|
+
try:
|
|
33
|
+
yield
|
|
34
|
+
finally:
|
|
35
|
+
await app.state.http_client.aclose()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ProxyMockApp(FastAPI):
|
|
39
|
+
def __init__(self, *args, **kwargs):
|
|
40
|
+
kwargs.setdefault("lifespan", app_lifespan)
|
|
41
|
+
super().__init__(*args, **kwargs)
|
|
42
|
+
|
|
43
|
+
self.state.cache = Cache(Cache.MEMORY, namespace="mocks")
|
|
44
|
+
self.state.traffic_store = TrafficStore()
|
|
45
|
+
# Toggled at runtime via POST /traffic/settings, so it lives in state rather than in a module constant.
|
|
46
|
+
self.state.record_unknown_traffic = record_unknown_traffic_default()
|
|
47
|
+
self.logger = app_logger
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_app() -> ProxyMockApp:
|
|
51
|
+
app = ProxyMockApp(title="Proxy Mock")
|
|
52
|
+
app.state.version = get_version_from_pyproject(app.logger)
|
|
53
|
+
|
|
54
|
+
app.include_router(service_router)
|
|
55
|
+
app.include_router(configure_router)
|
|
56
|
+
app.include_router(storage_router)
|
|
57
|
+
app.include_router(traffic_router)
|
|
58
|
+
|
|
59
|
+
@app.middleware("http")
|
|
60
|
+
@log_request
|
|
61
|
+
async def request_middleware(request: Request, call_next):
|
|
62
|
+
return await call_next(request)
|
|
63
|
+
|
|
64
|
+
@app.exception_handler(404)
|
|
65
|
+
async def custom_404_handler(request: Request, exc):
|
|
66
|
+
app.logger.warning(f"No mock found for {request.url.path}")
|
|
67
|
+
if app.state.record_unknown_traffic:
|
|
68
|
+
try:
|
|
69
|
+
await new_traffic_data(app, request, {"status_code": 404})
|
|
70
|
+
except Exception as err: # recording traffic must not break the 404 response
|
|
71
|
+
app.logger.error(f"Failed to record traffic for the 404: {err}")
|
|
72
|
+
return JSONResponse({"error": f"No mock found for {request.url.path}"}, 404)
|
|
73
|
+
|
|
74
|
+
app.logger.info(f"Service routes: {sorted(collect_service_paths(app))}")
|
|
75
|
+
|
|
76
|
+
return app
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def collect_service_paths(app: FastAPI) -> set[str]:
|
|
80
|
+
"""Paths of the service endpoints.
|
|
81
|
+
|
|
82
|
+
Since the FastAPI upgrade, routes added by include_router are nested in _IncludedRouter
|
|
83
|
+
and have no top-level ``path`` attribute of their own, so we read them from original_router.
|
|
84
|
+
"""
|
|
85
|
+
paths: set[str] = set()
|
|
86
|
+
for route in app.routes:
|
|
87
|
+
original_router = getattr(route, "original_router", None)
|
|
88
|
+
if original_router is not None:
|
|
89
|
+
for inner in getattr(original_router, "routes", []):
|
|
90
|
+
inner_path = getattr(inner, "path", None)
|
|
91
|
+
if inner_path:
|
|
92
|
+
paths.add(inner_path)
|
|
93
|
+
else:
|
|
94
|
+
route_path = getattr(route, "path", None)
|
|
95
|
+
if route_path:
|
|
96
|
+
paths.add(route_path)
|
|
97
|
+
return paths
|
proxy_mock/app/state.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from http import HTTPMethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import httpx2
|
|
5
|
+
import msgpack
|
|
6
|
+
from yarl import URL
|
|
7
|
+
|
|
8
|
+
from proxy_mock.client.service_endpoints import Endpoints
|
|
9
|
+
|
|
10
|
+
CONFIGURE_CONTENT_TYPE = "application/octet-stream"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _build_traffic_settings_payload(record_unknown_traffic: bool | None, max_items: int | None) -> dict:
|
|
14
|
+
"""The endpoint accepts a partial update, so fields that were not supplied are left out."""
|
|
15
|
+
payload = {}
|
|
16
|
+
if record_unknown_traffic is not None:
|
|
17
|
+
payload["record_unknown_traffic"] = record_unknown_traffic
|
|
18
|
+
if max_items is not None:
|
|
19
|
+
payload["max_items"] = max_items
|
|
20
|
+
return payload
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AsyncProxyMockRequestError(Exception):
|
|
24
|
+
"""Error while performing an async request to the proxy-mock server."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AsyncProxyMockResponseError(Exception):
|
|
28
|
+
"""HTTP error returned by the proxy-mock server."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AsyncProxyMock:
|
|
32
|
+
def __init__(self, host: str, timeout: float = 10.0) -> None:
|
|
33
|
+
self.host = host.rstrip("/")
|
|
34
|
+
self.timeout = timeout
|
|
35
|
+
self._client = httpx2.AsyncClient(base_url=self.host, timeout=timeout)
|
|
36
|
+
|
|
37
|
+
async def aclose(self) -> None:
|
|
38
|
+
await self._client.aclose()
|
|
39
|
+
|
|
40
|
+
async def __aenter__(self):
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
async def __aexit__(self, exc_type, exc, tb):
|
|
44
|
+
await self.aclose()
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _parse_response_body(response: httpx2.Response) -> dict | str | list | bytes | None:
|
|
48
|
+
if not response.content:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
content_type = response.headers.get("Content-Type", "")
|
|
52
|
+
if "json" in content_type:
|
|
53
|
+
try:
|
|
54
|
+
return response.json()
|
|
55
|
+
except Exception:
|
|
56
|
+
return response.text
|
|
57
|
+
if "text" in content_type:
|
|
58
|
+
return response.text
|
|
59
|
+
return response.content
|
|
60
|
+
|
|
61
|
+
async def execute_request(
|
|
62
|
+
self,
|
|
63
|
+
method: str | HTTPMethod,
|
|
64
|
+
route: str,
|
|
65
|
+
raise_for_status: bool = False,
|
|
66
|
+
**kwargs,
|
|
67
|
+
) -> httpx2.Response:
|
|
68
|
+
try:
|
|
69
|
+
response = await self._client.request(method=str(method), url=route, **kwargs)
|
|
70
|
+
except (httpx2.ConnectError, httpx2.TimeoutException) as err:
|
|
71
|
+
raise AsyncProxyMockRequestError(f"Proxy-mock request error:\n{err}") from err
|
|
72
|
+
|
|
73
|
+
if raise_for_status and response.status_code >= 400:
|
|
74
|
+
raise AsyncProxyMockResponseError(f"Proxy-mock returned HTTP {response.status_code}: {response.text}")
|
|
75
|
+
return response
|
|
76
|
+
|
|
77
|
+
async def execute_request_and_get_response_body(
|
|
78
|
+
self,
|
|
79
|
+
method: str | HTTPMethod,
|
|
80
|
+
route: str,
|
|
81
|
+
json: dict | None = None,
|
|
82
|
+
**kwargs,
|
|
83
|
+
) -> dict | str | list | bytes | None:
|
|
84
|
+
response = await self.execute_request(method=method, route=route, json=json, **kwargs)
|
|
85
|
+
return self._parse_response_body(response)
|
|
86
|
+
|
|
87
|
+
def _build_mock_request_data(
|
|
88
|
+
self,
|
|
89
|
+
path: str,
|
|
90
|
+
body: Any = None,
|
|
91
|
+
headers: dict | None = None,
|
|
92
|
+
status_code: int | None = None,
|
|
93
|
+
extra_info: dict | None = None,
|
|
94
|
+
proxy_host: str | None = None,
|
|
95
|
+
timeout: float | None = None,
|
|
96
|
+
rules: list[dict] | None = None,
|
|
97
|
+
methods: list[str] | None = None,
|
|
98
|
+
cache_time: int | None = None,
|
|
99
|
+
include_body_if_none: bool = True,
|
|
100
|
+
**kwargs,
|
|
101
|
+
) -> dict:
|
|
102
|
+
mock_data = {}
|
|
103
|
+
if include_body_if_none or body is not None:
|
|
104
|
+
mock_data["body"] = body
|
|
105
|
+
if status_code is not None:
|
|
106
|
+
mock_data["status_code"] = status_code
|
|
107
|
+
if headers is not None:
|
|
108
|
+
mock_data["headers"] = headers
|
|
109
|
+
|
|
110
|
+
request_data = {"path": path}
|
|
111
|
+
if methods is not None:
|
|
112
|
+
request_data["methods"] = methods
|
|
113
|
+
if mock_data:
|
|
114
|
+
request_data["mock_data"] = mock_data
|
|
115
|
+
if extra_info is not None:
|
|
116
|
+
request_data["extra_info"] = extra_info
|
|
117
|
+
if proxy_host is not None:
|
|
118
|
+
request_data["proxy_host"] = proxy_host
|
|
119
|
+
if timeout is not None:
|
|
120
|
+
request_data["timeout"] = timeout
|
|
121
|
+
if rules is not None:
|
|
122
|
+
request_data["rules"] = rules
|
|
123
|
+
if cache_time is not None:
|
|
124
|
+
request_data["cache_time"] = cache_time
|
|
125
|
+
|
|
126
|
+
request_data.update(kwargs)
|
|
127
|
+
return request_data
|
|
128
|
+
|
|
129
|
+
async def _send_configure(self, method: HTTPMethod, payload: dict):
|
|
130
|
+
return await self.execute_request_and_get_response_body(
|
|
131
|
+
method=method,
|
|
132
|
+
route=Endpoints.CONFIGURE_MOCK,
|
|
133
|
+
content=msgpack.packb(payload),
|
|
134
|
+
headers={"Content-Type": CONFIGURE_CONTENT_TYPE},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
async def get_proxy_mock(self):
|
|
138
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.GET, Endpoints.PROXY_MOCK)
|
|
139
|
+
|
|
140
|
+
async def configure_mock(
|
|
141
|
+
self,
|
|
142
|
+
path: str,
|
|
143
|
+
body: Any = None,
|
|
144
|
+
headers: dict | None = None,
|
|
145
|
+
status_code: int | None = None,
|
|
146
|
+
extra_info: dict | None = None,
|
|
147
|
+
proxy_host: str | None = None,
|
|
148
|
+
timeout: float | None = None,
|
|
149
|
+
rules: list[dict] | None = None,
|
|
150
|
+
methods: list[str] | None = None,
|
|
151
|
+
cache_time: int | None = None,
|
|
152
|
+
**kwargs,
|
|
153
|
+
):
|
|
154
|
+
payload = self._build_mock_request_data(
|
|
155
|
+
path=path,
|
|
156
|
+
body=body,
|
|
157
|
+
headers=headers,
|
|
158
|
+
status_code=status_code,
|
|
159
|
+
extra_info=extra_info,
|
|
160
|
+
proxy_host=proxy_host,
|
|
161
|
+
timeout=timeout,
|
|
162
|
+
rules=rules,
|
|
163
|
+
methods=methods,
|
|
164
|
+
cache_time=cache_time,
|
|
165
|
+
include_body_if_none=True,
|
|
166
|
+
**kwargs,
|
|
167
|
+
)
|
|
168
|
+
return await self._send_configure(HTTPMethod.POST, payload)
|
|
169
|
+
|
|
170
|
+
async def patch_mock(
|
|
171
|
+
self,
|
|
172
|
+
path: str,
|
|
173
|
+
body: Any = None,
|
|
174
|
+
headers: dict | None = None,
|
|
175
|
+
status_code: int | None = None,
|
|
176
|
+
extra_info: dict | None = None,
|
|
177
|
+
proxy_host: str | None = None,
|
|
178
|
+
timeout: float | None = None,
|
|
179
|
+
rules: list[dict] | None = None,
|
|
180
|
+
methods: list[str] | None = None,
|
|
181
|
+
cache_time: int | None = None,
|
|
182
|
+
**kwargs,
|
|
183
|
+
):
|
|
184
|
+
payload = self._build_mock_request_data(
|
|
185
|
+
path=path,
|
|
186
|
+
body=body,
|
|
187
|
+
headers=headers,
|
|
188
|
+
status_code=status_code,
|
|
189
|
+
extra_info=extra_info,
|
|
190
|
+
proxy_host=proxy_host,
|
|
191
|
+
timeout=timeout,
|
|
192
|
+
rules=rules,
|
|
193
|
+
methods=methods,
|
|
194
|
+
cache_time=cache_time,
|
|
195
|
+
include_body_if_none=False,
|
|
196
|
+
**kwargs,
|
|
197
|
+
)
|
|
198
|
+
return await self._send_configure(HTTPMethod.PATCH, payload)
|
|
199
|
+
|
|
200
|
+
async def get_traffic(self, path: str | None = None, method: str | None = None, limit: int | None = None):
|
|
201
|
+
query_params = {
|
|
202
|
+
**({"path": path} if path else {}),
|
|
203
|
+
**({"method": method} if method else {}),
|
|
204
|
+
**({"limit": limit} if limit is not None else {}),
|
|
205
|
+
}
|
|
206
|
+
full_path = URL().with_path(Endpoints.TRAFFIC).with_query(query_params).human_repr()
|
|
207
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.GET, full_path)
|
|
208
|
+
|
|
209
|
+
async def get_storage(self, path: str | None = None):
|
|
210
|
+
query_params = {**({"path": path} if path else {})}
|
|
211
|
+
full_path = URL().with_path(Endpoints.STORAGE).with_query(query_params).human_repr()
|
|
212
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.GET, full_path)
|
|
213
|
+
|
|
214
|
+
async def clean_storage(self, path: str | None = None):
|
|
215
|
+
query_params = {**({"path": path} if path else {})}
|
|
216
|
+
full_path = URL().with_path(Endpoints.STORAGE_CLEAN).with_query(query_params).human_repr()
|
|
217
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.POST, full_path)
|
|
218
|
+
|
|
219
|
+
async def delete_mock(self, path: str):
|
|
220
|
+
full_path = URL().with_path(Endpoints.STORAGE).with_query({"path": path}).human_repr()
|
|
221
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.DELETE, full_path)
|
|
222
|
+
|
|
223
|
+
async def clean_traffic(self):
|
|
224
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.POST, Endpoints.TRAFFIC_CLEAN)
|
|
225
|
+
|
|
226
|
+
async def get_traffic_settings(self):
|
|
227
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.GET, Endpoints.TRAFFIC_SETTINGS)
|
|
228
|
+
|
|
229
|
+
async def set_traffic_settings(self, record_unknown_traffic: bool | None = None, max_items: int | None = None):
|
|
230
|
+
return await self.execute_request_and_get_response_body(
|
|
231
|
+
HTTPMethod.POST,
|
|
232
|
+
Endpoints.TRAFFIC_SETTINGS,
|
|
233
|
+
json=_build_traffic_settings_payload(record_unknown_traffic, max_items),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
async def clean_cache(self):
|
|
237
|
+
return await self.execute_request_and_get_response_body(HTTPMethod.POST, Endpoints.CACHE_CLEAN)
|