test_q1_app_unit.py python
112 lines 3.6 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 9 hours ago
1 """Unit tests for Track Q / Q1 overseer app (§Q0.12)."""
2
3 from __future__ import annotations
4
5 import json
6 from argparse import Namespace
7
8 import pytest
9
10 from cli.context import CliContext
11 from cli.main import COMMANDS
12 from cli.output import OutputContext
13 from tools.app.auth import generate_csrf_token, generate_session_credential
14 from tools.app.bind import DEFAULT_PORT, port_is_available, validate_bind_address
15 from tools.app.cors import origin_allowed
16 from tools.app.engine import handle_governance_sync, handle_review_freeze
17 from tools.app.envelope import ApiEnvelope, bad_request
18
19
20 def test_app_registered_in_commands() -> None:
21 assert "app" in COMMANDS
22
23
24 @pytest.mark.parametrize(
25 ("bind", "expected"),
26 [
27 ("127.0.0.1", "127.0.0.1"),
28 ("localhost", "127.0.0.1"),
29 ("::1", "::1"),
30 ("0.0.0.0", None),
31 ("::", None),
32 ("192.168.1.1", None),
33 ],
34 )
35 def test_bind_allowlist(bind: str, expected: str | None) -> None:
36 assert validate_bind_address(bind) == expected
37
38
39 def test_default_port_constant() -> None:
40 assert DEFAULT_PORT == 8765
41
42
43 def test_session_and_csrf_entropy() -> None:
44 session = generate_session_credential()
45 csrf = generate_csrf_token()
46 assert len(session) >= 32
47 assert len(csrf) >= 32
48
49
50 def test_envelope_json_roundtrip() -> None:
51 env = ApiEnvelope(ok=True, exit_code=0, error=None, result={"x": 1})
52 parsed = json.loads(env.to_json_bytes().decode("utf-8"))
53 assert parsed["ok"] is True
54 assert parsed["exit_code"] == 0
55
56
57 def test_unknown_post_keys_rejected() -> None:
58 ctx = CliContext.create(output=OutputContext())
59 result = handle_review_freeze(ctx, {"path": "docs/x.md", "extra": True})
60 assert result.http_status == 400
61 assert result.error == "unknown_fields"
62
63
64 def test_review_defaults_dry_run_true(tmp_path) -> None:
65 from tests.fixtures.app import seed_app_repo
66
67 seed_app_repo(tmp_path)
68 ctx = CliContext.create(cwd=tmp_path, output=OutputContext())
69 result = handle_review_freeze(ctx, {"path": "docs/ROADMAP.md"}, repo_arg=str(tmp_path))
70 assert result.exit_code is not None
71
72
73 def test_governance_sync_defaults_non_write(tmp_path) -> None:
74 from tests.fixtures.app import seed_app_repo
75 from tests.support import make_runner, ok
76
77 seed_app_repo(tmp_path)
78 runner = make_runner(
79 {
80 "git rev-parse --abbrev-ref HEAD": ok("main"),
81 "git status --porcelain": ok(""),
82 "git rev-parse origin/main": ok("cafebabe"),
83 "gh pr list --state merged --limit 5 --json number,title,mergeCommit,mergedAt": ok("[]"),
84 "git remote get-url origin": ok("[email protected]:owner/repo.git"),
85 "git merge-base --is-ancestor": ok(""),
86 }
87 )
88 ctx = CliContext.create(cwd=tmp_path, runner=runner, output=OutputContext())
89 result = handle_governance_sync(ctx, {}, repo_arg=str(tmp_path))
90 assert result.result is not None
91 assert result.result.get("dry_run") is True
92
93
94 def test_bad_request_helper() -> None:
95 env = bad_request("unknown_fields")
96 assert env.http_status == 400
97
98
99 def test_cors_origin_allowlist() -> None:
100 port = 8765
101 assert origin_allowed(f"http://127.0.0.1:{port}", port)
102 assert origin_allowed(f"http://localhost:{port}", port)
103 assert origin_allowed(f"http://[::1]:{port}", port)
104 assert not origin_allowed("http://evil.example:8765", port)
105
106
107 def test_occupied_default_port_reports_busy() -> None:
108 import socket
109
110 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
111 sock.bind(("127.0.0.1", DEFAULT_PORT))
112 assert port_is_available("127.0.0.1", DEFAULT_PORT) is False
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 9 hours ago