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()