import io import json import re import shutil import threading import time import pytest from fastapi.testclient import TestClient from songtrace.config import Config from songtrace.pipeline import STAGE_ORDER, PipelineError, PipelineResult, song_slug from songtrace.web import STAGE_LINE, create_app def make_client(tmp_path, runner): cfg = Config(whisper_url="u", work_dir=tmp_path / "work", output_dir=tmp_path / "out") return TestClient(create_app(cfg, runner=runner)), cfg def ok_runner(inp, cfg, log=print, force_from=None, stages=None): log("[acquire] running") return PipelineResult(output_dir=cfg.output_dir / "X", warnings=["check 1:32"]) def wait_status(client, job_id, status, timeout=5): for _ in range(int(timeout / 0.05)): job = client.get(f"/api/jobs/{job_id}").json() if job["status"] == status: return job time.sleep(0.05) raise AssertionError(f"job never reached {status}: {job}") def test_submit_and_complete(tmp_path): client, _ = make_client(tmp_path, ok_runner) resp = client.post("/api/jobs", data={ "title": "T", "artist": "A", "lyrics": "la la", "youtube_url": "http://y"}) assert resp.status_code == 201 job = wait_status(client, resp.json()["id"], "done") assert job["warnings"] == ["check 1:32"] assert any("[acquire] running" in line for line in job["log"]) def test_failure_reported(tmp_path): def bad_runner(*a, **kw): raise PipelineError("boom") client, _ = make_client(tmp_path, bad_runner) resp = client.post("/api/jobs", data={ "title": "T", "artist": "A", "lyrics": "x", "youtube_url": "http://y"}) job = wait_status(client, resp.json()["id"], "failed") assert job["error"] == "boom" def test_submit_normalises_a_blank_genre_to_none(tmp_path): seen = [] client, _ = make_client(tmp_path, capture_runner_factory(seen)) resp = client.post("/api/jobs", data={ "title": "T", "artist": "A", "lyrics": "x", "youtube_url": "http://y", "genre": ""}) wait_status(client, resp.json()["id"], "done") assert seen[0]["inp"].genre is None def test_requires_one_source(tmp_path): client, _ = make_client(tmp_path, ok_runner) resp = client.post("/api/jobs", data={"title": "T", "artist": "A", "lyrics": "x"}) assert resp.status_code == 400 def test_rejects_both_sources(tmp_path): client, _ = make_client(tmp_path, ok_runner) resp = client.post("/api/jobs", data={ "title": "T", "artist": "A", "lyrics": "x", "youtube_url": "http://y"}, files={"video": ("v.mp4", b"data", "video/mp4")}) assert resp.status_code == 400 def test_job_list_and_404(tmp_path): client, _ = make_client(tmp_path, ok_runner) assert client.get("/api/jobs").json() == [] assert client.get("/api/jobs/nope").status_code == 404 def test_index_served(tmp_path): client, _ = make_client(tmp_path, ok_runner) resp = client.get("/") assert resp.status_code == 200 and "