feat(music-lab): orchestrator step 자동 재시도 (publish 제외)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from app import db
|
||||
from app.pipeline import orchestrator
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -10,6 +11,11 @@ def fresh_db(monkeypatch, tmp_path):
|
||||
return db_path
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_backoff(monkeypatch):
|
||||
monkeypatch.setattr(orchestrator, "STEP_RETRY_BACKOFF_SEC", [0, 0])
|
||||
|
||||
|
||||
def test_get_last_failed_step_returns_step(fresh_db):
|
||||
pid = db.create_pipeline(track_id=1)
|
||||
job_id = db.create_pipeline_job(pid, "video")
|
||||
@@ -22,3 +28,56 @@ def test_get_last_failed_step_none_when_no_failure(fresh_db):
|
||||
pid = db.create_pipeline(track_id=1)
|
||||
db.create_pipeline_job(pid, "cover")
|
||||
assert db.get_last_failed_step(pid) is None
|
||||
|
||||
|
||||
async def test_retryable_step_retries_then_succeeds(fresh_db, monkeypatch):
|
||||
pid = db.create_pipeline(track_id=1)
|
||||
calls = {"n": 0}
|
||||
|
||||
async def flaky(step, p, ctx, feedback):
|
||||
calls["n"] += 1
|
||||
if calls["n"] < 3:
|
||||
raise RuntimeError("transient")
|
||||
return {"next_state": "video_pending", "fields": {}}
|
||||
|
||||
monkeypatch.setattr(orchestrator, "_dispatch_step", flaky)
|
||||
monkeypatch.setattr(
|
||||
orchestrator, "_resolve_input",
|
||||
lambda p: {"genre": "x", "title": "t", "moods": [], "tracks": [], "audio_path": "", "duration_sec": 0},
|
||||
)
|
||||
await orchestrator.run_step(pid, "cover")
|
||||
assert calls["n"] == 3
|
||||
assert db.get_pipeline(pid)["state"] == "video_pending"
|
||||
|
||||
|
||||
async def test_retryable_step_exhausts_to_failed(fresh_db, monkeypatch):
|
||||
pid = db.create_pipeline(track_id=1)
|
||||
|
||||
async def always_fail(step, p, ctx, feedback):
|
||||
raise RuntimeError("permanent")
|
||||
|
||||
monkeypatch.setattr(orchestrator, "_dispatch_step", always_fail)
|
||||
monkeypatch.setattr(
|
||||
orchestrator, "_resolve_input",
|
||||
lambda p: {"genre": "x", "title": "t", "moods": [], "tracks": [], "audio_path": "", "duration_sec": 0},
|
||||
)
|
||||
await orchestrator.run_step(pid, "cover")
|
||||
assert db.get_pipeline(pid)["state"] == "failed"
|
||||
|
||||
|
||||
async def test_publish_not_retried(fresh_db, monkeypatch):
|
||||
pid = db.create_pipeline(track_id=1)
|
||||
calls = {"n": 0}
|
||||
|
||||
async def fail_publish(step, p, ctx, feedback):
|
||||
calls["n"] += 1
|
||||
raise RuntimeError("upload error")
|
||||
|
||||
monkeypatch.setattr(orchestrator, "_dispatch_step", fail_publish)
|
||||
monkeypatch.setattr(
|
||||
orchestrator, "_resolve_input",
|
||||
lambda p: {"genre": "x", "title": "t", "moods": [], "tracks": [], "audio_path": "", "duration_sec": 0},
|
||||
)
|
||||
await orchestrator.run_step(pid, "publish")
|
||||
assert calls["n"] == 1
|
||||
assert db.get_pipeline(pid)["state"] == "failed"
|
||||
|
||||
Reference in New Issue
Block a user