Files
transcription/tests/conftest.py
T
Jim Lancaster 6f4accf275
Quality Gate / gate (push) Successful in 2m38s
Implement rec - Phase 4 complete
2026-09-02 16:23:09 -05:00

121 lines
4.3 KiB
Python

"""Shared test fixtures.
Every test gets a fresh in-memory SQLite database so tests are
isolated, fast, and leave no artifacts on disk.
"""
import os
from pathlib import Path
import pytest
import pytest_asyncio
from sqlmodel import Session
from sqlmodel import SQLModel
from sqlmodel import create_engine
from sqlmodel.pool import StaticPool
from transcription.config import Settings
from transcription.config import SqliteSettings
from transcription.db.engine import get_database_url
from transcription.db.engine import get_engine
from transcription.db.session import dispose_session_factory
from transcription.db.session import get_session_factory
from transcription.db.session import session_scope
from transcription.services.documents import DocumentService
from transcription.services.jobs import JobService
@pytest.fixture(autouse=True, scope="session")
def isolate_settings_from_local_env_files(tmp_path_factory):
"""Point `Settings` at a controlled stub env file instead of a developer one.
`Settings()` resolves its env file through the shared config seam, so a repository-root
pytest run would otherwise read a developer's real `.env.production` into tests that
assert declared defaults.
The stub mirrors what `.github/workflows/quality-gate.yml` writes in CI: only
`OPENROUTER_API_KEY`, which is required and which many tests need `get_settings()` to
find. Everything else falls back to declared defaults, so local and CI runs agree. This
stays a file rather than a process environment variable because
`test_config.py::test_requires_api_key` asserts the missing-key failure via
`_env_file=None`. Guarded by `tests/test_config_isolation.py`.
"""
stub = tmp_path_factory.mktemp("settings-env") / ".env.test"
stub.write_text("OPENROUTER_API_KEY=test-placeholder-not-a-real-key\n", encoding="utf-8")
original = os.environ.get("ENV_FILE")
os.environ["ENV_FILE"] = str(stub)
try:
yield
finally:
if original is None:
os.environ.pop("ENV_FILE", None)
else:
os.environ["ENV_FILE"] = original
@pytest.fixture
def session():
"""Provide a clean synchronous database session for sync tests."""
engine = create_engine(
"sqlite://",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.drop_all(engine)
SQLModel.metadata.create_all(engine)
with Session(engine) as sync_session:
yield sync_session
@pytest_asyncio.fixture
async def default_settings(tmp_path):
"""Provide default settings for tests."""
database_path = tmp_path / "tests.db"
settings = Settings(
openrouter_api_key="test-key",
database=SqliteSettings(path=str(database_path)),
environment="test",
)
db_url = get_database_url(settings)
if Path(str(get_engine(database_url=db_url).url.database)).resolve() != database_path.resolve():
raise RuntimeError(f"Refusing to initialize destructive test fixtures against {db_url}")
await dispose_session_factory(db_url)
engine = get_engine(database_url=db_url)
# Cached in-memory engines persist across tests; reset schema per test for isolation.
async with engine.begin() as connection:
await connection.run_sync(SQLModel.metadata.drop_all)
await connection.run_sync(SQLModel.metadata.create_all)
yield settings
await dispose_session_factory(db_url)
@pytest_asyncio.fixture
async def async_session(default_settings: Settings):
"""Provide a clean asynchronous database session for async tests."""
db_url = get_database_url(default_settings)
async with session_scope(database_url=db_url) as async_session:
yield async_session
@pytest.fixture
def default_session_factory(default_settings: Settings):
"""Provide a base fixture for tests that require database access."""
db_url = get_database_url(default_settings)
session_factory = get_session_factory(database_url=db_url)
return session_factory
@pytest.fixture
def job_service(default_session_factory) -> JobService:
"""Provide a JobService instance for testing."""
return JobService(session_factory=default_session_factory)
@pytest.fixture
def document_service(default_session_factory) -> DocumentService:
"""Provide a DocumentService instance for testing."""
return DocumentService(session_factory=default_session_factory)