feat(phase1): add image storage utilities
- 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
This commit is contained in:
181
backend/tests/test_schema.py
Normal file
181
backend/tests/test_schema.py
Normal file
@@ -0,0 +1,181 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user