Files
orion/tests/unit/services/test_order_metrics_customer.py
Samir Boulahtit 39dff4ab7d refactor: fix architecture violations with provider patterns and dependency inversion
Major changes:
- Add AuditProvider protocol for cross-module audit logging
- Move customer order operations to orders module (dependency inversion)
- Add customer order metrics via MetricsProvider pattern
- Fix missing db parameter in get_admin_context() calls
- Move ProductMedia relationship to catalog module (proper ownership)
- Add marketplace breakdown stats to marketplace_widgets

New files:
- contracts/audit.py - AuditProviderProtocol
- core/services/audit_aggregator.py - Aggregates audit providers
- monitoring/services/audit_provider.py - Monitoring audit implementation
- orders/services/customer_order_service.py - Customer order operations
- orders/routes/api/vendor_customer_orders.py - Customer order endpoints
- catalog/services/product_media_service.py - Product media service
- Architecture documentation for patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 21:32:32 +01:00

184 lines
5.9 KiB
Python

# tests/unit/services/test_order_metrics_customer.py
"""
Unit tests for OrderMetricsProvider customer metrics.
Tests the get_customer_order_metrics method which provides
customer-level order statistics using the MetricsProvider pattern.
"""
from datetime import UTC, datetime
import pytest
from app.modules.orders.models import Order
from app.modules.orders.services.order_metrics import OrderMetricsProvider
@pytest.fixture
def order_metrics_provider():
"""Create OrderMetricsProvider instance."""
return OrderMetricsProvider()
@pytest.fixture
def customer_with_orders(db, test_vendor, test_customer):
"""Create a customer with multiple orders for metrics testing."""
orders = []
first_name = test_customer.first_name or "Test"
last_name = test_customer.last_name or "Customer"
for i in range(3):
order = Order(
vendor_id=test_vendor.id,
customer_id=test_customer.id,
order_number=f"METRICS-{i:04d}",
status="completed",
channel="direct",
order_date=datetime.now(UTC),
subtotal_cents=1000 * (i + 1), # 1000, 2000, 3000
total_amount_cents=1000 * (i + 1),
currency="EUR",
# Customer info
customer_email=test_customer.email,
customer_first_name=first_name,
customer_last_name=last_name,
# Shipping address
ship_first_name=first_name,
ship_last_name=last_name,
ship_address_line_1="123 Test St",
ship_city="Luxembourg",
ship_postal_code="L-1234",
ship_country_iso="LU",
# Billing address
bill_first_name=first_name,
bill_last_name=last_name,
bill_address_line_1="123 Test St",
bill_city="Luxembourg",
bill_postal_code="L-1234",
bill_country_iso="LU",
)
db.add(order)
orders.append(order)
db.commit()
for order in orders:
db.refresh(order)
return test_customer, orders
@pytest.mark.unit
class TestOrderMetricsProviderCustomerMetrics:
"""Tests for get_customer_order_metrics method."""
def test_get_customer_metrics_no_orders(
self, db, order_metrics_provider, test_vendor, test_customer
):
"""Test metrics when customer has no orders."""
metrics = order_metrics_provider.get_customer_order_metrics(
db=db,
vendor_id=test_vendor.id,
customer_id=test_customer.id,
)
# Should return metrics even with no orders
assert len(metrics) > 0
# Find total_orders metric
total_orders_metric = next(
(m for m in metrics if m.key == "customer.total_orders"), None
)
assert total_orders_metric is not None
assert total_orders_metric.value == 0
def test_get_customer_metrics_with_orders(
self, db, order_metrics_provider, test_vendor, customer_with_orders
):
"""Test metrics when customer has orders."""
customer, orders = customer_with_orders
metrics = order_metrics_provider.get_customer_order_metrics(
db=db,
vendor_id=test_vendor.id,
customer_id=customer.id,
)
# Check total orders
total_orders = next(
(m for m in metrics if m.key == "customer.total_orders"), None
)
assert total_orders is not None
assert total_orders.value == 3
# Check total spent (1000 + 2000 + 3000 = 6000 cents = 60.00)
total_spent = next(
(m for m in metrics if m.key == "customer.total_spent"), None
)
assert total_spent is not None
assert total_spent.value == 60.0
# Check average order value (6000 / 3 = 2000 cents = 20.00)
avg_value = next(
(m for m in metrics if m.key == "customer.avg_order_value"), None
)
assert avg_value is not None
assert avg_value.value == 20.0
def test_get_customer_metrics_has_required_fields(
self, db, order_metrics_provider, test_vendor, customer_with_orders
):
"""Test that all required metric fields are present."""
customer, _ = customer_with_orders
metrics = order_metrics_provider.get_customer_order_metrics(
db=db,
vendor_id=test_vendor.id,
customer_id=customer.id,
)
expected_keys = [
"customer.total_orders",
"customer.total_spent",
"customer.avg_order_value",
"customer.last_order_date",
"customer.first_order_date",
]
metric_keys = [m.key for m in metrics]
for key in expected_keys:
assert key in metric_keys, f"Missing metric: {key}"
def test_get_customer_metrics_has_labels_and_icons(
self, db, order_metrics_provider, test_vendor, customer_with_orders
):
"""Test that metrics have display metadata."""
customer, _ = customer_with_orders
metrics = order_metrics_provider.get_customer_order_metrics(
db=db,
vendor_id=test_vendor.id,
customer_id=customer.id,
)
for metric in metrics:
assert metric.label, f"Metric {metric.key} missing label"
assert metric.category == "customer_orders"
def test_get_customer_metrics_wrong_vendor(
self, db, order_metrics_provider, customer_with_orders
):
"""Test metrics with wrong vendor returns zero values."""
customer, _ = customer_with_orders
metrics = order_metrics_provider.get_customer_order_metrics(
db=db,
vendor_id=99999, # Non-existent vendor
customer_id=customer.id,
)
total_orders = next(
(m for m in metrics if m.key == "customer.total_orders"), None
)
assert total_orders is not None
assert total_orders.value == 0