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:
106
app/modules/core/tests/unit/test_stats_aggregator.py
Normal file
106
app/modules/core/tests/unit/test_stats_aggregator.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# app/modules/core/tests/unit/test_stats_aggregator.py
|
||||
"""Unit tests for StatsAggregatorService merchant methods."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.contracts.metrics import MetricValue
|
||||
from app.modules.core.services.stats_aggregator import StatsAggregatorService
|
||||
|
||||
|
||||
def _make_metric(key: str, value: int, category: str = "test") -> MetricValue:
|
||||
return MetricValue(key=key, value=value, label=key, category=category)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.core
|
||||
class TestStatsAggregatorMerchant:
|
||||
"""Tests for StatsAggregatorService merchant aggregation methods."""
|
||||
|
||||
def setup_method(self):
|
||||
self.service = StatsAggregatorService()
|
||||
|
||||
def test_get_merchant_dashboard_stats_aggregates_providers(self, db):
|
||||
"""Aggregates metrics from providers that implement get_merchant_metrics."""
|
||||
provider_a = MagicMock()
|
||||
provider_a.metrics_category = "billing"
|
||||
provider_a.get_merchant_metrics.return_value = [
|
||||
_make_metric("billing.active_subscriptions", 2, "billing"),
|
||||
]
|
||||
|
||||
provider_b = MagicMock()
|
||||
provider_b.metrics_category = "tenancy"
|
||||
provider_b.get_merchant_metrics.return_value = [
|
||||
_make_metric("tenancy.total_stores", 3, "tenancy"),
|
||||
]
|
||||
|
||||
module_a = MagicMock()
|
||||
module_a.code = "billing"
|
||||
module_b = MagicMock()
|
||||
module_b.code = "tenancy"
|
||||
|
||||
with patch.object(
|
||||
self.service, "_get_enabled_providers", return_value=[(module_a, provider_a), (module_b, provider_b)]
|
||||
):
|
||||
result = self.service.get_merchant_dashboard_stats(db, merchant_id=1, platform_id=1)
|
||||
|
||||
assert "billing" in result
|
||||
assert "tenancy" in result
|
||||
assert result["billing"][0].value == 2
|
||||
assert result["tenancy"][0].value == 3
|
||||
|
||||
def test_get_merchant_dashboard_stats_skips_providers_without_method(self, db):
|
||||
"""Skips providers that don't have get_merchant_metrics."""
|
||||
provider = MagicMock(spec=[]) # No attributes at all
|
||||
provider.metrics_category = "legacy"
|
||||
module = MagicMock()
|
||||
module.code = "legacy"
|
||||
|
||||
with patch.object(
|
||||
self.service, "_get_enabled_providers", return_value=[(module, provider)]
|
||||
):
|
||||
result = self.service.get_merchant_dashboard_stats(db, merchant_id=1, platform_id=1)
|
||||
|
||||
assert result == {}
|
||||
|
||||
def test_get_merchant_dashboard_stats_handles_provider_error(self, db):
|
||||
"""Gracefully handles a provider raising an exception."""
|
||||
provider = MagicMock()
|
||||
provider.metrics_category = "broken"
|
||||
provider.get_merchant_metrics.side_effect = RuntimeError("DB error")
|
||||
module = MagicMock()
|
||||
module.code = "broken"
|
||||
|
||||
with patch.object(
|
||||
self.service, "_get_enabled_providers", return_value=[(module, provider)]
|
||||
):
|
||||
result = self.service.get_merchant_dashboard_stats(db, merchant_id=1, platform_id=1)
|
||||
|
||||
assert result == {}
|
||||
|
||||
def test_get_merchant_stats_flat(self, db):
|
||||
"""Flattens categorized metrics into a single dict."""
|
||||
provider = MagicMock()
|
||||
provider.metrics_category = "tenancy"
|
||||
provider.get_merchant_metrics.return_value = [
|
||||
_make_metric("tenancy.total_stores", 5, "tenancy"),
|
||||
_make_metric("tenancy.team_members", 12, "tenancy"),
|
||||
]
|
||||
module = MagicMock()
|
||||
module.code = "tenancy"
|
||||
|
||||
with patch.object(
|
||||
self.service, "_get_enabled_providers", return_value=[(module, provider)]
|
||||
):
|
||||
flat = self.service.get_merchant_stats_flat(db, merchant_id=1, platform_id=1)
|
||||
|
||||
assert flat["tenancy.total_stores"] == 5
|
||||
assert flat["tenancy.team_members"] == 12
|
||||
|
||||
def test_get_merchant_stats_flat_empty(self, db):
|
||||
"""Returns empty dict when no providers have merchant metrics."""
|
||||
with patch.object(self.service, "_get_enabled_providers", return_value=[]):
|
||||
flat = self.service.get_merchant_stats_flat(db, merchant_id=1, platform_id=1)
|
||||
|
||||
assert flat == {}
|
||||
Reference in New Issue
Block a user