"""Regression tests for B-39: SQLite must run in WAL mode with a busy_timeout, since this app has five concurrent background tasks plus every HTTP handler sharing one database file, and the default rollback-journal mode lets a writer block every reader and fails a second writer immediately instead of waiting.""" import pytest from sqlalchemy.ext.asyncio import create_async_engine from app.db.base import _SQLITE_BUSY_TIMEOUT_MS, _register_sqlite_pragmas @pytest.fixture async def sqlite_engine(tmp_path): # WAL needs a real file (it writes a companion -wal/-shm file alongside it) — # ":memory:" wouldn't exercise the same path. engine = create_async_engine(f"sqlite+aiosqlite:///{tmp_path}/test.db") yield engine await engine.dispose() async def _pragma(engine, name: str): async with engine.connect() as conn: result = await conn.exec_driver_sql(f"PRAGMA {name}") return result.fetchone()[0] async def test_register_sqlite_pragmas_enables_wal_and_busy_timeout(sqlite_engine): _register_sqlite_pragmas(sqlite_engine) assert (await _pragma(sqlite_engine, "journal_mode")).lower() == "wal" assert await _pragma(sqlite_engine, "busy_timeout") == _SQLITE_BUSY_TIMEOUT_MS assert await _pragma(sqlite_engine, "synchronous") == 1 # NORMAL async def test_register_sqlite_pragmas_applies_to_every_new_connection(sqlite_engine): """The pool can open more than one underlying DBAPI connection over the engine's lifetime — the pragmas must be re-applied to each one, not just the first, or a later connection would silently fall back to SQLite's defaults.""" _register_sqlite_pragmas(sqlite_engine) async with sqlite_engine.connect() as first: await first.exec_driver_sql("PRAGMA journal_mode") async with sqlite_engine.connect() as second: result = await second.exec_driver_sql("PRAGMA busy_timeout") assert result.fetchone()[0] == _SQLITE_BUSY_TIMEOUT_MS def test_register_sqlite_pragmas_is_a_noop_for_other_dialects(): """Must not touch (or crash on) a non-sqlite engine — e.g. a future PostgreSQL DATABASE_URL, which neither needs nor understands these pragmas.""" class _FakeDialect: name = "postgresql" class _FakeEngine: dialect = _FakeDialect() _register_sqlite_pragmas(_FakeEngine()) # must not raise