131 lines
4.5 KiB
Python
131 lines
4.5 KiB
Python
from datetime import date
|
|
from hashlib import sha256
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
import unittest
|
|
|
|
import fitz # type: ignore
|
|
from PIL import Image
|
|
|
|
from roster_formatter import (
|
|
DEFAULT_LAYOUT,
|
|
PAGE_HEIGHT,
|
|
PAGE_WIDTH,
|
|
PNG_DPI,
|
|
PNG_HEIGHT,
|
|
PNG_WIDTH,
|
|
LayoutSettings,
|
|
RosterFormatError,
|
|
RosterPeriod,
|
|
format_roster,
|
|
validate_input_pdf,
|
|
)
|
|
from tests.helpers import make_geometry_pdf
|
|
|
|
|
|
LEGACY_GEOMETRY_SHA256 = "8b000127605f4fd78f9f39606de955eb02392ca113e8ddd808421d4f04c09c31"
|
|
|
|
|
|
class RosterPeriodTests(unittest.TestCase):
|
|
def test_original_month_abbreviation_rules_are_preserved(self) -> None:
|
|
expected = {
|
|
1: "JAN 2026",
|
|
2: "FEB 2026",
|
|
3: "MARCH 2026",
|
|
4: "APRIL 2026",
|
|
5: "MAY 2026",
|
|
6: "JUNE 2026",
|
|
7: "JULY 2026",
|
|
8: "AUG 2026",
|
|
9: "SEP 2026",
|
|
10: "OCT 2026",
|
|
11: "NOV 2026",
|
|
12: "DEC 2026",
|
|
}
|
|
self.assertEqual(
|
|
expected,
|
|
{month: RosterPeriod(2026, month).label for month in range(1, 13)},
|
|
)
|
|
|
|
def test_suggested_period_changes_on_the_fifteenth(self) -> None:
|
|
self.assertEqual(RosterPeriod(2026, 7), RosterPeriod.suggested(date(2026, 7, 14)))
|
|
self.assertEqual(RosterPeriod(2026, 8), RosterPeriod.suggested(date(2026, 7, 15)))
|
|
self.assertEqual(RosterPeriod(2027, 1), RosterPeriod.suggested(date(2026, 12, 31)))
|
|
|
|
def test_parse_accepts_browser_and_cli_formats(self) -> None:
|
|
self.assertEqual(RosterPeriod(2026, 9), RosterPeriod.parse("2026-09"))
|
|
self.assertEqual(RosterPeriod(2026, 9), RosterPeriod.parse("September 2026"))
|
|
self.assertEqual(RosterPeriod(2026, 9), RosterPeriod.parse("SEP 2026"))
|
|
|
|
def test_parse_rejects_untrusted_filename_text(self) -> None:
|
|
with self.assertRaises(RosterFormatError):
|
|
RosterPeriod.parse("../../September 2026")
|
|
|
|
|
|
class LayoutRegressionTests(unittest.TestCase):
|
|
def test_production_defaults_are_explicit(self) -> None:
|
|
self.assertEqual(792, PAGE_WIDTH)
|
|
self.assertEqual(612, PAGE_HEIGHT)
|
|
self.assertEqual(150, PNG_DPI)
|
|
self.assertEqual(1650, PNG_WIDTH)
|
|
self.assertEqual(1275, PNG_HEIGHT)
|
|
self.assertEqual(
|
|
LayoutSettings(
|
|
content_shift=100,
|
|
bottom_margin=60,
|
|
logo_x=50,
|
|
logo_y=-10,
|
|
logo_scale=0.27,
|
|
date_x=460,
|
|
date_y=88,
|
|
date_font_size=48,
|
|
),
|
|
DEFAULT_LAYOUT,
|
|
)
|
|
|
|
def test_legacy_default_is_byte_identical(self) -> None:
|
|
with TemporaryDirectory() as temp_dir:
|
|
root = Path(temp_dir)
|
|
source = make_geometry_pdf(root / "source.pdf")
|
|
output = root / "output.png"
|
|
format_roster(source, output, RosterPeriod(2026, 7))
|
|
|
|
self.assertEqual(LEGACY_GEOMETRY_SHA256, sha256(output.read_bytes()).hexdigest())
|
|
with Image.open(output) as image:
|
|
self.assertEqual((PNG_WIDTH, PNG_HEIGHT), image.size)
|
|
self.assertEqual("RGB", image.mode)
|
|
|
|
def test_explicit_adjustment_changes_pixels_but_not_canvas(self) -> None:
|
|
with TemporaryDirectory() as temp_dir:
|
|
root = Path(temp_dir)
|
|
source = make_geometry_pdf(root / "source.pdf")
|
|
default_output = root / "default.png"
|
|
adjusted_output = root / "adjusted.png"
|
|
format_roster(source, default_output, RosterPeriod(2026, 7))
|
|
format_roster(
|
|
source,
|
|
adjusted_output,
|
|
RosterPeriod(2026, 7),
|
|
LayoutSettings(logo_x=55),
|
|
)
|
|
|
|
self.assertNotEqual(default_output.read_bytes(), adjusted_output.read_bytes())
|
|
with Image.open(adjusted_output) as image:
|
|
self.assertEqual((PNG_WIDTH, PNG_HEIGHT), image.size)
|
|
|
|
def test_input_geometry_is_validated(self) -> None:
|
|
with TemporaryDirectory() as temp_dir:
|
|
source = Path(temp_dir) / "portrait.pdf"
|
|
doc = fitz.open()
|
|
page = doc.new_page(width=612, height=792)
|
|
page.insert_text((20, 20), "not landscape")
|
|
doc.save(source)
|
|
doc.close()
|
|
|
|
with self.assertRaisesRegex(RosterFormatError, "landscape"):
|
|
validate_input_pdf(source)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|