# tests/unit/models/schema/test_customer.py """Unit tests for customer Pydantic schemas.""" import pytest from pydantic import ValidationError from models.schema.customer import ( CustomerAddressCreate, CustomerAddressResponse, CustomerAddressUpdate, CustomerPreferencesUpdate, CustomerRegister, CustomerResponse, CustomerUpdate, ) @pytest.mark.unit @pytest.mark.schema class TestCustomerRegisterSchema: """Test CustomerRegister schema validation.""" def test_valid_registration(self): """Test valid registration data.""" customer = CustomerRegister( email="customer@example.com", password="Password123", first_name="John", last_name="Doe", ) assert customer.email == "customer@example.com" assert customer.first_name == "John" assert customer.last_name == "Doe" def test_email_normalized_to_lowercase(self): """Test email is normalized to lowercase.""" customer = CustomerRegister( email="Customer@Example.COM", password="Password123", first_name="John", last_name="Doe", ) assert customer.email == "customer@example.com" def test_invalid_email(self): """Test invalid email raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: CustomerRegister( email="not-an-email", password="Password123", first_name="John", last_name="Doe", ) assert "email" in str(exc_info.value).lower() def test_password_min_length(self): """Test password must be at least 8 characters.""" with pytest.raises(ValidationError) as exc_info: CustomerRegister( email="customer@example.com", password="Pass1", first_name="John", last_name="Doe", ) assert "password" in str(exc_info.value).lower() def test_password_requires_digit(self): """Test password must contain at least one digit.""" with pytest.raises(ValidationError) as exc_info: CustomerRegister( email="customer@example.com", password="Password", first_name="John", last_name="Doe", ) assert "digit" in str(exc_info.value).lower() def test_password_requires_letter(self): """Test password must contain at least one letter.""" with pytest.raises(ValidationError) as exc_info: CustomerRegister( email="customer@example.com", password="12345678", first_name="John", last_name="Doe", ) assert "letter" in str(exc_info.value).lower() def test_first_name_required(self): """Test first_name is required.""" with pytest.raises(ValidationError) as exc_info: CustomerRegister( email="customer@example.com", password="Password123", last_name="Doe", ) assert "first_name" in str(exc_info.value).lower() def test_marketing_consent_default(self): """Test marketing_consent defaults to False.""" customer = CustomerRegister( email="customer@example.com", password="Password123", first_name="John", last_name="Doe", ) assert customer.marketing_consent is False def test_optional_phone(self): """Test optional phone field.""" customer = CustomerRegister( email="customer@example.com", password="Password123", first_name="John", last_name="Doe", phone="+352 123 456", ) assert customer.phone == "+352 123 456" @pytest.mark.unit @pytest.mark.schema class TestCustomerUpdateSchema: """Test CustomerUpdate schema validation.""" def test_partial_update(self): """Test partial update with only some fields.""" update = CustomerUpdate(first_name="Jane") assert update.first_name == "Jane" assert update.last_name is None assert update.email is None def test_empty_update_is_valid(self): """Test empty update is valid.""" update = CustomerUpdate() assert update.model_dump(exclude_unset=True) == {} def test_email_normalized_to_lowercase(self): """Test email is normalized to lowercase.""" update = CustomerUpdate(email="NewEmail@Example.COM") assert update.email == "newemail@example.com" @pytest.mark.unit @pytest.mark.schema class TestCustomerResponseSchema: """Test CustomerResponse schema.""" def test_from_dict(self): """Test creating response from dict.""" from datetime import datetime from decimal import Decimal data = { "id": 1, "vendor_id": 1, "email": "customer@example.com", "first_name": "John", "last_name": "Doe", "phone": None, "customer_number": "CUST001", "marketing_consent": False, "preferred_language": "fr", "last_order_date": None, "total_orders": 5, "total_spent": Decimal("500.00"), "is_active": True, "created_at": datetime.now(), "updated_at": datetime.now(), } response = CustomerResponse(**data) assert response.id == 1 assert response.customer_number == "CUST001" assert response.total_orders == 5 @pytest.mark.unit @pytest.mark.schema class TestCustomerAddressCreateSchema: """Test CustomerAddressCreate schema validation.""" def test_valid_shipping_address(self): """Test valid shipping address creation.""" address = CustomerAddressCreate( address_type="shipping", first_name="John", last_name="Doe", address_line_1="123 Main St", city="Luxembourg", postal_code="L-1234", country="Luxembourg", ) assert address.address_type == "shipping" assert address.city == "Luxembourg" def test_valid_billing_address(self): """Test valid billing address creation.""" address = CustomerAddressCreate( address_type="billing", first_name="John", last_name="Doe", address_line_1="123 Main St", city="Luxembourg", postal_code="L-1234", country="Luxembourg", ) assert address.address_type == "billing" def test_invalid_address_type(self): """Test invalid address_type raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: CustomerAddressCreate( address_type="delivery", first_name="John", last_name="Doe", address_line_1="123 Main St", city="Luxembourg", postal_code="L-1234", country="Luxembourg", ) assert "address_type" in str(exc_info.value).lower() def test_is_default_defaults_to_false(self): """Test is_default defaults to False.""" address = CustomerAddressCreate( address_type="shipping", first_name="John", last_name="Doe", address_line_1="123 Main St", city="Luxembourg", postal_code="L-1234", country="Luxembourg", ) assert address.is_default is False def test_optional_company(self): """Test optional company field.""" address = CustomerAddressCreate( address_type="shipping", first_name="John", last_name="Doe", company="Tech Corp", address_line_1="123 Main St", city="Luxembourg", postal_code="L-1234", country="Luxembourg", ) assert address.company == "Tech Corp" def test_optional_address_line_2(self): """Test optional address_line_2 field.""" address = CustomerAddressCreate( address_type="shipping", first_name="John", last_name="Doe", address_line_1="123 Main St", address_line_2="Apt 4B", city="Luxembourg", postal_code="L-1234", country="Luxembourg", ) assert address.address_line_2 == "Apt 4B" def test_required_fields(self): """Test required fields.""" with pytest.raises(ValidationError): CustomerAddressCreate( address_type="shipping", first_name="John", # missing last_name, address_line_1, city, postal_code, country ) @pytest.mark.unit @pytest.mark.schema class TestCustomerAddressUpdateSchema: """Test CustomerAddressUpdate schema validation.""" def test_partial_update(self): """Test partial update with only some fields.""" update = CustomerAddressUpdate(city="Esch-sur-Alzette") assert update.city == "Esch-sur-Alzette" assert update.first_name is None def test_empty_update_is_valid(self): """Test empty update is valid.""" update = CustomerAddressUpdate() assert update.model_dump(exclude_unset=True) == {} def test_address_type_validation(self): """Test address_type validation in update.""" with pytest.raises(ValidationError): CustomerAddressUpdate(address_type="invalid") def test_valid_address_type_update(self): """Test valid address_type values in update.""" shipping = CustomerAddressUpdate(address_type="shipping") billing = CustomerAddressUpdate(address_type="billing") assert shipping.address_type == "shipping" assert billing.address_type == "billing" @pytest.mark.unit @pytest.mark.schema class TestCustomerAddressResponseSchema: """Test CustomerAddressResponse schema.""" def test_from_dict(self): """Test creating response from dict.""" from datetime import datetime data = { "id": 1, "vendor_id": 1, "customer_id": 1, "address_type": "shipping", "first_name": "John", "last_name": "Doe", "company": None, "address_line_1": "123 Main St", "address_line_2": None, "city": "Luxembourg", "postal_code": "L-1234", "country": "Luxembourg", "is_default": True, "created_at": datetime.now(), "updated_at": datetime.now(), } response = CustomerAddressResponse(**data) assert response.id == 1 assert response.is_default is True assert response.address_type == "shipping" @pytest.mark.unit @pytest.mark.schema class TestCustomerPreferencesUpdateSchema: """Test CustomerPreferencesUpdate schema validation.""" def test_partial_update(self): """Test partial update with only some fields.""" update = CustomerPreferencesUpdate(marketing_consent=True) assert update.marketing_consent is True assert update.preferred_language is None def test_language_update(self): """Test language preference update.""" update = CustomerPreferencesUpdate(preferred_language="fr") assert update.preferred_language == "fr" def test_currency_update(self): """Test currency preference update.""" update = CustomerPreferencesUpdate(currency="EUR") assert update.currency == "EUR" def test_notification_preferences(self): """Test notification preferences dict.""" update = CustomerPreferencesUpdate( notification_preferences={ "email": True, "sms": False, "push": True, } ) assert update.notification_preferences["email"] is True assert update.notification_preferences["sms"] is False