"""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