gabriel / musehub public
test_canary.py python
151 lines 6.4 KB
Raw
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ breaking 155 days ago
1 """Canary tests — run these first to rule out infra before debugging test failures.
2
3 Layer 1: DB reachable
4 Layer 2: All ORM tables exist in the test schema
5 Layer 3: TRUNCATE isolation works (no state bleeds between tests)
6 Layer 4: Session factory is wired to the test DB (not prod)
7 Layer 5: Unique constraints fire correctly (proving TRUNCATE reset identity)
8 """
9 from __future__ import annotations
10
11 import pytest
12 import pytest_asyncio
13 from sqlalchemy import text
14 from sqlalchemy.ext.asyncio import AsyncSession
15
16 from musehub.db.database import Base
17
18
19 def test_hello() -> None:
20 print("hello world")
21
22
23 class TestDBReachable:
24 """Layer 1 — can we talk to Postgres at all?"""
25
26 async def test_connection(self, db_session: AsyncSession) -> None:
27 result = await db_session.execute(text("SELECT 1"))
28 assert result.scalar() == 1
29
30 async def test_postgres_version(self, db_session: AsyncSession) -> None:
31 result = await db_session.execute(text("SELECT version()"))
32 version = result.scalar()
33 assert version is not None
34 assert "PostgreSQL" in version
35
36
37 class TestSchemaComplete:
38 """Layer 2 — every ORM model has a matching table in the test DB."""
39
40 async def test_all_orm_tables_exist(self, db_session: AsyncSession) -> None:
41 result = await db_session.execute(
42 text(
43 "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename"
44 )
45 )
46 live_tables = {row[0] for row in result.fetchall()}
47 orm_tables = {t.name for t in Base.metadata.tables.values()}
48 missing = orm_tables - live_tables
49 assert not missing, (
50 f"ORM models have no matching DB table — run create_all or add a migration:\n"
51 + "\n".join(f" {t}" for t in sorted(missing))
52 )
53
54 async def test_no_orphan_db_tables(self, db_session: AsyncSession) -> None:
55 """Tables in DB but not in ORM — usually a dropped model without a migration."""
56 result = await db_session.execute(
57 text(
58 "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename"
59 )
60 )
61 live_tables = {row[0] for row in result.fetchall()}
62 orm_tables = {t.name for t in Base.metadata.tables.values()}
63 # alembic_version is managed by Alembic, not our ORM — exclude it.
64 orphans = live_tables - orm_tables - {"alembic_version"}
65 assert not orphans, (
66 f"DB has tables with no ORM model — dead schema, needs a DROP migration:\n"
67 + "\n".join(f" {t}" for t in sorted(orphans))
68 )
69
70
71 class TestTruncateIsolation:
72 """Layer 3 — each test starts with a clean slate."""
73
74 async def test_insert_is_visible_within_test(self, db_session: AsyncSession) -> None:
75 from musehub.db.musehub_models import MusehubRepo
76 r = MusehubRepo(name="canary", owner="canary", slug="canary",
77 visibility="public", owner_user_id="uid-canary")
78 db_session.add(r)
79 await db_session.flush()
80 result = await db_session.execute(
81 text("SELECT COUNT(*) FROM musehub_repos WHERE owner = 'canary'")
82 )
83 assert result.scalar() == 1
84
85 async def test_previous_test_data_is_gone(self, db_session: AsyncSession) -> None:
86 """This runs after test_insert_is_visible_within_test — canary row must be gone."""
87 result = await db_session.execute(
88 text("SELECT COUNT(*) FROM musehub_repos WHERE owner = 'canary'")
89 )
90 assert result.scalar() == 0, (
91 "Canary row from previous test is still present — TRUNCATE isolation broken"
92 )
93
94
95 class TestTruncateCoverage:
96 """Layer 3b — TRUNCATE SQL covers every table in Base.metadata.
97
98 This catches the recurring "model registered after TRUNCATE SQL was
99 pre-computed" bug: conftest builds _TRUNCATE_SQL at import time from
100 Base.metadata.sorted_tables. If a DB model module is imported for the
101 first time inside a test (after that point), its table is NOT in the
102 TRUNCATE — so data from that test leaks into later tests.
103
104 Fix: ensure every model module is imported in conftest BEFORE
105 _TRUNCATE_SQL is computed.
106 """
107
108 def test_truncate_sql_covers_all_orm_tables(self) -> None:
109 import tests.conftest as cf
110 from musehub.db.database import Base
111
112 orm_tables = {t.name for t in Base.metadata.tables.values()}
113 # Parse the table names out of the pre-computed TRUNCATE statement.
114 # Format: "TRUNCATE table1, table2, ... RESTART IDENTITY CASCADE"
115 truncate_body = cf._TRUNCATE_SQL.split("TRUNCATE ", 1)[1]
116 truncate_body = truncate_body.split(" RESTART ")[0]
117 truncated = {t.strip() for t in truncate_body.split(",")}
118 missing = orm_tables - truncated
119 assert not missing, (
120 "These ORM tables are NOT in conftest._TRUNCATE_SQL — data from "
121 "tests that use them will leak into later tests.\n"
122 "Fix: import the model module in conftest.py BEFORE _TRUNCATE_SQL "
123 "is computed (i.e. before line ~168):\n"
124 + "\n".join(f" {t}" for t in sorted(missing))
125 )
126
127
128 class TestSessionWiring:
129 """Layer 4 — db_session fixture is pointing at the test DB, not prod."""
130
131 async def test_connected_to_test_database(self, db_session: AsyncSession) -> None:
132 result = await db_session.execute(text("SELECT current_database()"))
133 db_name = result.scalar()
134 assert "test" in db_name, (
135 f"Tests are running against '{db_name}', not the test DB — "
136 "check TEST_DATABASE_URL and the db_session fixture"
137 )
138
139 async def test_asyncsessionlocal_uses_test_engine(self, db_session: AsyncSession) -> None:
140 """AsyncSessionLocal() (used by executors) must point at the test DB."""
141 from musehub.db import database
142 result = await db_session.execute(text("SELECT current_database()"))
143 test_db = result.scalar()
144 # The conftest swaps database._async_session_factory; verify it's active.
145 async with database._async_session_factory() as s:
146 r2 = await s.execute(text("SELECT current_database()"))
147 executor_db = r2.scalar()
148 assert executor_db == test_db, (
149 f"database._async_session_factory points at '{executor_db}' "
150 f"but db_session is on '{test_db}' — fixture swap broken"
151 )
File History 1 commit
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ 155 days ago