import io import imagehash from PIL import Image from backend.app.utils.phash import compute_phash, find_similar def _img(color, size=(64, 64)): return Image.new("RGB", size, color) def test_compute_phash_stable_and_hex(): h1 = compute_phash(_img((10, 120, 200))) h2 = compute_phash(_img((10, 120, 200))) assert isinstance(h1, str) and len(h1) == 16 # hash_size=8 -> 64-bit -> 16 hex assert h1 == h2 def test_compute_phash_none_for_non_image(): assert compute_phash(io.BytesIO(b"not an image")) is None def test_find_similar_none_when_far(): new = compute_phash(_img((0, 0, 0))) cand = (compute_phash(_img((255, 255, 255))), 100, 100, 7) rel, mid = find_similar(new, 50, 50, [cand], threshold=2) assert rel == "none" and mid is None def test_find_similar_larger_exists_skips_new(): h = compute_phash(_img((123, 50, 7))) rel, mid = find_similar(h, 100, 100, [(h, 400, 400, 9)], threshold=2) assert rel == "larger_exists" and mid == 9 def test_find_similar_smaller_exists_supersede(): h = compute_phash(_img((123, 50, 7))) rel, mid = find_similar(h, 800, 800, [(h, 100, 100, 4)], threshold=2) assert rel == "smaller_exists" and mid == 4 def test_find_similar_threshold_boundary_inclusive_and_first_match(): h = compute_phash(_img((10, 10, 10))) far = compute_phash(_img((240, 240, 240))) d = imagehash.hex_to_hash(h) - imagehash.hex_to_hash(far) # at exactly the distance, it counts as similar (<=) rel, _ = find_similar(h, 10, 10, [(far, 999, 999, 1)], threshold=d) assert rel == "larger_exists" rel2, _ = find_similar(h, 10, 10, [(far, 999, 999, 1)], threshold=d - 1) assert rel2 == "none"