test: add tests for merchant dashboard metrics and fix invoice template location
Move invoice PDF template from app/templates/invoices/ to app/modules/orders/templates/invoices/ where InvoicePDFService expects it. Expand invoice PDF tests to validate template path and existence. Add unit tests for get_merchant_metrics() in tenancy, billing, and customer metrics providers. Add unit tests for StatsAggregatorService merchant methods. Add integration tests for the merchant dashboard stats endpoint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
130
app/modules/customers/tests/unit/test_customer_metrics.py
Normal file
130
app/modules/customers/tests/unit/test_customer_metrics.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# app/modules/customers/tests/unit/test_customer_metrics.py
|
||||
"""Unit tests for CustomerMetricsProvider.get_merchant_metrics."""
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.customers.models.customer import Customer
|
||||
from app.modules.customers.services.customer_metrics import CustomerMetricsProvider
|
||||
from app.modules.tenancy.models import Merchant, Store, User
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cust_merchant(db):
|
||||
"""Create a merchant for customer metrics tests."""
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
auth = AuthManager()
|
||||
user = User(
|
||||
email=f"custowner_{uuid.uuid4().hex[:8]}@test.com",
|
||||
username=f"custowner_{uuid.uuid4().hex[:8]}",
|
||||
hashed_password=auth.hash_password("pass123"),
|
||||
role="merchant_owner",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(user)
|
||||
db.flush()
|
||||
|
||||
merchant = Merchant(
|
||||
name="Customer Metrics Merchant",
|
||||
owner_user_id=user.id,
|
||||
contact_email=user.email,
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(merchant)
|
||||
db.commit()
|
||||
db.refresh(merchant)
|
||||
return merchant
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cust_stores(db, cust_merchant):
|
||||
"""Create 2 stores for the merchant."""
|
||||
stores = []
|
||||
for i in range(2):
|
||||
uid = uuid.uuid4().hex[:8].upper()
|
||||
store = Store(
|
||||
merchant_id=cust_merchant.id,
|
||||
store_code=f"CSTORE_{uid}",
|
||||
subdomain=f"cstore{uid.lower()}",
|
||||
name=f"Cust Store {i}",
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(store)
|
||||
stores.append(store)
|
||||
db.commit()
|
||||
for s in stores:
|
||||
db.refresh(s)
|
||||
return stores
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cust_customers(db, cust_stores):
|
||||
"""Create customers across the merchant's stores."""
|
||||
customers = []
|
||||
# 3 customers in store 0
|
||||
for i in range(3):
|
||||
uid = uuid.uuid4().hex[:8]
|
||||
c = Customer(
|
||||
store_id=cust_stores[0].id,
|
||||
email=f"cust_{uid}@test.com",
|
||||
hashed_password="hashed", # noqa: SEC001
|
||||
first_name=f"First{i}",
|
||||
last_name=f"Last{i}",
|
||||
customer_number=f"C{uid}",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(c)
|
||||
customers.append(c)
|
||||
# 2 customers in store 1
|
||||
for i in range(2):
|
||||
uid = uuid.uuid4().hex[:8]
|
||||
c = Customer(
|
||||
store_id=cust_stores[1].id,
|
||||
email=f"cust_{uid}@test.com",
|
||||
hashed_password="hashed", # noqa: SEC001
|
||||
first_name=f"First{i}",
|
||||
last_name=f"Last{i}",
|
||||
customer_number=f"C{uid}",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(c)
|
||||
customers.append(c)
|
||||
db.commit()
|
||||
return customers
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.customers
|
||||
class TestCustomerMetricsProviderMerchant:
|
||||
"""Tests for CustomerMetricsProvider.get_merchant_metrics."""
|
||||
|
||||
def setup_method(self):
|
||||
self.provider = CustomerMetricsProvider()
|
||||
|
||||
def test_total_customers_across_stores(self, db, cust_merchant, cust_stores, cust_customers):
|
||||
"""Aggregates customers across all merchant stores."""
|
||||
metrics = self.provider.get_merchant_metrics(db, cust_merchant.id)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
assert by_key["customers.total"] == 5
|
||||
|
||||
def test_no_customers(self, db, cust_merchant, cust_stores):
|
||||
"""Returns zero when stores have no customers."""
|
||||
metrics = self.provider.get_merchant_metrics(db, cust_merchant.id)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
assert by_key["customers.total"] == 0
|
||||
|
||||
def test_no_stores(self, db, cust_merchant):
|
||||
"""Returns zero when merchant has no stores."""
|
||||
metrics = self.provider.get_merchant_metrics(db, cust_merchant.id)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
assert by_key["customers.total"] == 0
|
||||
|
||||
def test_nonexistent_merchant(self, db):
|
||||
"""Returns zero for a non-existent merchant ID."""
|
||||
metrics = self.provider.get_merchant_metrics(db, 999999)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
assert by_key["customers.total"] == 0
|
||||
Reference in New Issue
Block a user