soulsync/tests/metadata/test_art_min_size.py
BoulderBadgeDad b202c176f7 Cover-art sources: skip low-res art (min-resolution guard) + max-res iTunes
Follow-up to the preferred-art feature. Real test runs showed a source could
win on priority while handing back a small cover: Cover Art Archive is
volunteer-uploaded with no size floor, so CAA-first gave a 599x531 (Taylor
Swift) and a 600x600 (Kendrick GNX) -- front-1200 only caps the max, so a
~600px upload stays ~600px -- and Deezer/iTunes lower in the order never got a
turn.

Fix:
- Minimum-resolution guard: artwork._min_size_art_validator builds the
  resolver's validate hook -- it fetches each candidate, caches the bytes (so
  the winner isn't fetched twice), and accepts art only when its shortest side
  >= metadata_enhancement.min_art_size (default 1000px; 0 disables). Art that's
  too small is a miss, so the resolver falls through to the next source instead
  of winning on priority. Unmeasurable images are accepted (don't over-reject;
  fallback is still today's art). Wired into both embed_album_art_metadata and
  download_cover_art.
- iTunes art upgraded to /3000x3000bb/ (was the 600px default) so it
  contributes high-res when it wins.
- select_preferred_art_url gains a validate passthrough to the resolver.
- config default metadata_enhancement.min_art_size: 1000.

Effect: with an order like caa > deezer > spotify > itunes, a ~600px CAA upload
is now skipped and Deezer's ~1900px wins -- consistent big art. (Spotify art
often maxes ~640px, so it's skipped at the 1000 floor in favor of bigger
sources; lower min_art_size to ~640 to allow it.)

Tests: tests/metadata/test_art_min_size.py (6 -- incl. the real 599x531 and
600x600 cases, shortest-side logic, unmeasurable-accept, no-bytes-reject,
0-disables) + iTunes max-res upgrade test. Full metadata suite green (617).
2026-06-01 12:24:51 -07:00

54 lines
1.8 KiB
Python

"""Tests for the minimum-resolution guard on preferred cover art.
A source's art is only accepted when its shortest side meets the threshold, so
a low-res cover (e.g. a small Cover Art Archive upload) is skipped and the
resolver falls through to the next source instead of winning on priority alone.
Reproduces the two real cases that motivated it: Taylor Swift 599x531 and
Kendrick GNX 600x600 — both rejected at the default 1000px floor.
"""
from __future__ import annotations
from unittest.mock import patch
from core.metadata import artwork
def _run(min_px, fetch_return, dims):
with patch.object(artwork, "_fetch_art_bytes", return_value=fetch_return), \
patch.object(artwork, "get_image_dimensions", return_value=dims):
validate, cache = artwork._min_size_art_validator(min_px)
return validate("deezer", "http://x/cover.jpg"), cache
def test_rejects_small_square_art():
ok, cache = _run(1000, (b"img", "image/jpeg"), (600, 600)) # GNX case
assert ok is False
# Bytes are cached even on reject (so a later accepted source reuses fetches).
assert cache["http://x/cover.jpg"] == (b"img", "image/jpeg")
def test_rejects_small_non_square_using_shortest_side():
ok, _ = _run(1000, (b"img", "image/jpeg"), (599, 531)) # Taylor case
assert ok is False
def test_accepts_big_art():
ok, cache = _run(1000, (b"img", "image/jpeg"), (1900, 1900))
assert ok is True
assert cache["http://x/cover.jpg"] == (b"img", "image/jpeg")
def test_accepts_unmeasurable_art_to_avoid_over_rejecting():
ok, _ = _run(1000, (b"img", "image/jpeg"), None)
assert ok is True
def test_rejects_when_fetch_returns_no_bytes():
ok, _ = _run(1000, (None, None), (4000, 4000))
assert ok is False
def test_zero_threshold_disables_the_gate():
ok, _ = _run(0, (b"img", "image/jpeg"), (10, 10))
assert ok is True