Complete project rebrand for better positioning in the press freedom and digital security space. FieldWitness communicates both field deployment and evidence testimony — appropriate for the target audience of journalists, NGOs, and human rights organizations. Rename mapping: - soosef → fieldwitness (package, CLI, all imports) - soosef.stegasoo → fieldwitness.stego - soosef.verisoo → fieldwitness.attest - ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name) - SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR - SoosefConfig → FieldWitnessConfig - SoosefError → FieldWitnessError Also includes: - License switch from MIT to GPL-3.0 - C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py - README repositioned to lead with provenance/federation, stego backgrounded - Threat model skeleton at docs/security/threat-model.md - Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md 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 fieldwitness.attest.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"
|