e49d12a72b
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>
62 lines
2.2 KiB
Python
62 lines
2.2 KiB
Python
import time
|
|
|
|
from sqlalchemy import text
|
|
|
|
from app import db as appdb
|
|
|
|
|
|
def test_init_db_sets_version_and_is_idempotent():
|
|
with appdb.engine.begin() as conn:
|
|
version = conn.execute(text("SELECT version FROM schema_version")).fetchone()[0]
|
|
assert version >= 3 # all shipped migrations applied
|
|
# Re-running must not raise (e.g. duplicate-column on a re-applied ALTER).
|
|
appdb.init_db()
|
|
appdb.init_db()
|
|
|
|
|
|
def test_prune_old_job_runs_fk_safe():
|
|
now = time.time()
|
|
old = now - 100 * 86400
|
|
with appdb.engine.begin() as conn:
|
|
conn.execute(
|
|
text(
|
|
"INSERT INTO job_runs (id, job_key, started_at, status, triggered_by) "
|
|
"VALUES (1,'old',:o,'success','schedule'),(2,'new',:n,'success','schedule')"
|
|
),
|
|
{"o": old, "n": now},
|
|
)
|
|
conn.execute(
|
|
text(
|
|
"INSERT INTO manual_imports (id, source_path, imported_by, imported_at, status, job_run_id) "
|
|
"VALUES (1,'x','me',:o,'done',1)"
|
|
),
|
|
{"o": old},
|
|
)
|
|
|
|
deleted = appdb.prune_old_job_runs(max_age_days=90)
|
|
assert deleted == 1
|
|
with appdb.engine.begin() as conn:
|
|
remaining = [r[0] for r in conn.execute(text("SELECT job_key FROM job_runs"))]
|
|
mi_ref = conn.execute(text("SELECT job_run_id FROM manual_imports WHERE id=1")).fetchone()[0]
|
|
mi_count = conn.execute(text("SELECT COUNT(*) FROM manual_imports")).fetchone()[0]
|
|
assert remaining == ["new"]
|
|
assert mi_ref is None # reference nulled, not cascade-deleted
|
|
assert mi_count == 1 # the import record itself is kept
|
|
|
|
|
|
def test_mark_interrupted_runs():
|
|
now = time.time()
|
|
with appdb.engine.begin() as conn:
|
|
conn.execute(
|
|
text(
|
|
"INSERT INTO job_runs (id, job_key, started_at, status, triggered_by) "
|
|
"VALUES (1,'stuck',:n,'running','schedule')"
|
|
),
|
|
{"n": now},
|
|
)
|
|
appdb.mark_interrupted_runs()
|
|
with appdb.engine.begin() as conn:
|
|
row = conn.execute(text("SELECT status, finished_at FROM job_runs WHERE id=1")).fetchone()
|
|
assert row[0] == "interrupted"
|
|
assert row[1] is not None
|