Files
alembic/tests/conftest.py
T
andrew e49d12a72b Add test suite (T1)
First automated coverage. 38 tests, run against the built image (which has the
app deps); tests/ and requirements-dev.txt are not baked into the runtime image.

- tests/test_dedup_gating.py: the safety-critical one. Drives dedup-library.sh's
  process_group with --apply + --only-paths and asserts ONLY the confirmed path
  is deleted and the higher-ranked FLAC is kept.
- tests/test_pipeline_runner.py: lock skip, use_lock=False bypass, lock release,
  success/failure/timeout recording.
- tests/test_db.py: schema version + idempotent migrations, FK-safe job-run
  pruning, stale-run reconciliation.
- tests/test_credential_service.py: conf-field substitution incl. newline-
  injection safety, secret/non-secret classification, cookie-expiry parsing.
- tests/test_playlist_service.py: cron round-trips, name validation, render.
- tests/test_diagnostics.py: /health + /setup checks.
- conftest.py points every path setting at throwaway temp dirs and a temp DB, so
  tests never touch a real library, /config, or the network. See tests/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:00:07 -06:00

68 lines
1.9 KiB
Python

"""Test setup.
Every path-derived setting must point at throwaway dirs BEFORE any app module
imports, because app.db builds its SQLAlchemy engine from settings at import
time. So this runs at module load (conftest is imported before test modules).
"""
import os
import tempfile
from pathlib import Path
from cryptography.fernet import Fernet
_REPO = Path(__file__).resolve().parent.parent
_TMP = Path(tempfile.mkdtemp(prefix="alembic-tests-"))
(_TMP / "config" / "pipeline").mkdir(parents=True, exist_ok=True)
(_TMP / "music").mkdir(parents=True, exist_ok=True)
_KEY = _TMP / "master.key"
_KEY.write_bytes(Fernet.generate_key())
os.environ["ALEMBIC_CONFIG_DIR"] = str(_TMP / "config")
os.environ["MUSIC_DATA_DIR"] = str(_TMP / "music")
os.environ["PIPELINE_DIR"] = str(_REPO / "pipeline")
os.environ["ENCRYPTION_KEY_FILE"] = str(_KEY)
os.environ["SESSION_SECRET"] = "test-only-secret"
# Make sure no stray OIDC env leaks in from the host shell.
for _v in ("POCKETID_ISSUER", "POCKETID_CLIENT_ID", "POCKETID_CLIENT_SECRET", "ALLOWED_EMAIL"):
os.environ.pop(_v, None)
import pytest # noqa: E402
_TABLES = [
"job_runs", "dedup_candidates", "dedup_runs", "genre_candidates", "genre_runs",
"manual_imports", "manual_fix_audit", "playlists", "secrets", "scheduled_jobs",
"users", "app_settings",
]
@pytest.fixture(autouse=True)
def fresh_db():
"""Each test gets an initialized, empty database (the engine is bound to the
one temp DB path, so clear rows between tests rather than rebinding)."""
from sqlalchemy import text
from app.db import engine, init_db
init_db()
with engine.begin() as conn:
for table in _TABLES:
conn.execute(text(f"DELETE FROM {table}"))
yield
@pytest.fixture()
def db():
from app.db import SessionLocal
session = SessionLocal()
try:
yield session
finally:
session.close()
@pytest.fixture()
def config_dir():
return _TMP / "config"