104 lines
4.1 KiB
Python
104 lines
4.1 KiB
Python
|
|
"""Executable and static quality gates for isolated HUX-08 research UX."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import subprocess
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[2]
|
||
|
|
RESEARCH = ROOT / "dockerfiles" / "hermes-webui-hux" / "research"
|
||
|
|
|
||
|
|
|
||
|
|
def test_hux_research_node_suite_and_coverage():
|
||
|
|
result = subprocess.run(
|
||
|
|
[
|
||
|
|
"node",
|
||
|
|
"--test",
|
||
|
|
"--experimental-strip-types",
|
||
|
|
"--experimental-test-coverage",
|
||
|
|
"--test-coverage-lines=95",
|
||
|
|
"--test-coverage-functions=95",
|
||
|
|
"--test-coverage-branches=95",
|
||
|
|
"--test-coverage-include=dockerfiles/hermes-webui-hux/research/model.ts",
|
||
|
|
"--test-coverage-include=dockerfiles/hermes-webui-hux/research/security.ts",
|
||
|
|
"--test-coverage-include=dockerfiles/hermes-webui-hux/research/endpoints.ts",
|
||
|
|
"testing/tests/test_hermes_hux_ui_research.mjs",
|
||
|
|
],
|
||
|
|
cwd=ROOT,
|
||
|
|
check=False,
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
timeout=30,
|
||
|
|
)
|
||
|
|
assert result.returncode == 0, result.stdout + result.stderr
|
||
|
|
|
||
|
|
|
||
|
|
def test_hux_research_matches_shared_contract_and_stays_default_off():
|
||
|
|
schema = json.loads(
|
||
|
|
(ROOT / "services/hermes/contracts/hux/citation.schema.json").read_text(
|
||
|
|
encoding="utf-8"
|
||
|
|
)
|
||
|
|
)
|
||
|
|
model = (RESEARCH / "model.ts").read_text(encoding="utf-8")
|
||
|
|
for definition in ("source", "passage", "citation", "notebook"):
|
||
|
|
assert schema["$defs"][definition]["properties"]["schema"]["const"] in model
|
||
|
|
for value in schema["$defs"]["source"]["properties"]["kind"]["enum"]:
|
||
|
|
assert f'"{value}"' in model
|
||
|
|
for value in schema["$defs"]["source"]["properties"]["classification"][
|
||
|
|
"enum"
|
||
|
|
]:
|
||
|
|
assert f'"{value}"' in model
|
||
|
|
for value in schema["$defs"]["citation"]["properties"]["support"]["enum"]:
|
||
|
|
assert f'"{value}"' in model
|
||
|
|
assert "researchEnabled(props.flags)" in (
|
||
|
|
RESEARCH / "ResearchWorkspace.tsx"
|
||
|
|
).read_text(encoding="utf-8")
|
||
|
|
assert "dockerfiles/hermes-webui-hux/research" not in (
|
||
|
|
ROOT / "dockerfiles/Dockerfile.hermes-webui"
|
||
|
|
).read_text(encoding="utf-8")
|
||
|
|
|
||
|
|
|
||
|
|
def test_hux_research_ui_is_accessible_and_evidence_is_not_executable():
|
||
|
|
component = (RESEARCH / "ResearchWorkspace.tsx").read_text(encoding="utf-8")
|
||
|
|
endpoints = (RESEARCH / "endpoints.ts").read_text(encoding="utf-8")
|
||
|
|
security = (RESEARCH / "security.ts").read_text(encoding="utf-8")
|
||
|
|
styles = (RESEARCH / "styles.css").read_text(encoding="utf-8")
|
||
|
|
assert "if (!enabled || !page) return null" in component
|
||
|
|
assert 'aria-label="Research and citations"' in component
|
||
|
|
assert 'aria-expanded={expanded}' in component
|
||
|
|
assert 'aria-controls={panelId}' in component
|
||
|
|
assert 'aria-live="polite"' in component
|
||
|
|
assert "primary source" not in component # classification is data-driven
|
||
|
|
assert "Research notes" in component
|
||
|
|
assert "Assumptions" in component
|
||
|
|
assert "Unresolved questions" in component
|
||
|
|
assert 'rel="noopener noreferrer"' in component
|
||
|
|
assert "dangerouslySetInnerHTML" not in component
|
||
|
|
assert "<iframe" not in component and "<object" not in component
|
||
|
|
assert "javascript:" not in component and "data:" not in component
|
||
|
|
assert 'parsed.protocol === "https:"' in security
|
||
|
|
assert "fetch(" not in endpoints and "axios" not in endpoints
|
||
|
|
assert ".hux-research" in styles
|
||
|
|
assert "body" not in styles and ":root" not in styles
|
||
|
|
assert "prefers-reduced-motion" in styles
|
||
|
|
|
||
|
|
|
||
|
|
def test_hux_research_sources_are_bounded_and_endpoint_only():
|
||
|
|
sources = sorted(RESEARCH.glob("*"))
|
||
|
|
assert {path.name for path in sources} == {
|
||
|
|
"ResearchWorkspace.tsx",
|
||
|
|
"endpoints.ts",
|
||
|
|
"index.ts",
|
||
|
|
"model.ts",
|
||
|
|
"security.ts",
|
||
|
|
"styles.css",
|
||
|
|
"types.ts",
|
||
|
|
}
|
||
|
|
for path in sources:
|
||
|
|
assert len(path.read_text(encoding="utf-8").splitlines()) <= 500
|
||
|
|
endpoints = (RESEARCH / "endpoints.ts").read_text(encoding="utf-8")
|
||
|
|
assert "expected_updated_at" in endpoints
|
||
|
|
assert '"PATCH"' not in endpoints and '"DELETE"' not in endpoints
|