diff --git a/agent-office/app/telegram/webhook.py b/agent-office/app/telegram/webhook.py index a736842..d15466d 100644 --- a/agent-office/app/telegram/webhook.py +++ b/agent-office/app/telegram/webhook.py @@ -43,6 +43,9 @@ async def _handle_callback(callback_query: dict) -> Optional[dict]: if callback_id.startswith("issue_"): return await _handle_insta_issue(callback_query, callback_id) + if callback_id.startswith("ytpub_retry_"): + return await _handle_ytpub_retry(callback_query, callback_id) + cb = get_telegram_callback(callback_id) if not cb: return None @@ -169,6 +172,30 @@ async def _handle_insta_issue(callback_query: dict, callback_id: str) -> dict: return {"ok": False, "error": str(e)} +async def _handle_ytpub_retry(callback_query: dict, callback_id: str) -> dict: + """ytpub_retry_{pipeline_id} 콜백 → music-lab pipeline retry 프록시.""" + from .. import service_proxy + from .messaging import send_raw + + await api_call( + "answerCallbackQuery", + {"callback_query_id": callback_query["id"], "text": "재시도 요청 중..."}, + ) + + try: + pid = int(callback_id.removeprefix("ytpub_retry_")) + except (ValueError, AttributeError): + return {"ok": False, "error": "invalid_callback_data"} + + res = await service_proxy.pipeline_retry(pid) + sc = res.get("status_code") + if sc in (200, 202): + await send_raw(text=f"🔄 파이프라인 #{pid} 재개: {res.get('retrying_step', '?')}") + else: + await send_raw(text=f"⚠️ 재개 불가 (#{pid}): {res.get('detail', sc)}") + return {"ok": True} + + async def _handle_message(message: dict, agent_dispatcher) -> Optional[dict]: """슬래시 명령 메시지 처리.""" from .router import parse_command, resolve_agent_command, HELP_TEXT diff --git a/agent-office/tests/test_youtube_publisher_retry.py b/agent-office/tests/test_youtube_publisher_retry.py index e9c005c..86393d4 100644 --- a/agent-office/tests/test_youtube_publisher_retry.py +++ b/agent-office/tests/test_youtube_publisher_retry.py @@ -147,6 +147,38 @@ async def test_failed_pipeline_renotify_after_recovery(): assert sent.await_count == 2 # 재알림 +@pytest.mark.asyncio +async def test_handle_ytpub_retry_calls_proxy(): + from app import service_proxy + from app.telegram import webhook + + retry = AsyncMock(return_value={"status_code": 202, "ok": True, "retrying_step": "video"}) + fake_send = AsyncMock(return_value={"ok": True}) + fake_api_call = AsyncMock(return_value={"ok": True}) + + with patch.object(service_proxy, "pipeline_retry", retry), \ + patch("app.telegram.messaging.send_raw", fake_send), \ + patch("app.telegram.webhook.api_call", fake_api_call): + res = await webhook._handle_ytpub_retry({"id": 1}, "ytpub_retry_7") + + retry.assert_awaited_once_with(7) + assert res["ok"] is True + + +@pytest.mark.asyncio +async def test_handle_ytpub_retry_invalid_data(): + from app.telegram import webhook + + fake_send = AsyncMock(return_value={"ok": True}) + fake_api_call = AsyncMock(return_value={"ok": True}) + + with patch("app.telegram.messaging.send_raw", fake_send), \ + patch("app.telegram.webhook.api_call", fake_api_call): + res = await webhook._handle_ytpub_retry({"id": 1}, "ytpub_retry_abc") + + assert res["ok"] is False + + @pytest.mark.asyncio async def test_failed_poll_exception_is_silent(): """list_failed_pipelines 예외 시 poll이 조용히 넘어감 (active 알림에 영향 없음)."""