diff --git a/services/insta-render/pytest.ini b/services/insta-render/pytest.ini new file mode 100644 index 0000000..2f4c80e --- /dev/null +++ b/services/insta-render/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +asyncio_mode = auto diff --git a/services/insta-render/tests/.gitkeep b/services/insta-render/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/services/insta-render/tests/test_worker.py b/services/insta-render/tests/test_worker.py new file mode 100644 index 0000000..1fa4a26 --- /dev/null +++ b/services/insta-render/tests/test_worker.py @@ -0,0 +1,122 @@ +"""worker.py — Redis BLPOP + webhook 단위 테스트.""" +import json +import pytest +import httpx +from unittest.mock import AsyncMock, patch + +import worker + + +@pytest.fixture +def fake_slate(): + return { + "id": 42, + "cover_copy": json.dumps({"headline": "테스트 H", "body": "테스트 B", "accent_color": "#FF0000"}), + "body_copies": json.dumps([{"headline": "본문1", "body": "..."} for _ in range(8)]), + "cta_copy": json.dumps({"headline": "CTA", "body": "...", "cta": "Click"}), + } + + +@pytest.mark.asyncio +async def test_post_update_sends_correct_payload(monkeypatch): + monkeypatch.setenv("INTERNAL_API_KEY", "test-secret") + monkeypatch.setenv("NAS_BASE_URL", "http://nas.test") + # worker 모듈 환경변수 재로딩 + worker.NAS_BASE_URL = "http://nas.test" + worker.INTERNAL_API_KEY = "test-secret" + + captured = {} + async def fake_post(self, url, headers=None, json=None, **kw): + captured["url"] = url + captured["headers"] = headers + captured["json"] = json + class R: + status_code = 200 + text = "ok" + return R() + monkeypatch.setattr(httpx.AsyncClient, "post", fake_post) + + async with httpx.AsyncClient() as client: + await worker._post_update(client, "t-1", "processing", 30) + + assert captured["url"] == "http://nas.test/api/internal/insta/update" + assert captured["headers"]["X-Internal-Key"] == "test-secret" + assert captured["json"]["status"] == "processing" + assert captured["json"]["progress"] == 30 + + +@pytest.mark.asyncio +async def test_process_one_success_calls_webhook_twice(monkeypatch, fake_slate): + """processing(50) → succeeded(100) 두 번 호출 + render 한 번.""" + calls: list = [] + + async def fake_post(self, url, headers=None, json=None, **kw): + calls.append({"status": json["status"], "progress": json["progress"]}) + class R: + status_code = 200 + text = "ok" + return R() + + async def fake_get(self, url, **kw): + class R: + status_code = 200 + def json(self_inner): return fake_slate + def raise_for_status(self_inner): pass + return R() + + async def fake_render(slate, slate_id, template="default/card.html.j2"): + return [f"/tmp/{slate_id}/{i:02d}.png" for i in range(1, 11)] + + monkeypatch.setattr(httpx.AsyncClient, "post", fake_post) + monkeypatch.setattr(httpx.AsyncClient, "get", fake_get) + monkeypatch.setattr(worker, "render_slate", fake_render) + worker.INTERNAL_API_KEY = "test" + worker.NAS_BASE_URL = "http://nas.test" + + async with httpx.AsyncClient() as client: + await worker._process_one(client, { + "task_id": "t-2", + "params": {"slate_id": 42, "theme": "default"}, + }) + + statuses = [c["status"] for c in calls] + assert "processing" in statuses + assert "succeeded" in statuses + assert calls[-1]["progress"] == 100 + + +@pytest.mark.asyncio +async def test_process_one_render_failure_reports_failed(monkeypatch, fake_slate): + """render 예외 시 failed webhook 호출.""" + calls: list = [] + + async def fake_post(self, url, headers=None, json=None, **kw): + calls.append(json) + class R: status_code = 200; text = "ok" + return R() + + async def fake_get(self, url, **kw): + class R: + status_code = 200 + def json(self_inner): return fake_slate + def raise_for_status(self_inner): pass + return R() + + async def fake_render(*a, **k): + raise RuntimeError("Chromium crashed") + + monkeypatch.setattr(httpx.AsyncClient, "post", fake_post) + monkeypatch.setattr(httpx.AsyncClient, "get", fake_get) + monkeypatch.setattr(worker, "render_slate", fake_render) + worker.INTERNAL_API_KEY = "test" + worker.NAS_BASE_URL = "http://nas.test" + + async with httpx.AsyncClient() as client: + await worker._process_one(client, { + "task_id": "t-3", + "params": {"slate_id": 99}, + }) + + last = calls[-1] + assert last["status"] == "failed" + assert "Chromium" in last["error"]