test_hosted_dashboard_unit.py
python
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