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:
2026-02-22 21:46:34 +01:00
parent b77952bf89
commit d7a383f3d7
8 changed files with 786 additions and 1 deletions

View 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 == {}