test_hosted_dashboard_e2e.py python
118 lines 3.7 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 3 days ago
1 """End-to-end tests for hosted governance dashboard (§HGD.12)."""
2
3 from __future__ import annotations
4
5 import os
6 import signal
7 import subprocess
8 import sys
9 import time
10 from pathlib import Path
11
12 from cli.kit_root import kit_root
13 from tests.fixtures.hosted_dashboard import (
14 HANDOVER_MD,
15 MARKER_YAML,
16 ROADMAP_MD,
17 start_test_hosted,
18 write_hosted_config,
19 )
20 from tools.hosted_dashboard.server import STATIC_ROOT
21
22
23 def test_ui_honesty_banner_present() -> None:
24 html = (STATIC_ROOT / "index.html").read_text(encoding="utf-8")
25 assert "Authoritative workflow remains" in html
26 assert "local" in html.lower()
27 js = (STATIC_ROOT / "assets" / "dashboard.js").read_text(encoding="utf-8")
28 assert "localStorage" not in js
29 assert "sessionStorage" not in js
30
31
32 def test_authenticated_flow_org_docs_gates() -> None:
33 handle, client, _ = start_test_hosted()
34 try:
35 status, health = client.get("/api/health", auth=False)
36 assert status == 200
37
38 status, summary = client.get("/api/org/summary")
39 assert status == 200
40 assert summary["result"]["repos"]
41
42 for action in ("roadmap", "handover", "gates", "config-marker"):
43 status, payload = client.get(f"/api/repos/acme/kit-demo/{action}")
44 assert status == 200
45 assert payload["ok"] is True
46
47 # Honesty banner in static UI bytes
48 status, html = client.get("/", auth=False)
49 assert status == 200
50 assert isinstance(html, str)
51 assert "Authoritative workflow remains" in html
52 finally:
53 handle.shutdown()
54
55
56 def test_cli_preview_sigint_exit_zero_no_vcs_mutation(tmp_path: Path, monkeypatch) -> None:
57 """Start ok hosted-dashboard briefly; SIGINT → exit 0; no .git/.muse mutation."""
58 config_path = tmp_path / ".overseer" / "config.yaml"
59 write_hosted_config(
60 config_path,
61 {
62 "enabled": True,
63 "org_allowlist": ["acme/kit-demo"],
64 "sources": {
65 "github_contents": True,
66 "github_meta": True,
67 "github_checks_advisory": False,
68 "musehub_read": False,
69 },
70 },
71 )
72 git_dir = tmp_path / ".git"
73 muse_dir = tmp_path / ".muse"
74 git_dir.mkdir()
75 muse_dir.mkdir()
76 sentinel = git_dir / "HEAD"
77 sentinel.write_text("ref: refs/heads/main\n", encoding="utf-8")
78 before = sentinel.read_bytes()
79 muse_before = {p.name: p.stat().st_mtime_ns for p in muse_dir.iterdir()} if any(muse_dir.iterdir()) else {}
80
81 env = os.environ.copy()
82 env["OVERSEER_HOSTED_DASHBOARD_VIEWER_TOKEN"] = "a" * 32
83 env["PYTHONPATH"] = str(kit_root())
84 # Use a free port via ephemeral; pick high port and hope available, or skip listen refuse.
85 from tests.fixtures.hosted_dashboard import free_port
86
87 port = free_port()
88 proc = subprocess.Popen(
89 [
90 sys.executable,
91 "-m",
92 "cli.main",
93 "hosted-dashboard",
94 "--port",
95 str(port),
96 "--bind",
97 "127.0.0.1",
98 "--config",
99 str(config_path),
100 ],
101 cwd=str(tmp_path),
102 env=env,
103 stdout=subprocess.PIPE,
104 stderr=subprocess.PIPE,
105 )
106 try:
107 time.sleep(0.6)
108 proc.send_signal(signal.SIGINT)
109 code = proc.wait(timeout=5)
110 # KeyboardInterrupt path returns 0; some platforms may deliver as non-zero — accept 0/-2/130.
111 assert code in {0, -2, 130} or code == 0
112 finally:
113 if proc.poll() is None:
114 proc.kill()
115 proc.wait(timeout=3)
116
117 assert sentinel.read_bytes() == before
118 assert muse_before == ({p.name: p.stat().st_mtime_ns for p in muse_dir.iterdir()} if any(muse_dir.iterdir()) else {})
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 3 days ago