Files
orion/app/modules/customers/tests/unit/test_customer_schema.py
Samir Boulahtit 688896d856
Some checks failed
CI / ruff (push) Successful in 9s
CI / architecture (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / audit (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
fix: add .dockerignore and env_file to docker-compose
Prevents .env from being baked into Docker image (was overriding
config defaults). Adds env_file directive so containers load host
.env properly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 20:01:21 +01:00

359 lines
12 KiB
Python

# tests/unit/models/schema/test_customer.py
"""Unit tests for customer Pydantic schemas."""
import pytest
from pydantic import ValidationError
from app.modules.customers.schemas 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", # noqa: SEC-001
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", # noqa: SEC-001
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", # noqa: SEC-001
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", # noqa: SEC-001
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", # noqa: SEC-001
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", # noqa: SEC-001
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", # noqa: SEC-001
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", # noqa: SEC-001
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", # noqa: SEC-001
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,
"store_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_name="Luxembourg",
country_iso="LU",
)
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_name="Luxembourg",
country_iso="LU",
)
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_name="Luxembourg",
country_iso="LU",
)
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_name="Luxembourg",
country_iso="LU",
)
assert address.is_default is False
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_name="Luxembourg",
country_iso="LU",
)
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,
"store_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_name": "Luxembourg",
"country_iso": "LU",
"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