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