diff --git a/services/insta-render/card_renderer.py b/services/insta-render/card_renderer.py index d53fc62..5f8c3c1 100644 --- a/services/insta-render/card_renderer.py +++ b/services/insta-render/card_renderer.py @@ -151,8 +151,11 @@ async def _render_slate_locked(slate: dict, slate_id: int, template: str) -> Lis html_path = f.name try: await page.goto(f"file://{html_path}", wait_until="networkidle") + await page.evaluate("document.fonts.ready") # 웹폰트 로딩 완료까지 대기 out_path = os.path.join(out_dir, f"{spec['page_no']:02d}.png") await page.screenshot(path=out_path, full_page=False, omit_background=False) + if os.path.getsize(out_path) < 1000: # 빈/깨진 PNG 방어 + raise RuntimeError(f"rendered PNG too small: {out_path}") paths.append(out_path) finally: try: diff --git a/services/insta-render/tests/test_worker.py b/services/insta-render/tests/test_worker.py index 4c4f89a..80ec07f 100644 --- a/services/insta-render/tests/test_worker.py +++ b/services/insta-render/tests/test_worker.py @@ -1,10 +1,13 @@ """worker.py — Redis BLPOP + webhook 단위 테스트.""" import json +import os +from pathlib import Path import pytest import httpx from unittest.mock import AsyncMock, patch import worker +from card_renderer import render_slate, init_browser, shutdown_browser @pytest.fixture @@ -179,6 +182,28 @@ async def test_poll_once_calls_fail_on_exception(monkeypatch): fake_queue.ack.assert_not_awaited() +@pytest.mark.asyncio +async def test_render_produces_nonempty_1080x1350(tmp_path, monkeypatch): + """Phase 2 — fonts.ready 대기 + PNG 비어있음 검증: 10장 모두 > 1000 bytes.""" + import card_renderer as _cr + templates_dir = str(Path(__file__).resolve().parent.parent / "templates") + monkeypatch.setattr(_cr, "CARD_TEMPLATE_DIR", templates_dir) + monkeypatch.setattr(_cr, "INSTA_MEDIA_ROOT", str(tmp_path)) + await init_browser() + try: + slate = { + "cover_copy": {"headline": "헤드라인", "body": "서브", "accent_color": "#0F62FE"}, + "body_copies": [{"headline": f"포인트{i}", "body": "본문"} for i in range(8)], + "cta_copy": {"headline": "요약", "body": "마무리", "cta": "팔로우"}, + } + paths = await render_slate(slate, slate_id=99999) + assert len(paths) == 10 + for p in paths: + assert os.path.getsize(p) > 1000 # 비어있지 않음 + finally: + await shutdown_browser() + + @pytest.mark.asyncio async def test_poll_once_returns_false_on_timeout(monkeypatch): """F6 — dequeue가 None 반환(타임아웃)이면 False 리턴, ack/fail 안 부름."""