feat: add proxy template origin field and implement socket utilities for log streaming
This commit is contained in:
@@ -9,7 +9,7 @@ from app.core.security import get_auth_context, require_authenticated_user
|
||||
from app.models.health import HealthSnapshot
|
||||
from app.models.proxy import ProxyCreateInput, ProxyDocument
|
||||
from app.models.service import ServiceEndpointDocument
|
||||
from app.services.mitmproxy.templates import BUILTIN_TEMPLATES
|
||||
from app.services.mitmproxy.templates import list_file_templates
|
||||
from app.services.mitmproxy.validator import ProxyCodeValidationError
|
||||
|
||||
proxy_router_module = importlib.import_module("app.api.proxy.router")
|
||||
@@ -864,6 +864,8 @@ def test_stop_proxy_updates_runtime_state(client, monkeypatch: object):
|
||||
|
||||
|
||||
def test_list_mitmproxy_templates_returns_builtins_and_custom(client, monkeypatch):
|
||||
file_templates = list_file_templates()
|
||||
|
||||
async def fake_list_custom_templates():
|
||||
return []
|
||||
|
||||
@@ -872,13 +874,19 @@ def test_list_mitmproxy_templates_returns_builtins_and_custom(client, monkeypatc
|
||||
"list_custom_templates",
|
||||
fake_list_custom_templates,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
proxy_router_module,
|
||||
"list_file_templates",
|
||||
lambda: file_templates,
|
||||
)
|
||||
|
||||
response = client.get("/proxy/mitmproxy/templates")
|
||||
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert len(body) == len(BUILTIN_TEMPLATES)
|
||||
assert len(body) == len(file_templates)
|
||||
assert body[0]["built_in"] is True
|
||||
assert body[0]["origin"] == "file"
|
||||
assert body[0]["protocol"] in {"http", "tcp"}
|
||||
assert isinstance(body[0]["tags"], list)
|
||||
assert body[0]["action"] in {"block", "rewrite"}
|
||||
@@ -906,6 +914,7 @@ def test_create_mitmproxy_template_validates_and_persists(client, monkeypatch):
|
||||
"code": payload.code,
|
||||
"source_url": payload.source_url,
|
||||
"built_in": False,
|
||||
"origin": "custom",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
@@ -939,4 +948,33 @@ def test_create_mitmproxy_template_validates_and_persists(client, monkeypatch):
|
||||
assert response.status_code == 201
|
||||
assert response.json()["id"] == "custom-header"
|
||||
assert response.json()["protocol"] == "http"
|
||||
assert response.json()["origin"] == "custom"
|
||||
assert captured["validated"] == "def response(flow):\n pass\n"
|
||||
|
||||
|
||||
def test_create_mitmproxy_template_rejects_file_backed_id(client, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
proxy_router_module,
|
||||
"file_template_ids",
|
||||
lambda: {"http-block-path-traversal"},
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
"/proxy/mitmproxy/templates",
|
||||
json={
|
||||
"id": "http-block-path-traversal",
|
||||
"name": "Custom header",
|
||||
"description": "Adds a custom response header",
|
||||
"category": "http",
|
||||
"protocol": "http",
|
||||
"family": "custom",
|
||||
"tags": [],
|
||||
"action": "custom",
|
||||
"summary": "Saved custom template.",
|
||||
"behavior": "Applies the current script as-is.",
|
||||
"code": "def response(flow):\n pass\n",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
assert "Template already exists" in response.json()["detail"]
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from app.services.mitmproxy.templates import BUILTIN_TEMPLATES
|
||||
import logging
|
||||
|
||||
import yaml
|
||||
|
||||
from app.services.mitmproxy.templates import list_file_templates
|
||||
|
||||
|
||||
def test_builtin_templates_use_four_space_indentation():
|
||||
for template in BUILTIN_TEMPLATES:
|
||||
def test_file_templates_use_four_space_indentation():
|
||||
for template in list_file_templates():
|
||||
for line in template.code.splitlines():
|
||||
assert "\t" not in line, f"{template.id} contains a tab: {line!r}"
|
||||
if not line.strip():
|
||||
@@ -11,3 +15,42 @@ def test_builtin_templates_use_four_space_indentation():
|
||||
assert indent % 4 == 0, (
|
||||
f"{template.id} has non-4-space indentation: {line!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_list_file_templates_loads_yaml_backed_templates():
|
||||
templates = list_file_templates()
|
||||
|
||||
assert templates
|
||||
assert all(template.origin == "file" for template in templates)
|
||||
assert all(template.built_in is True for template in templates)
|
||||
assert all(template.protocol in {"http", "tcp"} for template in templates)
|
||||
|
||||
|
||||
def test_list_file_templates_skips_invalid_files(tmp_path, caplog):
|
||||
path = tmp_path / "invalid.yaml"
|
||||
path.write_text("[]\n", encoding="utf-8")
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
templates = list_file_templates(tmp_path)
|
||||
|
||||
assert templates == []
|
||||
assert "Skipping template file" in caplog.text
|
||||
|
||||
|
||||
def test_list_file_templates_skips_duplicate_ids(tmp_path, caplog):
|
||||
payload = {
|
||||
"id": "duplicate",
|
||||
"name": "Duplicate",
|
||||
"protocol": "http",
|
||||
"description": "Duplicate template",
|
||||
"code": "from mitmproxy import http\n",
|
||||
}
|
||||
document = yaml.safe_dump(payload, sort_keys=False)
|
||||
(tmp_path / "one.yaml").write_text(document, encoding="utf-8")
|
||||
(tmp_path / "two.yaml").write_text(document, encoding="utf-8")
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
templates = list_file_templates(tmp_path)
|
||||
|
||||
assert templates == []
|
||||
assert "duplicate file-backed template id" in caplog.text
|
||||
|
||||
Reference in New Issue
Block a user