gabriel / musehub public

test_mist_phase4_router_registration.py file-level

at sha256:3 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:0 fix: fall back to any indexed mpack in read_object_bytes when push mpac… · gabriel · Jun 17, 2026
1 """Phase 4 TDD: Mist JSON API router explicit registration in main.py.
2
3 Tests are written RED first. Run before touching main.py and
4 musehub/api/routes/musehub/__init__.py to confirm failure, then implement.
5
6 The mists router must be:
7 1. Listed in _DIRECT_REGISTERED in __init__.py (excluded from auto-discovery).
8 2. Explicitly imported and included in main.py alongside api_identities_router,
9 api_orgs_router, etc.
10 3. Reachable via the OpenAPI schema at /api/mists paths.
11
12 These tests do not require DB state — they operate against the live app
13 instance and its route table.
14 """
15 from __future__ import annotations
16
17 import pytest
18 from httpx import AsyncClient
19
20
21 class TestMistsRouterDirectRegistration:
22 def test_mists_excluded_from_auto_discovery(self) -> None:
23 """'mists' must appear in _DIRECT_REGISTERED so the package __init__
24 does not auto-include it."""
25 from musehub.api.routes.musehub import _DIRECT_REGISTERED
26 assert "mists" in _DIRECT_REGISTERED, (
27 "'mists' must be in _DIRECT_REGISTERED to prevent double-registration"
28 )
29
30 def test_mists_router_imported_in_main(self) -> None:
31 """main.py must import the mists router explicitly."""
32 import musehub.main as _main
33 src = open(_main.__file__).read()
34 assert "mists" in src, (
35 "main.py must explicitly import or reference the mists router"
36 )
37
38 def test_mists_router_registered_via_include_router(self) -> None:
39 """main.py must call app.include_router with the mists router."""
40 import musehub.main as _main
41 src = open(_main.__file__).read()
42 # The module import line + the include_router call must both be present.
43 assert "api_mists_router" in src or "mists_router" in src or (
44 "from musehub.api.routes.musehub.mists import router" in src
45 ), "main.py must explicitly include the mists router by name"
46
47
48 class TestMistsRouterOpenAPI:
49 @pytest.mark.asyncio
50 async def test_mists_paths_in_openapi_schema(self, client: AsyncClient) -> None:
51 """GET /api/openapi.json must list /api/mists paths."""
52 r = await client.get("/api/openapi.json")
53 assert r.status_code == 200
54 schema = r.json()
55 paths = schema.get("paths", {})
56 mist_paths = [p for p in paths if "/mists" in p]
57 assert len(mist_paths) > 0, (
58 f"No /mists paths found in OpenAPI schema. "
59 f"Registered paths sample: {list(paths.keys())[:20]}"
60 )
61
62 @pytest.mark.asyncio
63 async def test_create_mist_operation_in_schema(self, client: AsyncClient) -> None:
64 r = await client.get("/api/openapi.json")
65 schema = r.json()
66 paths = schema.get("paths", {})
67 assert "/api/mists" in paths or any(
68 p.endswith("/mists") for p in paths
69 ), f"POST /api/mists not found in schema paths: {[p for p in paths if 'mist' in p]}"
70
71 @pytest.mark.asyncio
72 async def test_explore_mists_operation_in_schema(self, client: AsyncClient) -> None:
73 r = await client.get("/api/openapi.json")
74 schema = r.json()
75 paths = schema.get("paths", {})
76 assert any("mists/explore" in p for p in paths), (
77 f"GET /api/mists/explore not found in schema"
78 )
79
80
81 class TestMistsRouterEndpoints:
82 @pytest.mark.asyncio
83 async def test_explore_endpoint_returns_200(self, client: AsyncClient) -> None:
84 """GET /api/mists/explore must return 200, not 404."""
85 r = await client.get("/api/mists/explore")
86 assert r.status_code == 200, (
87 f"Expected 200 from /api/mists/explore; got {r.status_code} — "
88 "router may not be registered"
89 )
90
91 @pytest.mark.asyncio
92 async def test_explore_response_is_valid_json(self, client: AsyncClient) -> None:
93 r = await client.get("/api/mists/explore")
94 assert r.status_code == 200
95 body = r.json()
96 assert "mists" in body
97
98 @pytest.mark.asyncio
99 async def test_get_nonexistent_mist_returns_404_not_422(
100 self, client: AsyncClient
101 ) -> None:
102 """A valid mist_id that doesn't exist should return 404, not 422 (unprocessable
103 entity from FastAPI if the route is missing or double-registered)."""
104 r = await client.get("/api/mists/Abc123Xyz789")
105 assert r.status_code in (404, 200), (
106 f"Expected 404 for unknown mist, got {r.status_code} — "
107 "422 indicates a routing problem (possibly double-registration)"
108 )
109
110 @pytest.mark.asyncio
111 async def test_no_duplicate_operation_ids(self, client: AsyncClient) -> None:
112 """Double-registration causes duplicate operationIds — FastAPI raises on startup."""
113 r = await client.get("/api/openapi.json")
114 assert r.status_code == 200, (
115 "OpenAPI schema returned non-200 — likely duplicate operationId "
116 "from double-registering the mists router"
117 )
118 schema = r.json()
119 # Collect all operationIds from mist-related paths.
120 op_ids: list[str] = []
121 for path, methods in schema.get("paths", {}).items():
122 if "mist" not in path:
123 continue
124 for method_data in methods.values():
125 if isinstance(method_data, dict) and "operationId" in method_data:
126 op_ids.append(method_data["operationId"])
127 assert len(op_ids) == len(set(op_ids)), (
128 f"Duplicate operationIds detected — router registered twice: "
129 f"{[x for x in op_ids if op_ids.count(x) > 1]}"
130 )