131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
import json
|
|
import os
|
|
from datetime import date
|
|
from unittest.mock import patch
|
|
from io import BytesIO
|
|
from PIL import Image as PILImage, ImageDraw, ImageFont
|
|
|
|
from src.cover import (
|
|
generate_programmatic_cover,
|
|
generate_mosaic_cover,
|
|
generate_cover,
|
|
_truncate_to_width,
|
|
_get_font,
|
|
)
|
|
|
|
|
|
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_generate_mosaic_cover_single_image(tmp_path):
|
|
# Create a dummy image
|
|
img_path = str(tmp_path / "dummy.jpg")
|
|
img = PILImage.new("RGB", (1000, 500), color="red")
|
|
img.save(img_path)
|
|
|
|
output_dir = str(tmp_path)
|
|
week_start = date(2026, 4, 6)
|
|
week_end = date(2026, 4, 12)
|
|
headlines = ["Test Headline"]
|
|
|
|
cover_path = generate_mosaic_cover(output_dir, week_start, week_end, headlines, [img_path])
|
|
|
|
assert os.path.exists(cover_path)
|
|
with PILImage.open(cover_path) as result_img:
|
|
assert result_img.size == (480, 800)
|
|
|
|
|
|
def test_generate_cover_dispatches(tmp_path):
|
|
with patch("src.cover.generate_mosaic_cover") as mock_mosaic:
|
|
mock_mosaic.return_value = "/fake/mosaic.jpg"
|
|
result = generate_cover("mosaic", str(tmp_path), date(2026, 4, 6),
|
|
date(2026, 4, 12), ["A"], ["/x/a.jpg"])
|
|
assert result == "/fake/mosaic.jpg"
|
|
mock_mosaic.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_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}"
|
|
|
|
|
|
def test_obituary_headlines_filtered():
|
|
"""Verify the filtering pattern used by publish/issues/scheduler call sites."""
|
|
class FakeArticle:
|
|
def __init__(self, title, categories):
|
|
self.title = title
|
|
self.categories = json.dumps(categories)
|
|
|
|
articles = [
|
|
FakeArticle("Town Meeting Approves Budget", ["Government"]),
|
|
FakeArticle("John Smith, 85, beloved teacher", ["Obituaries"]),
|
|
FakeArticle("Harbor Festival This Weekend", ["Culture"]),
|
|
FakeArticle("Jane Doe, 92, retired nurse", ["Obituaries"]),
|
|
FakeArticle("Panthers Win Championship", ["Sports"]),
|
|
]
|
|
|
|
headlines = [
|
|
a.title for a in articles
|
|
if "Obituaries" not in json.loads(a.categories)
|
|
]
|
|
|
|
assert len(headlines) == 3
|
|
assert "John Smith, 85, beloved teacher" not in headlines
|
|
assert "Jane Doe, 92, retired nurse" not in headlines
|
|
assert "Town Meeting Approves Budget" in headlines
|
|
assert "Harbor Festival This Weekend" in headlines
|
|
assert "Panthers Win Championship" in headlines
|