125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Verify year-based final statistics aggregation in organize.py.
|
|
|
|
Creates test images with different EXIF years (and some without EXIF),
|
|
runs the organize function, and checks the output summary.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
# Make sure we can import organize
|
|
HERE = Path(__file__).parent.absolute()
|
|
if str(HERE) not in sys.path:
|
|
sys.path.insert(0, str(HERE))
|
|
|
|
from PIL import Image
|
|
from PIL.ExifTags import Base as ExifBase
|
|
|
|
from organize import organize
|
|
|
|
|
|
def _make_image(tmpdir: Path, name: str, year: int | None = None) -> Path:
|
|
"""Create a test image. If *year* is given, embed EXIF DateTimeOriginal."""
|
|
img = Image.new("RGB", (16, 16), color="red")
|
|
path = tmpdir / name
|
|
|
|
if year is not None:
|
|
exif_data = img.getexif()
|
|
exif_data[ExifBase.DateTimeOriginal] = f"{year:04d}:06:15 12:00:00"
|
|
img.save(path, exif=exif_data.tobytes())
|
|
else:
|
|
img.save(path) # no EXIF
|
|
|
|
img.close()
|
|
return path
|
|
|
|
|
|
def test_stats_summary_format() -> None:
|
|
"""Check that the summary section is printed after per-line output."""
|
|
import io
|
|
|
|
with tempfile.TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
|
|
# Create images with known years
|
|
_make_image(tmp, "img_2023.jpg", year=2023)
|
|
_make_image(tmp, "img_2024a.jpg", year=2024)
|
|
_make_image(tmp, "img_2024b.jpg", year=2024)
|
|
_make_image(tmp, "img_2025.jpg", year=2025)
|
|
_make_image(tmp, "no_exif.jpg", year=None) # skipped in our test
|
|
|
|
# We just check it doesn't crash and returns correct exit
|
|
# We'll capture stdout to verify the summary
|
|
old_stdout = sys.stdout
|
|
captured = io.StringIO()
|
|
sys.stdout = captured
|
|
try:
|
|
organize(tmp, no_color=True)
|
|
finally:
|
|
sys.stdout = old_stdout
|
|
|
|
output = captured.getvalue()
|
|
|
|
# Verify the summary section exists
|
|
assert "=== Year Summary ===" in output, "Year summary header missing"
|
|
|
|
# Verify per-year counts
|
|
assert "2023: 1 entries" in output, "Year 2023 count wrong"
|
|
assert "2024: 2 entries" in output, "Year 2024 count wrong"
|
|
assert "2025: 1 entries" in output, "Year 2025 count wrong"
|
|
|
|
# Verify category counts
|
|
assert "Total with EXIF date:" in output
|
|
assert "Total with FS date:" in output
|
|
assert "Total skipped:" in output
|
|
|
|
# Summary appears after per-line output (no interleaving check via text position)
|
|
|
|
print("test_stats_summary_format: PASS")
|
|
|
|
|
|
def test_stats_counts_match() -> None:
|
|
"""Verify that category totals add up correctly."""
|
|
with tempfile.TemporaryDirectory() as td:
|
|
tmp = Path(td)
|
|
|
|
# 3 with EXIF, 1 without (uses FS date via mtime)
|
|
_make_image(tmp, "a_2022.jpg", year=2022)
|
|
_make_image(tmp, "b_2023.jpg", year=2023)
|
|
_make_image(tmp, "c_2024.jpg", year=2024)
|
|
|
|
# Image without EXIF — falls back to FS date
|
|
img = Image.new("RGB", (8, 8), color="blue")
|
|
p = tmp / "no_exif.jpg"
|
|
img.save(p)
|
|
img.close()
|
|
|
|
old_stdout = sys.stdout
|
|
captured = io.StringIO()
|
|
sys.stdout = captured
|
|
try:
|
|
organize(tmp, no_color=True)
|
|
finally:
|
|
sys.stdout = old_stdout
|
|
|
|
output = captured.getvalue()
|
|
|
|
# Verify counts
|
|
assert "Total with EXIF date: 3" in output
|
|
assert "Total with FS date: 1" in output
|
|
assert "Total skipped: 0" in output
|
|
assert "Total processed: 4" in output
|
|
|
|
print("test_stats_counts_match: PASS")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_stats_summary_format()
|
|
test_stats_counts_match()
|
|
print("All stats tests passed!")
|