103 lines
4.6 KiB
Python
103 lines
4.6 KiB
Python
#!/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}"
|
|
|
|
assert "ui-interface-icons" in id_set, "Appearance must expose the SVG icon toggle"
|
|
assert any(path.name == "icons.js" for path in sources), "semantic icon module must be embedded in app.js"
|
|
|
|
assert 'data-i18n="overviewExtraRecentSamples"' not in html, "Overview metric tiles must not show the redundant Recent samples footer"
|
|
overview_extra = (WEB / "app/overview-extra-metrics.js").read_text(encoding="utf-8")
|
|
assert 't("overviewExtraRecentSamples")' not in overview_extra, "Overview metric details must not fall back to the generic Recent samples label"
|
|
|
|
# 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)"
|
|
)
|