Files
orion/tests/unit/services/test_customer_address_service.py
Samir Boulahtit b5b32fb351 feat: add customer multiple addresses management
- Add CustomerAddressService with CRUD operations
- Add shop API endpoints for address management (GET, POST, PUT, DELETE)
- Add set default endpoint for address type
- Implement addresses.html with full UI (cards, modals, Alpine.js)
- Integrate saved addresses in checkout flow
  - Address selector dropdowns for shipping/billing
  - Auto-select default addresses
  - Save new address checkbox option
- Add country_iso field alongside country_name
- Add address exceptions (NotFound, LimitExceeded, InvalidType)
- Max 10 addresses per customer limit
- One default address per type (shipping/billing)
- Add unit tests for CustomerAddressService
- Add integration tests for shop addresses API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 19:16:35 +01:00

454 lines
14 KiB
Python

# tests/unit/services/test_customer_address_service.py
"""
Unit tests for CustomerAddressService.
"""
import pytest
from app.exceptions import AddressLimitExceededException, AddressNotFoundException
from app.services.customer_address_service import CustomerAddressService
from models.database.customer import CustomerAddress
from models.schema.customer import CustomerAddressCreate, CustomerAddressUpdate
@pytest.fixture
def address_service():
"""Create CustomerAddressService instance."""
return CustomerAddressService()
@pytest.fixture
def multiple_addresses(db, test_vendor, test_customer):
"""Create multiple addresses for testing."""
addresses = []
for i in range(3):
address = CustomerAddress(
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_type="shipping" if i < 2 else "billing",
first_name=f"First{i}",
last_name=f"Last{i}",
address_line_1=f"{i+1} Test Street",
city="Luxembourg",
postal_code=f"L-{1000+i}",
country_name="Luxembourg",
country_iso="LU",
is_default=(i == 0), # First shipping is default
)
db.add(address)
addresses.append(address)
db.commit()
for a in addresses:
db.refresh(a)
return addresses
@pytest.mark.unit
class TestCustomerAddressServiceList:
"""Tests for list_addresses method."""
def test_list_addresses_empty(self, db, address_service, test_vendor, test_customer):
"""Test listing addresses when none exist."""
addresses = address_service.list_addresses(
db, vendor_id=test_vendor.id, customer_id=test_customer.id
)
assert addresses == []
def test_list_addresses_basic(
self, db, address_service, test_vendor, test_customer, test_customer_address
):
"""Test basic address listing."""
addresses = address_service.list_addresses(
db, vendor_id=test_vendor.id, customer_id=test_customer.id
)
assert len(addresses) == 1
assert addresses[0].id == test_customer_address.id
def test_list_addresses_ordered_by_default(
self, db, address_service, test_vendor, test_customer, multiple_addresses
):
"""Test addresses are ordered by default flag first."""
addresses = address_service.list_addresses(
db, vendor_id=test_vendor.id, customer_id=test_customer.id
)
# Default address should be first
assert addresses[0].is_default is True
def test_list_addresses_vendor_isolation(
self, db, address_service, test_vendor, test_customer, test_customer_address
):
"""Test addresses are isolated by vendor."""
# Query with different vendor ID
addresses = address_service.list_addresses(
db, vendor_id=99999, customer_id=test_customer.id
)
assert addresses == []
@pytest.mark.unit
class TestCustomerAddressServiceGet:
"""Tests for get_address method."""
def test_get_address_success(
self, db, address_service, test_vendor, test_customer, test_customer_address
):
"""Test getting address by ID."""
address = address_service.get_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=test_customer_address.id,
)
assert address.id == test_customer_address.id
assert address.first_name == test_customer_address.first_name
def test_get_address_not_found(
self, db, address_service, test_vendor, test_customer
):
"""Test error when address not found."""
with pytest.raises(AddressNotFoundException):
address_service.get_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=99999,
)
def test_get_address_wrong_customer(
self, db, address_service, test_vendor, test_customer, test_customer_address
):
"""Test cannot get another customer's address."""
with pytest.raises(AddressNotFoundException):
address_service.get_address(
db,
vendor_id=test_vendor.id,
customer_id=99999, # Different customer
address_id=test_customer_address.id,
)
@pytest.mark.unit
class TestCustomerAddressServiceGetDefault:
"""Tests for get_default_address method."""
def test_get_default_address_exists(
self, db, address_service, test_vendor, test_customer, multiple_addresses
):
"""Test getting default shipping address."""
address = address_service.get_default_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_type="shipping",
)
assert address is not None
assert address.is_default is True
assert address.address_type == "shipping"
def test_get_default_address_not_set(
self, db, address_service, test_vendor, test_customer, multiple_addresses
):
"""Test getting default billing when none is set."""
# Remove default from billing (none was set as default)
address = address_service.get_default_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_type="billing",
)
# The billing address exists but is not default
assert address is None
@pytest.mark.unit
class TestCustomerAddressServiceCreate:
"""Tests for create_address method."""
def test_create_address_success(
self, db, address_service, test_vendor, test_customer
):
"""Test creating a new address."""
address_data = CustomerAddressCreate(
address_type="shipping",
first_name="John",
last_name="Doe",
address_line_1="123 New Street",
city="Luxembourg",
postal_code="L-1234",
country_name="Luxembourg",
country_iso="LU",
is_default=False,
)
address = address_service.create_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_data=address_data,
)
db.commit()
assert address.id is not None
assert address.first_name == "John"
assert address.last_name == "Doe"
assert address.country_iso == "LU"
assert address.country_name == "Luxembourg"
def test_create_address_with_company(
self, db, address_service, test_vendor, test_customer
):
"""Test creating address with company name."""
address_data = CustomerAddressCreate(
address_type="billing",
first_name="Jane",
last_name="Doe",
company="Acme Corp",
address_line_1="456 Business Ave",
city="Luxembourg",
postal_code="L-5678",
country_name="Luxembourg",
country_iso="LU",
is_default=False,
)
address = address_service.create_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_data=address_data,
)
db.commit()
assert address.company == "Acme Corp"
def test_create_address_default_clears_others(
self, db, address_service, test_vendor, test_customer, multiple_addresses
):
"""Test creating default address clears other defaults of same type."""
# First address is default shipping
assert multiple_addresses[0].is_default is True
address_data = CustomerAddressCreate(
address_type="shipping",
first_name="New",
last_name="Default",
address_line_1="789 Main St",
city="Luxembourg",
postal_code="L-9999",
country_name="Luxembourg",
country_iso="LU",
is_default=True,
)
new_address = address_service.create_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_data=address_data,
)
db.commit()
# New address should be default
assert new_address.is_default is True
# Old default should be cleared
db.refresh(multiple_addresses[0])
assert multiple_addresses[0].is_default is False
def test_create_address_limit_exceeded(
self, db, address_service, test_vendor, test_customer
):
"""Test error when max addresses reached."""
# Create 10 addresses (max limit)
for i in range(10):
addr = CustomerAddress(
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_type="shipping",
first_name=f"Test{i}",
last_name="User",
address_line_1=f"{i} Street",
city="City",
postal_code="12345",
country_name="Luxembourg",
country_iso="LU",
)
db.add(addr)
db.commit()
# Try to create 11th address
address_data = CustomerAddressCreate(
address_type="shipping",
first_name="Eleventh",
last_name="User",
address_line_1="11 Street",
city="City",
postal_code="12345",
country_name="Luxembourg",
country_iso="LU",
is_default=False,
)
with pytest.raises(AddressLimitExceededException):
address_service.create_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_data=address_data,
)
@pytest.mark.unit
class TestCustomerAddressServiceUpdate:
"""Tests for update_address method."""
def test_update_address_success(
self, db, address_service, test_vendor, test_customer, test_customer_address
):
"""Test updating an address."""
update_data = CustomerAddressUpdate(
first_name="Updated",
city="New City",
)
address = address_service.update_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=test_customer_address.id,
address_data=update_data,
)
db.commit()
assert address.first_name == "Updated"
assert address.city == "New City"
# Unchanged fields should remain
assert address.last_name == test_customer_address.last_name
def test_update_address_set_default(
self, db, address_service, test_vendor, test_customer, multiple_addresses
):
"""Test setting address as default clears others."""
# Second address is not default
assert multiple_addresses[1].is_default is False
update_data = CustomerAddressUpdate(is_default=True)
address = address_service.update_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=multiple_addresses[1].id,
address_data=update_data,
)
db.commit()
assert address.is_default is True
# Old default should be cleared
db.refresh(multiple_addresses[0])
assert multiple_addresses[0].is_default is False
def test_update_address_not_found(
self, db, address_service, test_vendor, test_customer
):
"""Test error when address not found."""
update_data = CustomerAddressUpdate(first_name="Test")
with pytest.raises(AddressNotFoundException):
address_service.update_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=99999,
address_data=update_data,
)
@pytest.mark.unit
class TestCustomerAddressServiceDelete:
"""Tests for delete_address method."""
def test_delete_address_success(
self, db, address_service, test_vendor, test_customer, test_customer_address
):
"""Test deleting an address."""
address_id = test_customer_address.id
address_service.delete_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=address_id,
)
db.commit()
# Address should be gone
with pytest.raises(AddressNotFoundException):
address_service.get_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=address_id,
)
def test_delete_address_not_found(
self, db, address_service, test_vendor, test_customer
):
"""Test error when deleting non-existent address."""
with pytest.raises(AddressNotFoundException):
address_service.delete_address(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=99999,
)
@pytest.mark.unit
class TestCustomerAddressServiceSetDefault:
"""Tests for set_default method."""
def test_set_default_success(
self, db, address_service, test_vendor, test_customer, multiple_addresses
):
"""Test setting address as default."""
# Second shipping address is not default
assert multiple_addresses[1].is_default is False
assert multiple_addresses[1].address_type == "shipping"
address = address_service.set_default(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=multiple_addresses[1].id,
)
db.commit()
assert address.is_default is True
# Old default should be cleared
db.refresh(multiple_addresses[0])
assert multiple_addresses[0].is_default is False
def test_set_default_not_found(
self, db, address_service, test_vendor, test_customer
):
"""Test error when address not found."""
with pytest.raises(AddressNotFoundException):
address_service.set_default(
db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
address_id=99999,
)