test_hosted_dashboard_unit.py python
119 lines 4.1 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
1 """Unit tests for hosted governance dashboard (§HGD.12)."""
2
3 from __future__ import annotations
4
5 import json
6
7 import pytest
8
9 from cli.main import COMMANDS
10 from tools.hosted_dashboard.auth import generate_viewer_token
11 from tools.hosted_dashboard.bind import DEFAULT_PORT, validate_bind_address
12 from tools.hosted_dashboard.config import HostedDashboardConfigError, parse_hosted_dashboard_config
13 from tools.hosted_dashboard.envelope import build_meta, health_success
14 from tools.hosted_dashboard.handlers import is_track_q_act_path, match_repo_route
15 from tools.hosted_dashboard.hosts import host_allowed
16 from tools.hosted_dashboard.parsers import never_invent_done_on_garbage, parse_document_derived_gates
17 from tools.hosted_dashboard.scopes import refuse_write_scopes
18 from tools.hosted_dashboard.sources import SOURCE_IDS, is_known_source_id
19 from tools.hosted_dashboard.validators import validate_allowlist, valid_owner_repo_segment
20
21
22 def test_hosted_dashboard_registered() -> None:
23 assert "hosted-dashboard" in COMMANDS
24
25
26 def test_default_port_8766() -> None:
27 assert DEFAULT_PORT == 8766
28
29
30 @pytest.mark.parametrize(
31 ("bind", "allow", "expected"),
32 [
33 ("127.0.0.1", False, "127.0.0.1"),
34 ("localhost", False, "127.0.0.1"),
35 ("::1", False, "::1"),
36 ("0.0.0.0", False, None),
37 ("0.0.0.0", True, "0.0.0.0"),
38 ("192.168.1.1", False, None),
39 ],
40 )
41 def test_bind_refuse_without_allow_non_loopback(bind: str, allow: bool, expected: str | None) -> None:
42 assert validate_bind_address(bind, allow_non_loopback=allow) == expected
43
44
45 def test_viewer_token_entropy() -> None:
46 token = generate_viewer_token()
47 assert len(token) >= 32
48
49
50 def test_empty_allowlist_validates() -> None:
51 assert validate_allowlist([]) == []
52
53
54 def test_allowlist_shape() -> None:
55 pairs = validate_allowlist(["acme/kit", "acme"])
56 assert pairs == [("acme", "kit"), ("acme", None)]
57
58
59 def test_owner_repo_validators() -> None:
60 assert valid_owner_repo_segment("acme")
61 assert not valid_owner_repo_segment("../etc")
62 assert not valid_owner_repo_segment("a/b")
63
64
65 def test_write_scope_refuse() -> None:
66 assert refuse_write_scopes(["contents:write"]) == "write_scope_refused"
67 assert refuse_write_scopes(["contents:read"]) is None
68 assert refuse_write_scopes(None) is None
69
70
71 def test_upstream_host_allowlist() -> None:
72 assert host_allowed("api.github.com")
73 assert host_allowed("raw.githubusercontent.com")
74 assert not host_allowed("evil.example")
75 assert not host_allowed("127.0.0.1")
76 assert not host_allowed("169.254.169.254")
77
78
79 def test_envelope_authoritative_local() -> None:
80 meta = build_meta(source_id="github_contents", ref="main", content_sha256="abc")
81 assert meta["authoritative_workflow"] == "local"
82 health = health_success().to_dict()
83 assert health["result"] == {"status": "ok", "mode": "hosted-read-only"}
84
85
86 def test_parser_never_invents_done_on_garbage() -> None:
87 result = never_invent_done_on_garbage("!!! Status: definitely DONE somehow ???")
88 assert result.ok is False
89 assert all(p["status"] != "DONE" for p in result.phases)
90 assert result.phases == []
91
92
93 def test_parser_reads_build_status_rows() -> None:
94 text = "| **Alpha** | Auto | **DONE** |\n| **Beta** | Auto | **WIP** |\n"
95 result = parse_document_derived_gates(roadmap_text=text, handover_text=None)
96 assert result.ok is True
97 assert {"id": "Alpha", "status": "DONE"} in result.phases
98
99
100 def test_config_unknown_key_refuse() -> None:
101 with pytest.raises(HostedDashboardConfigError):
102 parse_hosted_dashboard_config({"enabled": False, "surprise": True})
103
104
105 def test_source_id_closed_vocabulary() -> None:
106 assert is_known_source_id("github_contents")
107 assert not is_known_source_id("arbitrary_s3")
108 assert "musehub_read" in SOURCE_IDS
109
110
111 def test_track_q_paths_detected() -> None:
112 assert is_track_q_act_path("/api/review/freeze")
113 assert is_track_q_act_path("/api/governance-sync")
114 assert not is_track_q_act_path("/api/org/summary")
115
116
117 def test_repo_route_match() -> None:
118 assert match_repo_route("/api/repos/acme/kit/roadmap") == ("acme", "kit", "roadmap")
119 assert match_repo_route("/api/repos/acme/kit/unknown") is None
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago