feat(music-lab): POST /pipeline/{id}/retry — 실패 step 수동 재개
terminal failed 파이프라인을 마지막 실패 step부터 재개. publish + youtube_video_id 있으면 중복 업로드 방지 409. pytest.ini에 pythonpath=.. 추가 (PYTHONPATH=.. 없이 TestClient 테스트 구동). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1128,6 +1128,25 @@ def cancel_pipeline(pid: int):
|
|||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/music/pipeline/{pid}/retry", status_code=202)
|
||||||
|
async def retry_pipeline(pid: int, bg: BackgroundTasks):
|
||||||
|
p = _db_module.get_pipeline(pid)
|
||||||
|
if not p:
|
||||||
|
raise HTTPException(404)
|
||||||
|
if p["state"] != "failed":
|
||||||
|
raise HTTPException(409, f"재개 불가 (state={p['state']})")
|
||||||
|
failed_step = _db_module.get_last_failed_step(pid)
|
||||||
|
if not failed_step:
|
||||||
|
reason = p.get("failed_reason") or ""
|
||||||
|
failed_step = reason.split(":", 1)[0].strip() or None
|
||||||
|
if not failed_step:
|
||||||
|
raise HTTPException(409, "실패 step을 판별할 수 없음")
|
||||||
|
if failed_step == "publish" and p.get("youtube_video_id"):
|
||||||
|
raise HTTPException(409, "이미 업로드됨 (중복 방지)")
|
||||||
|
bg.add_task(orchestrator.run_step, pid, failed_step)
|
||||||
|
return {"ok": True, "retrying_step": failed_step}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/music/pipeline/{pid}/publish", status_code=202)
|
@app.post("/api/music/pipeline/{pid}/publish", status_code=202)
|
||||||
async def publish_pipeline(pid: int, bg: BackgroundTasks):
|
async def publish_pipeline(pid: int, bg: BackgroundTasks):
|
||||||
p = _db_module.get_pipeline(pid)
|
p = _db_module.get_pipeline(pid)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[pytest]
|
[pytest]
|
||||||
testpaths = tests
|
testpaths = tests
|
||||||
pythonpath = .
|
pythonpath = . ..
|
||||||
asyncio_mode = auto
|
asyncio_mode = auto
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
from app import db
|
from app import db
|
||||||
from app.pipeline import orchestrator
|
from app.pipeline import orchestrator
|
||||||
|
|
||||||
@@ -81,3 +82,43 @@ async def test_publish_not_retried(fresh_db, monkeypatch):
|
|||||||
await orchestrator.run_step(pid, "publish")
|
await orchestrator.run_step(pid, "publish")
|
||||||
assert calls["n"] == 1
|
assert calls["n"] == 1
|
||||||
assert db.get_pipeline(pid)["state"] == "failed"
|
assert db.get_pipeline(pid)["state"] == "failed"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Task 3: retry endpoint tests ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(fresh_db):
|
||||||
|
from app.main import app
|
||||||
|
return TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_retry_failed_pipeline_retriggers(fresh_db, client, monkeypatch):
|
||||||
|
pid = db.create_pipeline(track_id=1)
|
||||||
|
job = db.create_pipeline_job(pid, "video")
|
||||||
|
db.update_pipeline_job(job, status="failed", error="boom")
|
||||||
|
db.update_pipeline_state(pid, "failed", failed_reason="video: boom")
|
||||||
|
called = {}
|
||||||
|
|
||||||
|
async def fake_run(p, step, *a):
|
||||||
|
called["pid"], called["step"] = p, step
|
||||||
|
|
||||||
|
monkeypatch.setattr(orchestrator, "run_step", fake_run)
|
||||||
|
r = client.post(f"/api/music/pipeline/{pid}/retry")
|
||||||
|
assert r.status_code in (200, 202)
|
||||||
|
assert r.json()["retrying_step"] == "video"
|
||||||
|
|
||||||
|
|
||||||
|
def test_retry_non_failed_409(fresh_db, client):
|
||||||
|
pid = db.create_pipeline(track_id=1) # state='created'
|
||||||
|
r = client.post(f"/api/music/pipeline/{pid}/retry")
|
||||||
|
assert r.status_code == 409
|
||||||
|
|
||||||
|
|
||||||
|
def test_retry_publish_with_video_id_rejected(fresh_db, client):
|
||||||
|
pid = db.create_pipeline(track_id=1)
|
||||||
|
job = db.create_pipeline_job(pid, "publish")
|
||||||
|
db.update_pipeline_job(job, status="failed", error="x")
|
||||||
|
db.update_pipeline_state(pid, "failed", failed_reason="publish: x",
|
||||||
|
youtube_video_id="abc123")
|
||||||
|
r = client.post(f"/api/music/pipeline/{pid}/retry")
|
||||||
|
assert r.status_code == 409
|
||||||
|
|||||||
Reference in New Issue
Block a user