"""Tests for transcription.db — schema bootstrap and session factory.""" import pytest from sqlalchemy import inspect from sqlalchemy import text from sqlalchemy.ext.asyncio import create_async_engine from sqlmodel import SQLModel from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.pool import StaticPool def _in_memory_engine(): """Create a fresh in-memory SQLite engine for isolated db tests.""" return create_async_engine( "sqlite+aiosqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) class TestSchemaBootstrap: """Verify create_all produces the expected table set.""" @pytest.mark.asyncio async def test_create_all_creates_expected_tables(self): """After create_all(), document, job, and transcript tables exist.""" engine = _in_memory_engine() # Ensure models are imported so metadata is populated import transcription.db as db_module from transcription.models import Document # noqa: F401 from transcription.models import Job # noqa: F401 from transcription.models import Transcript # noqa: F401 await db_module.create_all(engine=engine) async with engine.connect() as connection: table_names = set(await connection.run_sync(lambda conn: inspect(conn).get_table_names())) await engine.dispose() assert "document" in table_names assert "job" in table_names assert "transcript" in table_names class TestSessionFactory: """Verify get_session yields and cleans up sessions.""" @pytest.mark.asyncio async def test_get_session_yields_session(self): """get_session() yields a usable Session object.""" engine = _in_memory_engine() async with engine.begin() as connection: await connection.run_sync(SQLModel.metadata.create_all) import transcription.db as db_module factory = db_module.async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with db_module.get_session(session_factory=factory) as session: assert isinstance(session, AsyncSession) await engine.dispose() @pytest.mark.asyncio async def test_session_is_closed_after_generator_exit(self): """After the context manager exits, the session is closed.""" engine = _in_memory_engine() async with engine.begin() as connection: await connection.run_sync(SQLModel.metadata.create_all) import transcription.db as db_module factory = db_module.async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with db_module.get_session(session_factory=factory) as session: # Session is usable inside the context await session.exec(text("SELECT 1")) captured = session assert captured.sync_session._transaction is None await engine.dispose() class TestBootstrapPolicy: """Verify schema bootstrap policy defaults and overrides.""" def test_production_defaults_to_no_bootstrap(self): """Production defaults to explicit non-bootstrap startup behavior.""" from transcription.config import Settings from transcription.db import should_bootstrap_schema settings = Settings( openrouter_api_key="test-key", environment="production", bootstrap_schema_on_startup=None, ) assert should_bootstrap_schema(settings) is False def test_development_defaults_to_bootstrap(self): """Development defaults to schema bootstrap for local workflows.""" from transcription.config import Settings from transcription.db import should_bootstrap_schema settings = Settings( openrouter_api_key="test-key", environment="development", bootstrap_schema_on_startup=None, ) assert should_bootstrap_schema(settings) is True def test_explicit_override_wins(self): """Explicit bootstrap_schema_on_startup overrides environment default.""" from transcription.config import Settings from transcription.db import should_bootstrap_schema settings = Settings( openrouter_api_key="test-key", environment="production", bootstrap_schema_on_startup=True, ) assert should_bootstrap_schema(settings) is True class TestRuntimeSqliteEngineConfig: """Verify sqlite runtime engine configuration from settings.""" def test_in_memory_sqlite_uses_static_pool_by_default(self): """In-memory sqlite URLs get StaticPool for consistent connections.""" from transcription.config import Settings from transcription.db.runtime import _build_engine settings = Settings(openrouter_api_key="test-key", database_url="sqlite://") engine = _build_engine(settings) assert engine.sync_engine.pool.__class__.__name__ == "StaticPool" def test_file_sqlite_does_not_force_static_pool(self): """File-backed sqlite URLs keep default pool behavior.""" from transcription.config import Settings from transcription.db.runtime import _build_engine settings = Settings(openrouter_api_key="test-key", database_url="sqlite:///./runtime-test.db") engine = _build_engine(settings) assert engine.sync_engine.pool.__class__.__name__ != "StaticPool"