kanban-app/backend/tests/test_schemas.py
2026-02-24 19:32:35 +03:00

277 lines
No EOL
8.2 KiB
Python

"""Test Pydantic schemas"""
import pytest
from pydantic import ValidationError
from decimal import Decimal
from app.schemas import ProductCreateRequest, ProductResponse
class TestProductCreateRequestSchema:
"""Test ProductCreateRequest schema"""
@pytest.mark.unit
def test_valid_product_request(self):
"""Test valid product creation request"""
data = {
'name': 'Handcrafted Wooden Bowl',
'description': 'A beautiful handcrafted bowl',
'price': 45.99,
'stock': 10,
'image_url': 'https://example.com/bowl.jpg'
}
product = ProductCreateRequest(**data)
assert product.name == data['name']
assert product.description == data['description']
assert product.price == Decimal('45.99')
assert product.stock == 10
assert product.image_url == data['image_url']
@pytest.mark.unit
def test_minimal_valid_request(self):
"""Test minimal valid request (only required fields)"""
data = {
'name': 'Simple Product',
'price': 19.99
}
product = ProductCreateRequest(**data)
assert product.name == 'Simple Product'
assert product.price == Decimal('19.99')
assert product.stock == 0
assert product.description is None
assert product.image_url is None
@pytest.mark.unit
def test_missing_name(self):
"""Test request with missing name"""
data = {
'price': 19.99
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any(error['loc'] == ('name',) for error in errors)
@pytest.mark.unit
def test_missing_price(self):
"""Test request with missing price"""
data = {
'name': 'Test Product'
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any(error['loc'] == ('price',) for error in errors)
@pytest.mark.unit
def test_invalid_price_negative(self):
"""Test request with negative price"""
data = {
'name': 'Test Product',
'price': -10.99
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any(error['type'] == 'greater_than' for error in errors)
@pytest.mark.unit
def test_invalid_price_zero(self):
"""Test request with zero price"""
data = {
'name': 'Test Product',
'price': 0.0
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any(error['type'] == 'greater_than' for error in errors)
@pytest.mark.unit
def test_invalid_price_too_many_decimals(self):
"""Test request with too many decimal places"""
data = {
'name': 'Test Product',
'price': 10.999
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any('decimal places' in str(error).lower() for error in errors)
@pytest.mark.unit
def test_invalid_stock_negative(self):
"""Test request with negative stock"""
data = {
'name': 'Test Product',
'price': 19.99,
'stock': -5
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any(error['type'] == 'greater_than_equal' for error in errors)
@pytest.mark.unit
def test_name_too_long(self):
"""Test request with name exceeding max length"""
data = {
'name': 'A' * 201, # Exceeds 200 character limit
'price': 19.99
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any(error['loc'] == ('name',) for error in errors)
@pytest.mark.unit
def test_image_url_too_long(self):
"""Test request with image_url exceeding max length"""
data = {
'name': 'Test Product',
'price': 19.99,
'image_url': 'A' * 501 # Exceeds 500 character limit
}
with pytest.raises(ValidationError) as exc_info:
ProductCreateRequest(**data)
errors = exc_info.value.errors()
assert any(error['loc'] == ('image_url',) for error in errors)
@pytest.mark.unit
def test_price_string_conversion(self):
"""Test price string to Decimal conversion"""
data = {
'name': 'Test Product',
'price': '29.99'
}
product = ProductCreateRequest(**data)
assert product.price == Decimal('29.99')
@pytest.mark.unit
def test_stock_string_conversion(self):
"""Test stock string to int conversion"""
data = {
'name': 'Test Product',
'price': 19.99,
'stock': '10'
}
product = ProductCreateRequest(**data)
assert product.stock == 10
assert isinstance(product.stock, int)
class TestProductResponseSchema:
"""Test ProductResponse schema"""
@pytest.mark.unit
def test_valid_product_response(self):
"""Test valid product response"""
data = {
'id': 1,
'name': 'Test Product',
'description': 'A test product',
'price': 45.99,
'stock': 10,
'image_url': 'https://example.com/product.jpg',
'is_active': True,
'created_at': '2024-01-15T10:30:00',
'updated_at': '2024-01-15T10:30:00'
}
product = ProductResponse(**data)
assert product.id == 1
assert product.name == 'Test Product'
assert product.price == 45.99
assert product.stock == 10
assert product.is_active is True
@pytest.mark.unit
def test_product_response_with_none_fields(self):
"""Test product response with optional None fields"""
data = {
'id': 1,
'name': 'Test Product',
'price': 19.99,
'stock': 0,
'is_active': True
}
product = ProductResponse(**data)
assert product.description is None
assert product.image_url is None
assert product.created_at is None
assert product.updated_at is None
@pytest.mark.unit
def test_model_validate_from_sqlalchemy(self, db_session):
"""Test validating SQLAlchemy model to Pydantic schema"""
from app.models import Product
db_product = Product(
name='Test Product',
description='A test product',
price=Decimal('45.99'),
stock=10
)
db_session.add(db_product)
db_session.commit()
# Validate using model_validate (for SQLAlchemy models)
response = ProductResponse.model_validate(db_product)
assert response.name == 'Test Product'
assert response.price == 45.99
assert response.stock == 10
@pytest.mark.unit
def test_model_dump(self):
"""Test model_dump method"""
data = {
'id': 1,
'name': 'Test Product',
'price': 19.99,
'stock': 5,
'is_active': True
}
product = ProductResponse(**data)
dumped = product.model_dump()
assert isinstance(dumped, dict)
assert dumped['id'] == 1
assert dumped['name'] == 'Test Product'
assert dumped['price'] == 19.99
@pytest.mark.unit
def test_model_dump_json(self):
"""Test model_dump_json method"""
data = {
'id': 1,
'name': 'Test Product',
'price': 19.99,
'stock': 5,
'is_active': True
}
product = ProductResponse(**data)
json_str = product.model_dump_json()
assert isinstance(json_str, str)
assert 'Test Product' in json_str