phase 5
This commit is contained in:
2
backend/tests/images/__init__.py
Normal file
2
backend/tests/images/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Image tests package."""
|
||||
|
||||
79
backend/tests/images/test_processing.py
Normal file
79
backend/tests/images/test_processing.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Tests for image processing and thumbnail generation."""
|
||||
|
||||
import io
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from PIL import Image as PILImage
|
||||
|
||||
from app.images.processing import generate_thumbnails
|
||||
|
||||
|
||||
class TestThumbnailGeneration:
|
||||
"""Tests for thumbnail generation."""
|
||||
|
||||
def test_generate_thumbnails_creates_all_sizes(self):
|
||||
"""Test that thumbnails are generated for all quality levels."""
|
||||
# Create a test image
|
||||
image_id = uuid4()
|
||||
image = PILImage.new("RGB", (2000, 1500), color="red")
|
||||
buffer = io.BytesIO()
|
||||
image.save(buffer, format="JPEG")
|
||||
contents = buffer.getvalue()
|
||||
|
||||
# Mock storage client to avoid actual uploads
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
with patch("app.images.processing.get_storage_client") as mock_storage:
|
||||
mock_storage.return_value.put_object = MagicMock()
|
||||
|
||||
# Generate thumbnails
|
||||
thumbnail_paths = generate_thumbnails(image_id, "test/path.jpg", contents)
|
||||
|
||||
# Verify all sizes created
|
||||
assert "low" in thumbnail_paths
|
||||
assert "medium" in thumbnail_paths
|
||||
assert "high" in thumbnail_paths
|
||||
|
||||
# Verify storage was called
|
||||
assert mock_storage.return_value.put_object.call_count >= 2
|
||||
|
||||
def test_skip_thumbnail_for_small_images(self):
|
||||
"""Test that thumbnails are skipped if image is smaller than target size."""
|
||||
# Create a small test image (smaller than low quality threshold)
|
||||
image_id = uuid4()
|
||||
image = PILImage.new("RGB", (500, 375), color="blue")
|
||||
buffer = io.BytesIO()
|
||||
image.save(buffer, format="JPEG")
|
||||
contents = buffer.getvalue()
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
with patch("app.images.processing.get_storage_client") as mock_storage:
|
||||
mock_storage.return_value.put_object = MagicMock()
|
||||
|
||||
# Generate thumbnails
|
||||
thumbnail_paths = generate_thumbnails(image_id, "test/small.jpg", contents)
|
||||
|
||||
# Should use original path for all sizes
|
||||
assert thumbnail_paths["low"] == "test/small.jpg"
|
||||
|
||||
def test_handles_transparent_images(self):
|
||||
"""Test conversion of transparent images to RGB."""
|
||||
# Create RGBA image
|
||||
image_id = uuid4()
|
||||
image = PILImage.new("RGBA", (2000, 1500), color=(255, 0, 0, 128))
|
||||
buffer = io.BytesIO()
|
||||
image.save(buffer, format="PNG")
|
||||
contents = buffer.getvalue()
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
with patch("app.images.processing.get_storage_client") as mock_storage:
|
||||
mock_storage.return_value.put_object = MagicMock()
|
||||
|
||||
# Should not raise exception
|
||||
thumbnail_paths = generate_thumbnails(image_id, "test/transparent.png", contents)
|
||||
|
||||
assert len(thumbnail_paths) > 0
|
||||
|
||||
82
backend/tests/images/test_validation.py
Normal file
82
backend/tests/images/test_validation.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Tests for file validation."""
|
||||
|
||||
import io
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException, UploadFile
|
||||
|
||||
from app.images.validation import sanitize_filename, validate_image_file
|
||||
|
||||
|
||||
class TestSanitizeFilename:
|
||||
"""Tests for filename sanitization."""
|
||||
|
||||
def test_sanitize_normal_filename(self):
|
||||
"""Test sanitizing normal filename."""
|
||||
assert sanitize_filename("image.jpg") == "image.jpg"
|
||||
assert sanitize_filename("my_photo-2025.png") == "my_photo-2025.png"
|
||||
|
||||
def test_sanitize_path_traversal(self):
|
||||
"""Test preventing path traversal."""
|
||||
assert "/" not in sanitize_filename("../../../etc/passwd")
|
||||
assert "\\" not in sanitize_filename("..\\..\\..\\windows\\system32")
|
||||
|
||||
def test_sanitize_special_characters(self):
|
||||
"""Test removing special characters."""
|
||||
result = sanitize_filename("file name with spaces!@#.jpg")
|
||||
assert " " not in result or result == "file_name_with_spaces___.jpg"
|
||||
|
||||
def test_sanitize_long_filename(self):
|
||||
"""Test truncating long filenames."""
|
||||
long_name = "a" * 300 + ".jpg"
|
||||
result = sanitize_filename(long_name)
|
||||
assert len(result) <= 255
|
||||
assert result.endswith(".jpg")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestValidateImageFile:
|
||||
"""Tests for image file validation."""
|
||||
|
||||
async def test_validate_empty_file(self):
|
||||
"""Test rejection of empty files."""
|
||||
mock_file = AsyncMock(spec=UploadFile)
|
||||
mock_file.read = AsyncMock(return_value=b"")
|
||||
mock_file.seek = AsyncMock()
|
||||
mock_file.filename = "empty.jpg"
|
||||
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await validate_image_file(mock_file)
|
||||
|
||||
assert exc.value.status_code == 400
|
||||
assert "empty" in exc.value.detail.lower()
|
||||
|
||||
async def test_validate_file_too_large(self):
|
||||
"""Test rejection of oversized files."""
|
||||
# Create 60MB file
|
||||
large_data = b"x" * (60 * 1024 * 1024)
|
||||
mock_file = AsyncMock(spec=UploadFile)
|
||||
mock_file.read = AsyncMock(return_value=large_data)
|
||||
mock_file.seek = AsyncMock()
|
||||
mock_file.filename = "large.jpg"
|
||||
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await validate_image_file(mock_file)
|
||||
|
||||
assert exc.value.status_code == 413
|
||||
assert "too large" in exc.value.detail.lower()
|
||||
|
||||
async def test_validate_invalid_extension(self):
|
||||
"""Test rejection of invalid extensions."""
|
||||
mock_file = AsyncMock(spec=UploadFile)
|
||||
mock_file.read = AsyncMock(return_value=b"fake image data")
|
||||
mock_file.seek = AsyncMock()
|
||||
mock_file.filename = "document.pdf"
|
||||
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await validate_image_file(mock_file)
|
||||
|
||||
assert exc.value.status_code == 400
|
||||
assert "extension" in exc.value.detail.lower()
|
||||
|
||||
Reference in New Issue
Block a user