- 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>
149 lines
4.4 KiB
Python
149 lines
4.4 KiB
Python
# app/modules/billing/tests/unit/test_billing_metrics.py
|
|
"""Unit tests for BillingMetricsProvider.get_merchant_metrics."""
|
|
|
|
import uuid
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
import pytest
|
|
|
|
from app.modules.billing.models import (
|
|
MerchantSubscription,
|
|
SubscriptionStatus,
|
|
SubscriptionTier,
|
|
)
|
|
from app.modules.billing.services.billing_metrics import BillingMetricsProvider
|
|
from app.modules.tenancy.models import Merchant, Platform, User
|
|
|
|
|
|
@pytest.fixture
|
|
def billing_platform(db):
|
|
"""Create a platform for billing metrics tests."""
|
|
platform = Platform(
|
|
code=f"bm_{uuid.uuid4().hex[:8]}",
|
|
name="Billing Metrics Platform",
|
|
is_active=True,
|
|
)
|
|
db.add(platform)
|
|
db.commit()
|
|
db.refresh(platform)
|
|
return platform
|
|
|
|
|
|
@pytest.fixture
|
|
def billing_merchant(db):
|
|
"""Create a merchant for billing metrics tests."""
|
|
from middleware.auth import AuthManager
|
|
|
|
auth = AuthManager()
|
|
user = User(
|
|
email=f"billowner_{uuid.uuid4().hex[:8]}@test.com",
|
|
username=f"billowner_{uuid.uuid4().hex[:8]}",
|
|
hashed_password=auth.hash_password("pass123"),
|
|
role="merchant_owner",
|
|
is_active=True,
|
|
)
|
|
db.add(user)
|
|
db.flush()
|
|
|
|
merchant = Merchant(
|
|
name="Billing 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 billing_tier(db, billing_platform):
|
|
"""Create a subscription tier."""
|
|
tier = SubscriptionTier(
|
|
code="professional",
|
|
name="Professional",
|
|
description="Pro tier",
|
|
price_monthly_cents=2900,
|
|
price_annual_cents=29000,
|
|
display_order=1,
|
|
is_active=True,
|
|
is_public=True,
|
|
platform_id=billing_platform.id,
|
|
)
|
|
db.add(tier)
|
|
db.commit()
|
|
db.refresh(tier)
|
|
return tier
|
|
|
|
|
|
@pytest.fixture
|
|
def billing_extra_platforms(db):
|
|
"""Create additional platforms for multiple subscriptions (unique constraint: merchant+platform)."""
|
|
platforms = []
|
|
for i in range(2):
|
|
platforms.append(Platform(
|
|
code=f"bm_extra_{uuid.uuid4().hex[:8]}",
|
|
name=f"Extra Platform {i}",
|
|
is_active=True,
|
|
))
|
|
db.add_all(platforms)
|
|
db.commit()
|
|
for p in platforms:
|
|
db.refresh(p)
|
|
return platforms
|
|
|
|
|
|
@pytest.fixture
|
|
def billing_subscriptions(db, billing_merchant, billing_platform, billing_tier, billing_extra_platforms):
|
|
"""Create subscriptions: 1 active, 1 trial, 1 cancelled (each on a different platform)."""
|
|
platforms = [billing_platform, billing_extra_platforms[0], billing_extra_platforms[1]]
|
|
subs = []
|
|
for status, platform in zip(
|
|
[SubscriptionStatus.ACTIVE, SubscriptionStatus.TRIAL, SubscriptionStatus.CANCELLED],
|
|
platforms, strict=False,
|
|
):
|
|
sub = MerchantSubscription(
|
|
merchant_id=billing_merchant.id,
|
|
platform_id=platform.id,
|
|
tier_id=billing_tier.id,
|
|
status=status.value,
|
|
is_annual=False,
|
|
period_start=datetime.now(UTC),
|
|
period_end=datetime.now(UTC) + timedelta(days=30),
|
|
)
|
|
db.add(sub)
|
|
subs.append(sub)
|
|
db.commit()
|
|
for s in subs:
|
|
db.refresh(s)
|
|
return subs
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.billing
|
|
class TestBillingMetricsProviderMerchant:
|
|
"""Tests for BillingMetricsProvider.get_merchant_metrics."""
|
|
|
|
def setup_method(self):
|
|
self.provider = BillingMetricsProvider()
|
|
|
|
def test_active_subscriptions_count(self, db, billing_merchant, billing_subscriptions):
|
|
"""Counts active + trial subscriptions, excludes cancelled."""
|
|
metrics = self.provider.get_merchant_metrics(db, billing_merchant.id)
|
|
by_key = {m.key: m.value for m in metrics}
|
|
assert by_key["billing.active_subscriptions"] == 2
|
|
|
|
def test_no_subscriptions(self, db, billing_merchant):
|
|
"""Returns zero when merchant has no subscriptions."""
|
|
metrics = self.provider.get_merchant_metrics(db, billing_merchant.id)
|
|
by_key = {m.key: m.value for m in metrics}
|
|
assert by_key["billing.active_subscriptions"] == 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["billing.active_subscriptions"] == 0
|