Files
pi-weekly-newspaper/tests/test_cover.py
cottongin 9a8b586292 feat(cover): pixel-width headline truncation and larger font
Replace character-count truncation (45 chars) with Pillow textbbox
pixel-width measurement. Bump headline font 14→18, line spacing 22→26.

Made-with: Cursor
2026-04-06 18:58:42 -04:00

139 lines
4.5 KiB
Python

import os
from collections import Counter
from datetime import date
from unittest.mock import patch, MagicMock
from io import BytesIO
from PIL import Image as PILImage, ImageDraw, ImageFont
from src.cover import (
generate_programmatic_cover,
generate_ai_cover,
generate_cover,
_get_dominant_category,
_truncate_to_width,
_get_font,
CATEGORY_PROMPTS,
)
def test_programmatic_cover_is_portrait(tmp_path):
path = generate_programmatic_cover(
output_dir=str(tmp_path),
week_start=date(2026, 4, 6),
week_end=date(2026, 4, 12),
headlines=["Article One", "Article Two", "Article Three"],
categories=["Government", "Government", "Culture"],
)
assert os.path.exists(path)
img = PILImage.open(path)
assert img.format == "JPEG"
assert img.size == (480, 800)
def test_programmatic_cover_no_headlines(tmp_path):
path = generate_programmatic_cover(
output_dir=str(tmp_path),
week_start=date(2026, 4, 6),
week_end=date(2026, 4, 12),
headlines=[],
categories=[],
)
assert os.path.exists(path)
img = PILImage.open(path)
assert img.size == (480, 800)
def test_ai_cover_is_portrait(tmp_path):
fake_img = PILImage.new("RGB", (480, 800), color="blue")
buf = BytesIO()
fake_img.save(buf, format="JPEG")
fake_bytes = buf.getvalue()
mock_response = MagicMock()
mock_response.content = fake_bytes
mock_response.raise_for_status = MagicMock()
with patch("src.cover.requests.get", return_value=mock_response):
path = generate_ai_cover(
output_dir=str(tmp_path),
week_start=date(2026, 4, 6),
week_end=date(2026, 4, 12),
headlines=["Test Headline"],
categories=["Government"],
)
assert os.path.exists(path)
img = PILImage.open(path)
assert img.format == "JPEG"
assert img.size == (480, 800)
def test_ai_cover_falls_back_to_programmatic(tmp_path):
with patch("src.cover.requests.get", side_effect=Exception("API down")):
path = generate_ai_cover(
output_dir=str(tmp_path),
week_start=date(2026, 4, 6),
week_end=date(2026, 4, 12),
headlines=["Test"],
categories=["Government"],
)
assert os.path.exists(path)
img = PILImage.open(path)
assert img.format == "JPEG"
assert img.size == (480, 800)
def test_generate_cover_dispatches(tmp_path):
with patch("src.cover.generate_ai_cover") as mock_ai:
mock_ai.return_value = "/fake/ai.jpg"
result = generate_cover("ai", str(tmp_path), date(2026, 4, 6),
date(2026, 4, 12), ["A"], ["Government"])
assert result == "/fake/ai.jpg"
mock_ai.assert_called_once()
with patch("src.cover.generate_programmatic_cover") as mock_prog:
mock_prog.return_value = "/fake/text.jpg"
result = generate_cover("text", str(tmp_path), date(2026, 4, 6),
date(2026, 4, 12), ["A"], ["Government"])
assert result == "/fake/text.jpg"
mock_prog.assert_called_once()
def test_get_dominant_category():
assert _get_dominant_category(["Government", "Government", "Culture"]) == "Government"
assert _get_dominant_category(["Culture", "Government"]) == "Culture"
assert _get_dominant_category([]) == "Default"
def test_category_prompts_include_massachusetts():
for category, prompt in CATEGORY_PROMPTS.items():
assert "Massachusetts" in prompt, f"Prompt for {category} missing 'Massachusetts'"
def test_truncate_to_width_respects_pixel_budget():
font = _get_font(18)
short = "Short text"
result = _truncate_to_width(short, font, 400)
assert result == short, "Short text should not be truncated"
long = "A" * 200
result = _truncate_to_width(long, font, 400)
assert result.endswith("\u2026"), "Long text should end with ellipsis"
assert len(result) < len(long), "Truncated text should be shorter"
def test_truncate_to_width_fits_within_budget():
font = _get_font(18)
long = "This is a really long headline that should definitely be truncated to fit"
max_width = 448
result = _truncate_to_width(long, font, max_width)
dummy = PILImage.new("RGBA", (1, 1))
draw = ImageDraw.Draw(dummy)
bbox = draw.textbbox((0, 0), result, font=font)
rendered_width = bbox[2] - bbox[0]
assert rendered_width <= max_width, f"Rendered width {rendered_width} exceeds {max_width}"