This commit is contained in:
Mateusz Gruszczyński committed 2026-11-07 13:13:25 +01:00
1 parent 3a384baa6b
commit 672c84bc13
21 files changed
+450 -70

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Static integrity checks for the embedded administration interface.
The test intentionally uses only the Python standard library. It verifies the
actual HTML assembly and JavaScript files embedded by src/admin/router.rs; it is
not a replacement for Rust/browser integration tests.
"""
from __future__ import annotations
from collections import Counter
from pathlib import Path
import re
ROOT = Path(__file__).resolve().parents[1]
WEB = ROOT / "src/admin/web"
ROUTER = (ROOT / "src/admin/router.rs").read_text(encoding="utf-8")
def assembled_html() -> str:
html = (WEB / "index.html").read_text(encoding="utf-8")
metrics = (WEB / "partials/overview-metrics.html").read_text(encoding="utf-8")
charts = (WEB / "partials/overview-charts.html").read_text(encoding="utf-8")
metrics_main, metrics_web = metrics.split("<!-- RP_METRICS_SPLIT -->", 1)
charts_main, charts_web = charts.split("<!-- RP_CHARTS_SPLIT -->", 1)
return (
html.replace("<!-- RP_METRICS_MAIN -->", metrics_main)
.replace("<!-- RP_METRICS_WEB -->", metrics_web)
.replace("<!-- RP_CHARTS_MAIN -->", charts_main)
.replace("<!-- RP_CHARTS_WEB -->", charts_web)
)
def embedded_app_sources() -> list[Path]:
start = ROUTER.index("fn admin_app_javascript()")
end = ROUTER.index("fn admin_vhosts_javascript()", start)
app_block = ROUTER[start:end]
paths = [WEB / rel for rel in re.findall(r'include_str!\("web/([^"\n]+\.js)"\)', app_block)]
vstart = end
vend = ROUTER.index("pub fn router", vstart)
vhost_block = ROUTER[vstart:vend]
vhost_paths = [WEB / rel for rel in re.findall(r'include_str!\("web/([^"\n]+\.js)"\)', vhost_block)]
result: list[Path] = []
for path in [*paths, *vhost_paths]:
if path not in result:
result.append(path)
return result
def attribute_values(html: str, name: str) -> set[str]:
return set(re.findall(rf'\b{re.escape(name)}=["\']([^"\']+)', html))
html = assembled_html()
ids = re.findall(r'\bid=["\']([^"\']+)', html)
duplicates = sorted(name for name, count in Counter(ids).items() if count > 1)
assert not duplicates, f"duplicate panel DOM ids: {duplicates}"
id_set = set(ids)
sources = embedded_app_sources()
assert sources, "no embedded administration JavaScript sources found"
missing_files = [str(path.relative_to(ROOT)) for path in sources if not path.is_file()]
assert not missing_files, f"missing embedded JS sources: {missing_files}"
literal_refs: dict[str, set[str]] = {}
patterns = [
re.compile(r'\$\(["\']([^"\']+)["\']\)'),
re.compile(r'document\.getElementById\(["\']([^"\']+)["\']\)'),
]
for path in sources:
source = path.read_text(encoding="utf-8")
for pattern in patterns:
for dom_id in pattern.findall(source):
literal_refs.setdefault(dom_id, set()).add(str(path.relative_to(ROOT)))
missing_refs = {dom_id: sorted(paths) for dom_id, paths in literal_refs.items() if dom_id not in id_set}
assert not missing_refs, f"embedded panel JS references missing DOM ids: {missing_refs}"
# Every visible primary navigation target must have a panel section. filelogs is
# intentionally opened from the Logs menu rather than the main navigation.
panel_targets = attribute_values(html, "data-panel")
panel_sections = {value.removeprefix("panel-") for value in id_set if value.startswith("panel-")}
assert panel_targets <= panel_sections, f"navigation targets without panels: {sorted(panel_targets - panel_sections)}"
assert panel_sections - panel_targets <= {"filelogs"}, f"unexpected orphan panels: {sorted(panel_sections - panel_targets)}"
# Tab/page contracts used by the generic tab handlers in the embedded JS.
for prefix in ("settings", "policy-settings", "cache-settings", "logging-settings", "traffic"):
tabs = attribute_values(html, f"data-{prefix}-tab")
pages = attribute_values(html, f"data-{prefix}-page")
assert tabs == pages, f"{prefix} tab/page mismatch: tabs={sorted(tabs - pages)}, pages={sorted(pages - tabs)}"
print(
f"Panel interface contract: OK ({len(ids)} unique DOM ids, "
f"{len(literal_refs)} literal JS references, {len(sources)} embedded JS files)"
)
+39
View File
@@ -0,0 +1,39 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const root = path.join(__dirname, "..");
const read = relative => fs.readFileSync(path.join(root, relative), "utf8");
const settings = read("src/admin/web/settings.js");
const certificateApi = read("src/admin/web/certificate-api.js");
const core = read("src/admin/web/app/core.js");
const cache = read("src/admin/web/cache-panel.js");
const transform = read("src/admin/web/transform-panel.js");
const traffic = read("src/admin/web/traffic-panel.js");
const openapi = JSON.parse(read("src/admin/openapi.json"));
assert.match(settings, /authenticated && name === "certificate-api"[^\n]+rustProxyCertificateApi\?\.load/);
assert.match(settings, /authenticated && active === "certificate-api"[^\n]+rustProxyCertificateApi\?\.load/);
assert.ok(!/renderCertificateApi\(\);\s*loadCertificateApi\(\);\s*\}\)\(\);\s*$/.test(certificateApi),
"Certificate API must not fetch credentials when its module is initialized");
assert.match(core, /async function refresh\(force = false, includeLogs = false\)/);
assert.match(core, /if \(includeLogs\) tasks\.push\(rustProxyRefresh\.manual\("logs-fallback"\)\)/);
assert.match(core, /refresh\(true, true\)\.catch/);
for (const [name, source, endpoint] of [
["cache", cache, "/api/cache"],
["transform", transform, "/api/transform"],
["traffic", traffic, "/api/traffic"],
]) {
assert.ok(/view=live/.test(source) || /set\(\"view\", \"live\"\)/.test(source), `${name} panel must use the lightweight polling view`);
assert.match(source, /data\.revision !== staticRevision/, `${name} panel must invalidate static data on config revision changes`);
assert.match(source, /reset\(\)[\s\S]{0,220}staticData = null[\s\S]{0,120}staticRevision = null/, `${name} panel reset must discard static cached API data`);
const operation = openapi.paths[endpoint].get;
const view = (operation.parameters || []).find(parameter => parameter.name === "view");
assert.deepEqual(view?.schema?.enum, ["live"], `${endpoint} must document view=live`);
}
assert.equal(openapi.info.version, "0.36.0");
console.log("Panel refresh/API contract checks: OK");