gabriel / musehub public

test_canary.py file-level

at sha256:f · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:e fix(9A-4 F7): env alias + staging secrets for overseer provenance gate.… · aaronrene · Jul 13, 2026
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 f"{'\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 f"{'\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 datetime import datetime, timezone
76 from musehub.core.genesis import compute_identity_id, compute_repo_id
77 from musehub.db.musehub_repo_models import MusehubRepo
78 _created_at = datetime.now(tz=timezone.utc)
79 _owner_id = compute_identity_id(b"canary")
80 r = MusehubRepo(
81 repo_id=compute_repo_id(_owner_id, "canary", "code", _created_at.isoformat()),
82 name="canary", owner="canary",
83 slug="canary", visibility="public", owner_user_id=_owner_id,
84 created_at=_created_at, updated_at=_created_at,
85 )
86 db_session.add(r)
87 await db_session.flush()
88 result = await db_session.execute(
89 text("SELECT COUNT(*) FROM musehub_repos WHERE owner = 'canary'")
90 )
91 assert result.scalar() == 1
92
93 async def test_previous_test_data_is_gone(self, db_session: AsyncSession) -> None:
94 """This runs after test_insert_is_visible_within_test β€” canary row must be gone."""
95 result = await db_session.execute(
96 text("SELECT COUNT(*) FROM musehub_repos WHERE owner = 'canary'")
97 )
98 assert result.scalar() == 0, (
99 "Canary row from previous test is still present β€” TRUNCATE isolation broken"
100 )
101
102
103 class TestTruncateCoverage:
104 """Layer 3b β€” TRUNCATE SQL covers every table in Base.metadata.
105
106 This catches the recurring "model registered after TRUNCATE SQL was
107 pre-computed" bug: conftest builds _TRUNCATE_SQL at import time from
108 Base.metadata.sorted_tables. If a DB model module is imported for the
109 first time inside a test (after that point), its table is NOT in the
110 TRUNCATE β€” so data from that test leaks into later tests.
111
112 Fix: ensure every model module is imported in conftest BEFORE
113 _TRUNCATE_SQL is computed.
114 """
115
116 def test_truncate_sql_covers_all_orm_tables(self) -> None:
117 import tests.conftest as cf
118 from musehub.db.database import Base
119
120 orm_tables = {t.name for t in Base.metadata.tables.values()}
121 # Parse the table names out of the pre-computed TRUNCATE statement.
122 # Format: "TRUNCATE table1, table2, ... RESTART IDENTITY CASCADE"
123 truncate_body = cf._TRUNCATE_SQL.split("TRUNCATE ", 1)[1]
124 truncate_body = truncate_body.split(" RESTART ")[0]
125 truncated = {t.strip() for t in truncate_body.split(",")}
126 missing = orm_tables - truncated
127 assert not missing, (
128 "These ORM tables are NOT in conftest._TRUNCATE_SQL β€” data from "
129 "tests that use them will leak into later tests.\n"
130 "Fix: import the model module in conftest.py BEFORE _TRUNCATE_SQL "
131 f"is computed (i.e. before line ~168):\n"
132 f"{'\n'.join(f' {t}' for t in sorted(missing))}"
133 )
134
135
136 class TestSessionWiring:
137 """Layer 4 β€” db_session fixture is pointing at the test DB, not prod."""
138
139 async def test_connected_to_test_database(self, db_session: AsyncSession) -> None:
140 result = await db_session.execute(text("SELECT current_database()"))
141 db_name = result.scalar()
142 assert "test" in db_name, (
143 f"Tests are running against '{db_name}', not the test DB β€” "
144 "check TEST_DATABASE_URL and the db_session fixture"
145 )
146
147 async def test_asyncsessionlocal_uses_test_engine(self, db_session: AsyncSession) -> None:
148 """AsyncSessionLocal() (used by executors) must point at the test DB."""
149 from musehub.db import database
150 result = await db_session.execute(text("SELECT current_database()"))
151 test_db = result.scalar()
152 # The conftest swaps database._async_session_factory; verify it's active.
153 async with database._async_session_factory() as s:
154 r2 = await s.execute(text("SELECT current_database()"))
155 executor_db = r2.scalar()
156 assert executor_db == test_db, (
157 f"database._async_session_factory points at '{executor_db}' "
158 f"but db_session is on '{test_db}' β€” fixture swap broken"
159 )