"""Test Pydantic schemas""" from decimal import Decimal import pytest from pydantic import ValidationError 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, "price": 19.99} # Exceeds 200 character limit 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