Files
biliup-next/tests/test_song_detect_retry_policy.py

104 lines
3.9 KiB
Python

from __future__ import annotations
import unittest
from types import SimpleNamespace
from biliup_next.app.retry_meta import retry_meta_for_step
from biliup_next.app.task_engine import next_runnable_step
from biliup_next.app.task_policies import resolve_failure
from biliup_next.core.errors import ModuleError
from biliup_next.core.models import TaskStep
from biliup_next.modules.song_detect.providers.qwen_cli import QwenCliSongDetector
class _Repo:
def __init__(self) -> None:
self.steps = [TaskStep(None, "task-1", "song_detect", "running", None, None, 0, None, None)]
self.step_updates: list[tuple] = []
self.task_updates: list[tuple] = []
def list_steps(self, task_id: str): # noqa: ANN001
return list(self.steps)
def get_task(self, task_id: str): # noqa: ANN001
return SimpleNamespace(id=task_id, status="running")
def update_step_status(self, task_id: str, step_name: str, status: str, **kwargs) -> None: # noqa: ANN001
self.step_updates.append((task_id, step_name, status, kwargs))
self.steps = [
TaskStep(
None,
task_id,
step_name,
status,
kwargs.get("error_code"),
kwargs.get("error_message"),
kwargs.get("retry_count", 0),
kwargs.get("started_at"),
kwargs.get("finished_at"),
)
]
def update_task_status(self, task_id: str, status: str, updated_at: str) -> None:
self.task_updates.append((task_id, status, updated_at))
class SongDetectRetryPolicyTests(unittest.TestCase):
def test_retry_meta_reports_wait_window_for_song_detect(self) -> None:
step = TaskStep(None, "task-1", "song_detect", "failed_retryable", "ERR", "boom", 1, None, "2099-01-01T00:00:00+00:00")
payload = retry_meta_for_step(step, {"song_detect": {"retry_schedule_minutes": [10]}})
self.assertIsNotNone(payload)
self.assertFalse(payload["retry_due"])
self.assertEqual(payload["retry_wait_seconds"], 600)
def test_next_runnable_step_waits_for_retryable_song_detect(self) -> None:
task = SimpleNamespace(id="task-1", status="failed_retryable")
steps = {
"song_detect": TaskStep(None, "task-1", "song_detect", "failed_retryable", "ERR", "boom", 1, None, "2099-01-01T00:00:00+00:00"),
}
state = {
"settings": {
"transcribe": {},
"song_detect": {"retry_schedule_minutes": [10]},
"comment": {"enabled": True},
"collection": {"enabled": True},
"paths": {},
"publish": {},
}
}
step_name, waiting_payload = next_runnable_step(task, steps, state)
self.assertIsNone(step_name)
self.assertIsNotNone(waiting_payload)
self.assertEqual(waiting_payload["step"], "song_detect")
def test_resolve_failure_adds_song_detect_retry_delay(self) -> None:
repo = _Repo()
task = SimpleNamespace(id="task-1", status="running")
state = {
"settings": {
"transcribe": {},
"song_detect": {"retry_schedule_minutes": [5, 10]},
"publish": {},
"comment": {},
"paths": {},
"collection": {"enabled": True},
}
}
result = resolve_failure(task, repo, state, ModuleError(code="SONG_DETECT_FAILED", message="boom", retryable=True))
self.assertEqual(result["payload"]["retry_status"], "failed_retryable")
self.assertEqual(result["payload"]["next_retry_delay_seconds"], 300)
def test_qwen_auth_errors_are_not_retryable(self) -> None:
self.assertTrue(QwenCliSongDetector._is_auth_error("[API Error: 401 invalid access token or token expired]"))
self.assertFalse(QwenCliSongDetector._is_auth_error("temporary network failure"))
if __name__ == "__main__":
unittest.main()