metadata-scrubber-tool:

added unit test for image handler
This commit is contained in:
HERITAGE-XION 2026-01-06 21:49:46 +01:00
parent c2854d3241
commit 989902d86e
8 changed files with 200 additions and 3 deletions

View File

@ -0,0 +1,29 @@
"""Generate PNG test images with metadata."""
import random
from pathlib import Path
from PIL import Image, PngImagePlugin
dest_dir = Path("tests/assets/test_images")
# Generate 45 PNG images with various metadata
for i in range(1, 46):
# Create a simple colored image
r, g, b = random.randint(50, 200), random.randint(50, 200), random.randint(50, 200)
img = Image.new("RGB", (100, 100), color=(r, g, b))
# Add PNG text metadata
pnginfo = PngImagePlugin.PngInfo()
pnginfo.add_text("Author", f"Test Author {i}")
pnginfo.add_text("Description", f"Test image number {i} for metadata scrubbing")
pnginfo.add_text("Software", "Python PIL Test Generator")
pnginfo.add_text("Copyright", f"Copyright 2024 Test {i}")
pnginfo.add_text("Creation Time", f"2024-01-{i % 28 + 1:02d}")
# Save with metadata
filename = dest_dir / f"generated_test_{i:02d}.png"
img.save(filename, pnginfo=pnginfo)
print("Generated 45 PNG images with metadata")
print(f"Total PNG count: {len(list(dest_dir.glob('*.png')))}")

View File

@ -131,7 +131,7 @@ class ImageHandler(MetadataHandler):
img, self.text_keys_to_delete
)
def save(self, output_path: Optional[str] = None) -> None:
def save(self, output_path: Optional[str | Path] = None) -> None:
"""
Writes the changes to a copy of the original file.
@ -156,6 +156,7 @@ class ImageHandler(MetadataHandler):
with Image.open(destination_file_path) as img:
exif_bytes = piexif.dump(self.processed_metadata)
img.save(destination_file_path, exif=exif_bytes)
elif actual_format == "png":
# PNG: Open original, save fresh copy without metadata
with Image.open(Path(self.filepath)) as img:

View File

@ -40,6 +40,7 @@ class MetadataFactory:
UnsupportedFormatError: If no handler is defined for the file type.
ValueError: If the path is not a valid file.
"""
supported_extensions = ".jpg, .jpeg, .png"
ext = Path(filepath).suffix.lower()
if Path(filepath).is_file():
if ext in [".jpg", ".jpeg", ".png"]:
@ -53,8 +54,11 @@ class MetadataFactory:
# elif ext == ".pptx":
# return PowerPointHandler(filepath)
else:
raise UnsupportedFormatError(f"No handler defined for {ext} files.")
print("i ran")
raise UnsupportedFormatError(
f"No handler defined for {ext} files. we curently only support {supported_extensions} files."
)
else:
raise ValueError(
f"{filepath} is not a file. if you want to process a directory, use the --recursive flag."
f"{filepath} is not a file. if you want to process a directory, use the --recursive or -r flag."
)

0
tests/__init__.py Normal file
View File

0
tests/e2e/__init__.py Normal file
View File

View File

0
tests/unit/__init__.py Normal file
View File

View File

@ -0,0 +1,163 @@
import shutil
from pathlib import Path
import pytest
from src.services.image_handler import ImageHandler
from src.utils.exceptions import MetadataNotFoundError, UnsupportedFormatError
# Test file paths
JPG_TEST_FILE = r"C:\Users\Xheri\development\metadata-scrubber-tool\tests\assets\test_images\DSCN0012.jpg"
PNG_TEST_FILE = r"C:\Users\Xheri\development\metadata-scrubber-tool\tests\assets\test_images\generated_test_45.png"
# ============== success Case Tests ==============
@pytest.mark.parametrize(
"x",
[JPG_TEST_FILE, PNG_TEST_FILE],
)
def test_read_image_metadata(x):
"""
test for reading image metadata
"""
# checks if file exists
assert Path(x).exists()
processor = ImageHandler(x)
metadata = processor.read()
# checks if metadata is not empty
assert processor.metadata == metadata
# checks if tags_to_delete or text_keys_to_delete is not empty
assert (
processor.tags_to_delete is not None
or processor.text_keys_to_delete is not None
)
# checks if metadata is a dictionary
assert isinstance(metadata, dict)
@pytest.mark.parametrize(
"x",
[JPG_TEST_FILE, PNG_TEST_FILE],
)
def test_wipe_image_metadata(x):
"""
test for wiping image metadata
"""
# checks if file exists
assert Path(x).exists()
processor = ImageHandler(x)
metadata = processor.read()
processor.wipe()
# checks if processed_metadata is not equal to metadata
assert processor.processed_metadata != metadata
@pytest.mark.parametrize(
"x",
[JPG_TEST_FILE, PNG_TEST_FILE],
)
def test_save_processed_image_metadata(x):
"""
test for saving image processed metadata to a copy of the file
"""
# creates output directory
output_dir = Path("./tests/assets/output")
output_dir.mkdir(parents=True, exist_ok=True)
processor = ImageHandler(x)
metadata = processor.read()
processor.wipe()
# checks if processed_metadata is not equal to metadata
assert processor.processed_metadata != metadata
# Pass full file path
output_file = output_dir / Path(x).name
processor.save(str(output_file))
# checks if output file exists and then deletes it
assert output_file.exists()
shutil.rmtree(output_dir)
@pytest.mark.parametrize(
"x",
[JPG_TEST_FILE, PNG_TEST_FILE],
)
def test_output_file_has_less_metadata(x):
"""
Test that the output file has metadata stripped
"""
output_dir = Path("./tests/assets/output")
output_dir.mkdir(parents=True, exist_ok=True)
# Process original file
processor = ImageHandler(x)
original_metadata = processor.read()
original_count = len(original_metadata)
processor.wipe()
# Save processed file
output_file = output_dir / Path(x).name
processor.save(str(output_file))
# Read output file and verify metadata is reduced or gone
try:
output_processor = ImageHandler(str(output_file))
output_metadata = output_processor.read()
# Output should have fewer metadata entries
assert len(output_metadata) < original_count
except MetadataNotFoundError:
# If no metadata found, that's expected for fully stripped files
pass
# clean up
shutil.rmtree(output_dir)
def test_format_detection_works():
"""
Test that format detection uses Pillow, not file extension
"""
handler = ImageHandler(JPG_TEST_FILE)
detected = handler._detect_format()
assert detected == "jpeg"
handler_png = ImageHandler(PNG_TEST_FILE)
detected_png = handler_png._detect_format()
assert detected_png == "png"
# ============== Error Case Tests ==============
def test_unsupported_format_raises_error(tmp_path):
"""
Test that unsupported file formats raise an error (PIL or custom)
"""
from PIL import UnidentifiedImageError
# Create a fake text file
fake_file = tmp_path / "test.txt"
fake_file.write_text("not an image")
handler = ImageHandler(str(fake_file))
# Either our UnsupportedFormatError or PIL's UnidentifiedImageError is acceptable
with pytest.raises((UnsupportedFormatError, UnidentifiedImageError)):
handler.read()
def test_save_without_output_path_raises_error():
"""
Test that save() raises ValueError when output_path is None
"""
handler = ImageHandler(JPG_TEST_FILE)
handler.read()
handler.wipe()
with pytest.raises(ValueError):
handler.save(None)