fix(test): router-smoke tests leak-proof against full-suite order (#932)

tests/test_router_smoke.py showed ~10 `sqlite3.OperationalError: no such
table: jobs` failures in the full suite (and in isolation on a clean data
dir), but passed when a schema-creating module ran first.

Root cause: the `client` fixture builds a bare `TestClient(app)` with no
`with` block, so the FastAPI lifespan never runs — and `init_db()` (the
only place the schema is created) lives in that lifespan (main.py). The
smoke tests therefore free-rode on whatever schema an earlier module left
on the active DB. A module that reloads `core.config`/`core.db` and leaves
`core.db.DB_PATH` pointed at a fresh, schema-less DB (test_pronunciation_api's
`importlib.reload` teardown restores the env var but never re-runs init_db
on the restored data dir) strands router-smoke on a DB with no tables ->
every DB-backed route 500s. Same class as #878 / #917.

Fix (test-only, zero blast radius): the `client` fixture now calls
`core.db.init_db()` against whatever DB is active at run time before serving
requests — the same `init_db()` pattern test_api.py / test_personas_api.py
use. Because it targets the live `core.db.DB_PATH`, it re-creates the schema
regardless of which path any prior module left active, making the suite
self-sufficient and order-independent.

Verify:
- `pytest tests/test_router_smoke.py` alone: 10 failed -> 24 passed
- `pytest tests/test_pronunciation_api.py tests/test_router_smoke.py`
  (deterministic reproducer): 10 failed -> 38 passed
- `pytest tests/` full suite: 2215 passed, 20 skipped, 10 xfailed,
  4 xpassed, 0 failed / 0 errors

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Palash Debnath
2026-07-03 17:47:45 +05:30
committed by GitHub
co-authored by mergetest Claude Fable 5
parent 0d80ab2cb0
commit ed0b0b63cf
+23 -4
View File
@@ -17,12 +17,31 @@ os.environ.setdefault("OMNIVOICE_DISABLE_FILE_LOG", "1")
def client():
# Lazy import so test_api.py's session fixtures can mock the model first
# if both suites run together.
# `client=("127.0.0.1", 50000)` so `request.client.host` resolves to a
# loopback address — the system router is now gated by a router-level
# `require_loopback` dependency. Smoke tests are happy-path tests and
# should pass the gate.
from fastapi.testclient import TestClient
from main import app
# Create the DB schema explicitly against whatever DB is active *now*.
# A bare `TestClient(app)` (no `with` block) never enters the app
# lifespan, so the lifespan's `init_db()` — the only place the schema is
# laid down (main.py) — never runs. These smoke tests therefore used to
# free-ride on the schema some *earlier* module happened to create on the
# shared data dir. That made them order-dependent: run first / alone, or
# after a module that reloads `core.config`/`core.db` and leaves
# `core.db.DB_PATH` pointed at a fresh, schema-less DB (e.g.
# test_pronunciation_api's `importlib.reload` teardown), and every
# DB-backed route 500s with `no such table: jobs` / `voice_profiles`.
# Seeding the schema here — the same `init_db()` call test_api.py /
# test_personas_api.py use — makes the suite self-sufficient and
# leak-proof against full-suite ordering (same class as #878 / #917).
# Uses the live `core.db` from sys.modules so it targets the exact
# DB_PATH the app's routers resolve to.
import core.db
core.db.init_db()
# `client=("127.0.0.1", 50000)` so `request.client.host` resolves to a
# loopback address — the system router is gated by a router-level
# `require_loopback` dependency. Smoke tests are happy-path tests and
# should pass the gate.
return TestClient(app, client=("127.0.0.1", 50000))