Add architecture rule that detects when API routes import database models directly, enforcing Routes → Services → Models pattern. Changes: - Add API-007 rule to .architecture-rules/api.yaml - Add _check_no_model_imports() validation to validator script - Update customer imports to use canonical module location - Add storefront module restructure implementation plan The validator now detects 81 violations across 67 API files where database models are imported directly instead of going through services. This is Phase 1 of the storefront restructure plan. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
281 lines
9.4 KiB
Python
281 lines
9.4 KiB
Python
# tests/unit/services/test_admin_customer_service.py
|
|
"""
|
|
Unit tests for AdminCustomerService.
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from app.exceptions.customer import CustomerNotFoundException
|
|
from app.services.admin_customer_service import AdminCustomerService
|
|
from app.modules.customers.models.customer import Customer
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_customer_service():
|
|
"""Create AdminCustomerService instance."""
|
|
return AdminCustomerService()
|
|
|
|
|
|
@pytest.fixture
|
|
def customer_with_orders(db, test_vendor, test_customer):
|
|
"""Create a customer with order data."""
|
|
test_customer.total_orders = 5
|
|
test_customer.total_spent = Decimal("250.00")
|
|
db.commit()
|
|
db.refresh(test_customer)
|
|
return test_customer
|
|
|
|
|
|
@pytest.fixture
|
|
def multiple_customers(db, test_vendor):
|
|
"""Create multiple customers for testing."""
|
|
customers = []
|
|
for i in range(5):
|
|
customer = Customer(
|
|
vendor_id=test_vendor.id,
|
|
email=f"customer{i}@example.com",
|
|
hashed_password="hashed_password_placeholder",
|
|
first_name=f"First{i}",
|
|
last_name=f"Last{i}",
|
|
customer_number=f"CUST-00{i}",
|
|
is_active=(i % 2 == 0), # Alternate active/inactive
|
|
total_orders=i,
|
|
total_spent=Decimal(str(i * 100)),
|
|
)
|
|
db.add(customer)
|
|
customers.append(customer)
|
|
|
|
db.commit()
|
|
for c in customers:
|
|
db.refresh(c)
|
|
|
|
return customers
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestAdminCustomerServiceList:
|
|
"""Tests for list_customers method."""
|
|
|
|
def test_list_customers_empty(self, db, admin_customer_service, test_vendor):
|
|
"""Test listing customers when none exist."""
|
|
customers, total = admin_customer_service.list_customers(db)
|
|
|
|
assert customers == []
|
|
assert total == 0
|
|
|
|
def test_list_customers_basic(self, db, admin_customer_service, test_customer):
|
|
"""Test basic customer listing."""
|
|
customers, total = admin_customer_service.list_customers(db)
|
|
|
|
assert total == 1
|
|
assert len(customers) == 1
|
|
assert customers[0]["id"] == test_customer.id
|
|
assert customers[0]["email"] == test_customer.email
|
|
|
|
def test_list_customers_with_vendor_info(
|
|
self, db, admin_customer_service, test_customer, test_vendor
|
|
):
|
|
"""Test that vendor info is included."""
|
|
customers, total = admin_customer_service.list_customers(db)
|
|
|
|
assert customers[0]["vendor_name"] == test_vendor.name
|
|
assert customers[0]["vendor_code"] == test_vendor.vendor_code
|
|
|
|
def test_list_customers_filter_by_vendor(
|
|
self, db, admin_customer_service, multiple_customers, test_vendor
|
|
):
|
|
"""Test filtering by vendor ID."""
|
|
customers, total = admin_customer_service.list_customers(
|
|
db, vendor_id=test_vendor.id
|
|
)
|
|
|
|
assert total == 5
|
|
for customer in customers:
|
|
assert customer["vendor_id"] == test_vendor.id
|
|
|
|
def test_list_customers_filter_by_active_status(
|
|
self, db, admin_customer_service, multiple_customers
|
|
):
|
|
"""Test filtering by active status."""
|
|
# Get active customers (0, 2, 4 = 3 customers)
|
|
customers, total = admin_customer_service.list_customers(db, is_active=True)
|
|
assert total == 3
|
|
|
|
# Get inactive customers (1, 3 = 2 customers)
|
|
customers, total = admin_customer_service.list_customers(db, is_active=False)
|
|
assert total == 2
|
|
|
|
def test_list_customers_search_by_email(
|
|
self, db, admin_customer_service, multiple_customers
|
|
):
|
|
"""Test searching by email."""
|
|
customers, total = admin_customer_service.list_customers(
|
|
db, search="customer2@"
|
|
)
|
|
|
|
assert total == 1
|
|
assert customers[0]["email"] == "customer2@example.com"
|
|
|
|
def test_list_customers_search_by_name(
|
|
self, db, admin_customer_service, multiple_customers
|
|
):
|
|
"""Test searching by name."""
|
|
customers, total = admin_customer_service.list_customers(db, search="First3")
|
|
|
|
assert total == 1
|
|
assert customers[0]["first_name"] == "First3"
|
|
|
|
def test_list_customers_search_by_customer_number(
|
|
self, db, admin_customer_service, multiple_customers
|
|
):
|
|
"""Test searching by customer number."""
|
|
customers, total = admin_customer_service.list_customers(db, search="CUST-001")
|
|
|
|
assert total == 1
|
|
assert customers[0]["customer_number"] == "CUST-001"
|
|
|
|
def test_list_customers_pagination(
|
|
self, db, admin_customer_service, multiple_customers
|
|
):
|
|
"""Test pagination."""
|
|
# Get first page
|
|
customers, total = admin_customer_service.list_customers(db, skip=0, limit=2)
|
|
assert len(customers) == 2
|
|
assert total == 5
|
|
|
|
# Get second page
|
|
customers, total = admin_customer_service.list_customers(db, skip=2, limit=2)
|
|
assert len(customers) == 2
|
|
|
|
# Get last page
|
|
customers, total = admin_customer_service.list_customers(db, skip=4, limit=2)
|
|
assert len(customers) == 1
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestAdminCustomerServiceStats:
|
|
"""Tests for get_customer_stats method."""
|
|
|
|
def test_get_customer_stats_empty(self, db, admin_customer_service, test_vendor):
|
|
"""Test stats when no customers exist."""
|
|
stats = admin_customer_service.get_customer_stats(db)
|
|
|
|
assert stats["total"] == 0
|
|
assert stats["active"] == 0
|
|
assert stats["inactive"] == 0
|
|
assert stats["with_orders"] == 0
|
|
assert stats["total_spent"] == 0
|
|
assert stats["total_orders"] == 0
|
|
assert stats["avg_order_value"] == 0
|
|
|
|
def test_get_customer_stats_with_data(
|
|
self, db, admin_customer_service, multiple_customers
|
|
):
|
|
"""Test stats with customer data."""
|
|
stats = admin_customer_service.get_customer_stats(db)
|
|
|
|
assert stats["total"] == 5
|
|
assert stats["active"] == 3 # 0, 2, 4
|
|
assert stats["inactive"] == 2 # 1, 3
|
|
# with_orders = customers with total_orders > 0 (1, 2, 3, 4 = 4 customers)
|
|
assert stats["with_orders"] == 4
|
|
# total_spent = 0 + 100 + 200 + 300 + 400 = 1000
|
|
assert stats["total_spent"] == 1000.0
|
|
# total_orders = 0 + 1 + 2 + 3 + 4 = 10
|
|
assert stats["total_orders"] == 10
|
|
|
|
def test_get_customer_stats_by_vendor(
|
|
self, db, admin_customer_service, test_customer, test_vendor
|
|
):
|
|
"""Test stats filtered by vendor."""
|
|
stats = admin_customer_service.get_customer_stats(db, vendor_id=test_vendor.id)
|
|
|
|
assert stats["total"] == 1
|
|
|
|
def test_get_customer_stats_avg_order_value(
|
|
self, db, admin_customer_service, customer_with_orders
|
|
):
|
|
"""Test average order value calculation."""
|
|
stats = admin_customer_service.get_customer_stats(db)
|
|
|
|
# total_spent = 250, total_orders = 5
|
|
# avg = 250 / 5 = 50
|
|
assert stats["avg_order_value"] == 50.0
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestAdminCustomerServiceGetCustomer:
|
|
"""Tests for get_customer method."""
|
|
|
|
def test_get_customer_success(self, db, admin_customer_service, test_customer):
|
|
"""Test getting customer by ID."""
|
|
customer = admin_customer_service.get_customer(db, test_customer.id)
|
|
|
|
assert customer["id"] == test_customer.id
|
|
assert customer["email"] == test_customer.email
|
|
assert customer["first_name"] == test_customer.first_name
|
|
assert customer["last_name"] == test_customer.last_name
|
|
|
|
def test_get_customer_with_vendor_info(
|
|
self, db, admin_customer_service, test_customer, test_vendor
|
|
):
|
|
"""Test vendor info in customer detail."""
|
|
customer = admin_customer_service.get_customer(db, test_customer.id)
|
|
|
|
assert customer["vendor_name"] == test_vendor.name
|
|
assert customer["vendor_code"] == test_vendor.vendor_code
|
|
|
|
def test_get_customer_not_found(self, db, admin_customer_service):
|
|
"""Test error when customer not found."""
|
|
with pytest.raises(CustomerNotFoundException):
|
|
admin_customer_service.get_customer(db, 99999)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestAdminCustomerServiceToggleStatus:
|
|
"""Tests for toggle_customer_status method."""
|
|
|
|
def test_toggle_status_activate(
|
|
self, db, admin_customer_service, test_customer, test_admin
|
|
):
|
|
"""Test activating an inactive customer."""
|
|
# Make customer inactive first
|
|
test_customer.is_active = False
|
|
db.commit()
|
|
|
|
result = admin_customer_service.toggle_customer_status(
|
|
db, test_customer.id, test_admin.email
|
|
)
|
|
db.commit()
|
|
|
|
assert result["id"] == test_customer.id
|
|
assert result["is_active"] is True
|
|
assert "activated" in result["message"]
|
|
|
|
def test_toggle_status_deactivate(
|
|
self, db, admin_customer_service, test_customer, test_admin
|
|
):
|
|
"""Test deactivating an active customer."""
|
|
test_customer.is_active = True
|
|
db.commit()
|
|
|
|
result = admin_customer_service.toggle_customer_status(
|
|
db, test_customer.id, test_admin.email
|
|
)
|
|
db.commit()
|
|
|
|
assert result["id"] == test_customer.id
|
|
assert result["is_active"] is False
|
|
assert "deactivated" in result["message"]
|
|
|
|
def test_toggle_status_not_found(
|
|
self, db, admin_customer_service, test_admin
|
|
):
|
|
"""Test error when customer not found."""
|
|
with pytest.raises(CustomerNotFoundException):
|
|
admin_customer_service.toggle_customer_status(
|
|
db, 99999, test_admin.email
|
|
)
|