Files
photoanalyzer/tests/characterization/test_pa_exif.py

88 lines
3.2 KiB
Python

"""Characterize EXIF behavior (donor: build_exif_caption_from_result,
read_existing_exif, write_exif merge/idempotency, _rec_has_nsfw,
filter_nsfw_tagged). Uses the real exiftool on synthetic temp files."""
import subprocess
import pytest
import photo_analyzer as pa
from conftest import EXIFTOOL
needs_exiftool = pytest.mark.skipif(not EXIFTOOL, reason="exiftool not on PATH")
RESULT = {
"description": "A dog on a beach.",
"tags": ["dog", "beach"],
"mood": "joyful",
"location_hint": "Rome, Italy",
"approx_year": 2015,
}
def test_caption_golden_full():
assert pa.build_exif_caption_from_result(RESULT) == (
"A dog on a beach. | Tags: dog, beach | Mood: joyful | "
"Location: Rome, Italy | ~2015")
def test_caption_golden_sparse():
assert pa.build_exif_caption_from_result(
{"description": "X.", "tags": [], "location_hint": None}) == "X."
def test_rec_has_nsfw_list_scalar_case():
assert pa._rec_has_nsfw({"Keywords": ["beach", "NSFW "]})
assert pa._rec_has_nsfw({"Subject": "nsfw"})
assert not pa._rec_has_nsfw({"Keywords": ["sfw"], "Subject": ["beach"]})
assert not pa._rec_has_nsfw({})
@needs_exiftool
def test_write_exif_roundtrip_and_idempotent(img):
caption = pa.build_exif_caption_from_result(RESULT)
assert pa.write_exif(str(img), caption, RESULT["tags"])
rec = pa.read_existing_exif(str(img))
assert rec["ImageDescription"] == caption
assert rec["XPComment"] == caption
assert rec["Subject"] == RESULT["tags"]
assert rec["Keywords"] == RESULT["tags"]
# second write with the same caption must not duplicate anything
assert pa.write_exif(str(img), caption, RESULT["tags"])
rec2 = pa.read_existing_exif(str(img))
assert rec2["ImageDescription"] == caption
assert rec2["Keywords"] == RESULT["tags"]
@needs_exiftool
def test_write_exif_preserves_existing_metadata(img):
subprocess.run(
["exiftool", "-m", "-overwrite_original", "-Artist=Original Artist",
"-ImageDescription=User caption", "-Keywords=userkw", str(img)],
check=True, capture_output=True)
assert pa.write_exif(str(img), "AI caption", ["aitag"])
rec = pa.read_existing_exif(str(img))
# donor behavior: AI caption prepended, user caption kept after ' | '
assert rec["ImageDescription"] == "AI caption | User caption"
kws = rec["Keywords"] if isinstance(rec["Keywords"], list) else [rec["Keywords"]]
assert "userkw" in kws and "aitag" in kws, "keywords merge, never replace"
full = subprocess.run(["exiftool", "-j", "-Artist", str(img)],
capture_output=True, text=True)
assert "Original Artist" in full.stdout, "non-owned field preserved"
@needs_exiftool
def test_filter_nsfw_tagged_splits_and_normalizes(img, tmp_path):
from conftest import write_fixture
clean = write_fixture("fx-blocks-02", tmp_path / "clean.jpg")
subprocess.run(["exiftool", "-m", "-overwrite_original",
"-Keywords=nsfw", "-Subject=nsfw", str(img)],
check=True, capture_output=True)
kept, skipped = pa.filter_nsfw_tagged([str(img), str(clean)])
assert kept == [str(clean)]
assert skipped == [str(img)]
def test_filter_nsfw_tagged_empty_list_noop():
assert pa.filter_nsfw_tagged([]) == ([], [])