- Create backend/services/image_storage.py with 4 core functions:
- sanitize_filename(): remove unsafe chars, limit to 255 chars, convert to lowercase
- get_unique_filename(): handle collisions with UUID suffix (format: {name}_{uuid8}_{variant}.jpg)
- ensure_image_directories(): create /images/ root and category subdirs on startup
- save_image(): save bytes to /images/{category}/{filename}, returns relative path
- Create comprehensive test suite (22 tests) covering all functionality
- Integrate ensure_image_directories() into FastAPI startup event
- Directory structure: /images/{category}/{filename}
- Collision handling: auto-suffix with UUID if filename exists
- All tests passing, pathlib.Path for safe operations
182 lines
6.4 KiB
Python
182 lines
6.4 KiB
Python
import pytest
|
|
from datetime import datetime
|
|
from sqlalchemy.orm import Session
|
|
|
|
from backend.models import Item, AuditLog, User
|
|
|
|
|
|
class TestItemPhotoFields:
|
|
"""Tests for Item model photo fields."""
|
|
|
|
def test_item_has_photo_path_field(self, test_db: Session):
|
|
"""Verify Item model has photo_path field."""
|
|
# Verify the field exists as an attribute
|
|
assert hasattr(Item, "photo_path"), "Item model must have photo_path field"
|
|
|
|
def test_item_has_photo_thumbnail_path_field(self, test_db: Session):
|
|
"""Verify Item model has photo_thumbnail_path field."""
|
|
assert hasattr(Item, "photo_thumbnail_path"), "Item model must have photo_thumbnail_path field"
|
|
|
|
def test_item_has_photo_upload_date_field(self, test_db: Session):
|
|
"""Verify Item model has photo_upload_date field."""
|
|
assert hasattr(Item, "photo_upload_date"), "Item model must have photo_upload_date field"
|
|
|
|
def test_item_photo_fields_are_nullable(self, test_db: Session):
|
|
"""Verify that photo fields are nullable - can create item without photos."""
|
|
# Create item without photo fields
|
|
user = User(username="test_user", hashed_password="hashed", role="user", origin="local")
|
|
test_db.add(user)
|
|
test_db.commit()
|
|
|
|
item = Item(
|
|
barcode="TEST-001",
|
|
name="Test Item",
|
|
category="Electronics",
|
|
quantity=5.0
|
|
)
|
|
test_db.add(item)
|
|
test_db.commit()
|
|
test_db.refresh(item)
|
|
|
|
# Verify item was created and photo fields are None
|
|
assert item.id is not None
|
|
assert item.photo_path is None
|
|
assert item.photo_thumbnail_path is None
|
|
assert item.photo_upload_date is None
|
|
|
|
def test_item_photo_fields_can_be_set(self, test_db: Session):
|
|
"""Verify that photo fields can be set with values."""
|
|
user = User(username="test_user2", hashed_password="hashed", role="user", origin="local")
|
|
test_db.add(user)
|
|
test_db.commit()
|
|
|
|
upload_date = datetime.now()
|
|
item = Item(
|
|
barcode="TEST-002",
|
|
name="Test Item with Photo",
|
|
category="Electronics",
|
|
quantity=3.0,
|
|
photo_path="networking/SFP-LR_original.jpg",
|
|
photo_thumbnail_path="networking/SFP-LR_thumb.jpg",
|
|
photo_upload_date=upload_date
|
|
)
|
|
test_db.add(item)
|
|
test_db.commit()
|
|
test_db.refresh(item)
|
|
|
|
# Verify fields were set correctly
|
|
assert item.photo_path == "networking/SFP-LR_original.jpg"
|
|
assert item.photo_thumbnail_path == "networking/SFP-LR_thumb.jpg"
|
|
assert item.photo_upload_date == upload_date
|
|
|
|
def test_item_photo_path_is_string_type(self, test_db: Session):
|
|
"""Verify photo_path field accepts string values."""
|
|
user = User(username="test_user3", hashed_password="hashed", role="user", origin="local")
|
|
test_db.add(user)
|
|
test_db.commit()
|
|
|
|
path = "path/to/image.jpg"
|
|
item = Item(
|
|
barcode="TEST-003",
|
|
name="Item",
|
|
category="Electronics",
|
|
photo_path=path
|
|
)
|
|
test_db.add(item)
|
|
test_db.commit()
|
|
test_db.refresh(item)
|
|
|
|
assert isinstance(item.photo_path, str)
|
|
assert item.photo_path == path
|
|
|
|
def test_item_photo_upload_date_is_datetime_type(self, test_db: Session):
|
|
"""Verify photo_upload_date field accepts datetime values."""
|
|
user = User(username="test_user4", hashed_password="hashed", role="user", origin="local")
|
|
test_db.add(user)
|
|
test_db.commit()
|
|
|
|
now = datetime.now()
|
|
item = Item(
|
|
barcode="TEST-004",
|
|
name="Item",
|
|
category="Electronics",
|
|
photo_upload_date=now
|
|
)
|
|
test_db.add(item)
|
|
test_db.commit()
|
|
test_db.refresh(item)
|
|
|
|
assert isinstance(item.photo_upload_date, datetime)
|
|
assert item.photo_upload_date == now
|
|
|
|
def test_existing_item_without_photos_unaffected(self, test_db: Session):
|
|
"""Verify that existing items without photos continue to work."""
|
|
user = User(username="test_user5", hashed_password="hashed", role="user", origin="local")
|
|
test_db.add(user)
|
|
test_db.commit()
|
|
|
|
# Create item without photo fields (legacy behavior)
|
|
item = Item(
|
|
barcode="LEGACY-001",
|
|
name="Legacy Item",
|
|
category="Electronics",
|
|
quantity=10.0,
|
|
part_number="PN-999",
|
|
description="A legacy item"
|
|
)
|
|
test_db.add(item)
|
|
test_db.commit()
|
|
test_db.refresh(item)
|
|
|
|
# Verify all existing fields still work
|
|
assert item.barcode == "LEGACY-001"
|
|
assert item.name == "Legacy Item"
|
|
assert item.category == "Electronics"
|
|
assert item.quantity == 10.0
|
|
assert item.part_number == "PN-999"
|
|
assert item.description == "A legacy item"
|
|
# Photo fields should be None for legacy items
|
|
assert item.photo_path is None
|
|
assert item.photo_thumbnail_path is None
|
|
assert item.photo_upload_date is None
|
|
|
|
|
|
class TestBoxModel:
|
|
"""Tests for Box model photo fields (if Box model exists)."""
|
|
|
|
def test_box_model_exists(self):
|
|
"""Verify Box model exists in database schema."""
|
|
# Import Box model if it exists
|
|
try:
|
|
from backend.models import Box
|
|
assert Box is not None
|
|
except ImportError:
|
|
pytest.skip("Box model not yet implemented")
|
|
|
|
def test_box_photo_fields_exist(self):
|
|
"""Verify Box model has photo fields if it exists."""
|
|
try:
|
|
from backend.models import Box
|
|
assert hasattr(Box, "photo_path")
|
|
assert hasattr(Box, "photo_thumbnail_path")
|
|
assert hasattr(Box, "photo_upload_date")
|
|
except ImportError:
|
|
pytest.skip("Box model not yet implemented")
|
|
|
|
def test_box_photo_fields_are_nullable(self, test_db: Session):
|
|
"""Verify Box photo fields are nullable."""
|
|
try:
|
|
from backend.models import Box
|
|
|
|
box = Box(name="Test Box")
|
|
test_db.add(box)
|
|
test_db.commit()
|
|
test_db.refresh(box)
|
|
|
|
assert box.id is not None
|
|
assert box.photo_path is None
|
|
assert box.photo_thumbnail_path is None
|
|
assert box.photo_upload_date is None
|
|
except ImportError:
|
|
pytest.skip("Box model not yet implemented")
|