Merge stegasoo (v4.3.0, steganography) and verisoo (v0.1.0, attestation) as subpackages under soosef.stegasoo and soosef.verisoo. This eliminates cross-repo coordination and enables atomic changes across the full stack. - Copy stegasoo (34 modules) and verisoo (15 modules) into src/soosef/ - Convert all verisoo absolute imports to relative imports - Rewire ~50 import sites across soosef code (cli, web, keystore, tests) - Replace stegasoo/verisoo pip deps with inlined code + pip extras (stego-dct, stego-audio, attest, web, api, cli, fieldkit, all, dev) - Add _availability.py for runtime feature detection - Add unified FastAPI mount point at soosef.api - Copy and adapt tests from both repos (155 pass, 1 skip) - Drop standalone CLI/web frontends; keep FastAPI as optional modules - Both source repos tagged pre-monorepo-consolidation on GitHub Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
86 lines
2.6 KiB
Python
86 lines
2.6 KiB
Python
"""Basic tests for image hashing."""
|
|
|
|
from io import BytesIO
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
from soosef.verisoo.hashing import hash_image, perceptual_distance, is_same_image
|
|
|
|
|
|
def create_test_image(width: int = 100, height: int = 100, color: tuple = (255, 0, 0)) -> bytes:
|
|
"""Create a simple test image."""
|
|
img = Image.new("RGB", (width, height), color)
|
|
buffer = BytesIO()
|
|
img.save(buffer, format="PNG")
|
|
return buffer.getvalue()
|
|
|
|
|
|
class TestHashImage:
|
|
"""Tests for hash_image function."""
|
|
|
|
def test_hash_returns_all_components(self):
|
|
"""Hash should return sha256, phash, and dhash."""
|
|
image_data = create_test_image()
|
|
hashes = hash_image(image_data)
|
|
|
|
assert hashes.sha256
|
|
assert hashes.phash
|
|
assert hashes.dhash
|
|
assert len(hashes.sha256) == 64 # SHA-256 hex
|
|
|
|
def test_identical_images_same_hash(self):
|
|
"""Identical bytes should produce identical hashes."""
|
|
image_data = create_test_image()
|
|
hash1 = hash_image(image_data)
|
|
hash2 = hash_image(image_data)
|
|
|
|
assert hash1.sha256 == hash2.sha256
|
|
assert hash1.phash == hash2.phash
|
|
assert hash1.dhash == hash2.dhash
|
|
|
|
def test_different_images_different_hash(self):
|
|
"""Different images should produce different SHA-256."""
|
|
red = create_test_image(color=(255, 0, 0))
|
|
blue = create_test_image(color=(0, 0, 255))
|
|
|
|
hash_red = hash_image(red)
|
|
hash_blue = hash_image(blue)
|
|
|
|
assert hash_red.sha256 != hash_blue.sha256
|
|
|
|
|
|
class TestPerceptualDistance:
|
|
"""Tests for perceptual distance calculation."""
|
|
|
|
def test_identical_hashes_zero_distance(self):
|
|
"""Identical hashes should have zero distance."""
|
|
h = "0123456789abcdef"
|
|
assert perceptual_distance(h, h) == 0
|
|
|
|
def test_different_hashes_nonzero_distance(self):
|
|
"""Different hashes should have positive distance."""
|
|
h1 = "0000000000000000"
|
|
h2 = "0000000000000001"
|
|
assert perceptual_distance(h1, h2) == 1
|
|
|
|
def test_completely_different_max_distance(self):
|
|
"""Completely different hashes should have max distance."""
|
|
h1 = "0000000000000000"
|
|
h2 = "ffffffffffffffff"
|
|
assert perceptual_distance(h1, h2) == 64 # 16 hex chars = 64 bits
|
|
|
|
|
|
class TestIsSameImage:
|
|
"""Tests for image comparison."""
|
|
|
|
def test_exact_match(self):
|
|
"""Identical bytes should be exact match."""
|
|
image_data = create_test_image()
|
|
hash1 = hash_image(image_data)
|
|
hash2 = hash_image(image_data)
|
|
|
|
is_same, reason = is_same_image(hash1, hash2)
|
|
assert is_same
|
|
assert reason == "exact"
|