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:
132
app/modules/tenancy/tests/unit/test_tenancy_metrics.py
Normal file
132
app/modules/tenancy/tests/unit/test_tenancy_metrics.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# app/modules/tenancy/tests/unit/test_tenancy_metrics.py
|
||||
"""Unit tests for TenancyMetricsProvider.get_merchant_metrics."""
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.tenancy.models import Merchant, Store, StoreUser, User
|
||||
from app.modules.tenancy.services.tenancy_metrics import TenancyMetricsProvider
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def metrics_merchant(db):
|
||||
"""Create a merchant owner and merchant for metrics tests."""
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
auth = AuthManager()
|
||||
user = User(
|
||||
email=f"metricsowner_{uuid.uuid4().hex[:8]}@test.com",
|
||||
username=f"metricsowner_{uuid.uuid4().hex[:8]}",
|
||||
hashed_password=auth.hash_password("pass123"),
|
||||
role="merchant_owner",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(user)
|
||||
db.flush()
|
||||
|
||||
merchant = Merchant(
|
||||
name="Metrics Test 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 metrics_stores(db, metrics_merchant):
|
||||
"""Create 3 stores (2 active, 1 inactive) for the merchant."""
|
||||
stores = []
|
||||
for i, active in enumerate([True, True, False]):
|
||||
uid = uuid.uuid4().hex[:8].upper()
|
||||
store = Store(
|
||||
merchant_id=metrics_merchant.id,
|
||||
store_code=f"MSTORE_{uid}",
|
||||
subdomain=f"mstore{uid.lower()}",
|
||||
name=f"Metrics Store {i}",
|
||||
is_active=active,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(store)
|
||||
stores.append(store)
|
||||
db.commit()
|
||||
for s in stores:
|
||||
db.refresh(s)
|
||||
return stores
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def metrics_team_members(db, metrics_stores):
|
||||
"""Create team members across merchant stores."""
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
auth = AuthManager()
|
||||
users = []
|
||||
for i in range(3):
|
||||
u = User(
|
||||
email=f"team_{uuid.uuid4().hex[:8]}@test.com",
|
||||
username=f"team_{uuid.uuid4().hex[:8]}",
|
||||
hashed_password=auth.hash_password("pass123"),
|
||||
role="store_user",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(u)
|
||||
users.append(u)
|
||||
db.flush()
|
||||
|
||||
# User 0 on store 0 and store 1 (should be counted once)
|
||||
db.add(StoreUser(store_id=metrics_stores[0].id, user_id=users[0].id, is_active=True))
|
||||
db.add(StoreUser(store_id=metrics_stores[1].id, user_id=users[0].id, is_active=True))
|
||||
# User 1 on store 0 only
|
||||
db.add(StoreUser(store_id=metrics_stores[0].id, user_id=users[1].id, is_active=True))
|
||||
# User 2 on store 0 but inactive
|
||||
db.add(StoreUser(store_id=metrics_stores[0].id, user_id=users[2].id, is_active=False))
|
||||
db.commit()
|
||||
return users
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.tenancy
|
||||
class TestTenancyMetricsProviderMerchant:
|
||||
"""Tests for TenancyMetricsProvider.get_merchant_metrics."""
|
||||
|
||||
def setup_method(self):
|
||||
self.provider = TenancyMetricsProvider()
|
||||
|
||||
def test_total_stores(self, db, metrics_merchant, metrics_stores):
|
||||
"""Returns correct total store count for merchant."""
|
||||
metrics = self.provider.get_merchant_metrics(db, metrics_merchant.id)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
assert by_key["tenancy.total_stores"] == 3
|
||||
|
||||
def test_active_stores(self, db, metrics_merchant, metrics_stores):
|
||||
"""Returns correct active store count (excludes inactive)."""
|
||||
metrics = self.provider.get_merchant_metrics(db, metrics_merchant.id)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
assert by_key["tenancy.active_stores"] == 2
|
||||
|
||||
def test_team_members_distinct(self, db, metrics_merchant, metrics_stores, metrics_team_members):
|
||||
"""Counts distinct active team members across stores."""
|
||||
metrics = self.provider.get_merchant_metrics(db, metrics_merchant.id)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
# 2 active distinct users (user 0 on 2 stores counted once, user 1, user 2 inactive)
|
||||
assert by_key["tenancy.team_members"] == 2
|
||||
|
||||
def test_no_stores(self, db, metrics_merchant):
|
||||
"""Returns zero counts when merchant has no stores."""
|
||||
metrics = self.provider.get_merchant_metrics(db, metrics_merchant.id)
|
||||
by_key = {m.key: m.value for m in metrics}
|
||||
assert by_key["tenancy.total_stores"] == 0
|
||||
assert by_key["tenancy.active_stores"] == 0
|
||||
assert by_key["tenancy.team_members"] == 0
|
||||
|
||||
def test_nonexistent_merchant(self, db):
|
||||
"""Returns zero counts 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["tenancy.total_stores"] == 0
|
||||
Reference in New Issue
Block a user