metadata-scrubber-tool:
added unit test for image handler
This commit is contained in:
parent
c2854d3241
commit
989902d86e
|
|
@ -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')))}")
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,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)
|
||||
Loading…
Reference in New Issue