- Fix IPv6 host parsing with _strip_port() utility - Remove dangerous StorePlatform→Store.subdomain silent fallback - Close storefront gate bypass when frontend_type is None - Add custom subdomain management UI and API for stores - Add domain health diagnostic tool - Convert db.add() in loops to db.add_all() (24 PERF-006 fixes) - Add tests for all new functionality (18 subdomain service tests) - Add .github templates for validator compliance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
# 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):
|
|
users.append(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_all(users)
|
|
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
|