Files
alembic/tests/test_pipeline_runner.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

61 lines
1.6 KiB
Python

import asyncio
from app.services import pipeline_runner as pr
# One loop for the whole module: pipeline_runner._lock is a module-level
# asyncio.Lock, and reusing it across different loops (as asyncio.run would
# create) raises "bound to a different event loop".
_loop = asyncio.new_event_loop()
def _run(coro):
return _loop.run_until_complete(coro)
def test_run_job_success_records_row_and_log():
run = _run(pr.run_job("test:ok", ["true"]))
assert run.status == "success"
assert run.exit_code == 0
assert run.log_path # a log file path was recorded
def test_run_job_failure():
run = _run(pr.run_job("test:fail", ["false"]))
assert run.status == "failed"
assert run.exit_code == 1
def test_lock_skip_when_held():
async def scenario():
await pr._lock.acquire()
try:
return await pr.run_job("test:locked", ["true"], use_lock=True)
finally:
pr._lock.release()
run = _run(scenario())
assert run.status == "skipped_lock"
def test_use_lock_false_bypasses_held_lock():
async def scenario():
await pr._lock.acquire()
try:
return await pr.run_job("test:bypass", ["true"], use_lock=False)
finally:
pr._lock.release()
run = _run(scenario())
assert run.status == "success"
def test_lock_released_after_run():
_run(pr.run_job("test:release", ["true"]))
assert not pr._lock.locked()
def test_timeout_kills_and_marks_failed():
run = _run(pr.run_job("test:timeout", ["sleep", "5"], timeout=0.3))
assert run.status == "failed"
assert run.exit_code == -1