v0.36.0
This commit is contained in:
1 parent
3a384baa6b
commit
672c84bc13
21 files changed
+450
-70
No files matched your search
@@ -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)"
|
||||
)
|
||||
Reference in new issue
Block a user