- Add LoyaltyFeatureProvider with 11 BINARY/MERCHANT features for billing feature gating, wired into loyalty module definition - Fix subscription-tiers admin page showing 0 features by populating feature_codes from tier relationship in all admin tier endpoints - Fix merchants admin page showing 0 stores and N/A owner by adding store_count and owner_email to MerchantResponse and eager-loading owner - Add "no tiers" warning with link in subscription creation modal when platform has no configured tiers - Add missing mobile menu panel to storefront base template so hamburger toggle actually shows navigation links Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
# tests/unit/modules/loyalty/test_loyalty_features.py
|
|
"""
|
|
Unit tests for the loyalty feature provider.
|
|
|
|
Tests cover:
|
|
- Feature category
|
|
- Feature declarations (count, codes, types, scopes)
|
|
- Usage methods return empty lists
|
|
- Protocol compliance
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from app.modules.contracts.features import (
|
|
FeatureProviderProtocol,
|
|
FeatureScope,
|
|
FeatureType,
|
|
)
|
|
from app.modules.loyalty.definition import loyalty_module
|
|
from app.modules.loyalty.services.loyalty_features import (
|
|
LoyaltyFeatureProvider,
|
|
loyalty_feature_provider,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestLoyaltyFeatureProvider:
|
|
"""Tests for LoyaltyFeatureProvider."""
|
|
|
|
def test_feature_category(self):
|
|
"""Feature category should be 'loyalty'."""
|
|
assert loyalty_feature_provider.feature_category == "loyalty"
|
|
|
|
def test_get_feature_declarations_count(self):
|
|
"""Should return exactly 11 feature declarations."""
|
|
declarations = loyalty_feature_provider.get_feature_declarations()
|
|
assert len(declarations) == 11
|
|
|
|
def test_feature_codes_match_module_definition(self):
|
|
"""All declared feature codes should match loyalty_module.features."""
|
|
declarations = loyalty_feature_provider.get_feature_declarations()
|
|
declared_codes = {d.code for d in declarations}
|
|
module_features = set(loyalty_module.features)
|
|
assert declared_codes == module_features
|
|
|
|
def test_all_features_are_binary(self):
|
|
"""All loyalty features should be BINARY type."""
|
|
declarations = loyalty_feature_provider.get_feature_declarations()
|
|
for decl in declarations:
|
|
assert decl.feature_type == FeatureType.BINARY, (
|
|
f"Feature {decl.code} should be BINARY, got {decl.feature_type}"
|
|
)
|
|
|
|
def test_all_features_are_merchant_scoped(self):
|
|
"""All loyalty features should be MERCHANT scoped."""
|
|
declarations = loyalty_feature_provider.get_feature_declarations()
|
|
for decl in declarations:
|
|
assert decl.scope == FeatureScope.MERCHANT, (
|
|
f"Feature {decl.code} should be MERCHANT scoped, got {decl.scope}"
|
|
)
|
|
|
|
def test_all_features_have_name_key(self):
|
|
"""All features should have a non-empty name_key."""
|
|
declarations = loyalty_feature_provider.get_feature_declarations()
|
|
for decl in declarations:
|
|
assert decl.name_key, f"Feature {decl.code} missing name_key"
|
|
|
|
def test_all_features_have_description_key(self):
|
|
"""All features should have a non-empty description_key."""
|
|
declarations = loyalty_feature_provider.get_feature_declarations()
|
|
for decl in declarations:
|
|
assert decl.description_key, f"Feature {decl.code} missing description_key"
|
|
|
|
def test_get_store_usage_returns_empty(self):
|
|
"""get_store_usage should return an empty list."""
|
|
result = loyalty_feature_provider.get_store_usage(db=None, store_id=1)
|
|
assert result == []
|
|
|
|
def test_get_merchant_usage_returns_empty(self):
|
|
"""get_merchant_usage should return an empty list."""
|
|
result = loyalty_feature_provider.get_merchant_usage(
|
|
db=None, merchant_id=1, platform_id=1
|
|
)
|
|
assert result == []
|
|
|
|
def test_satisfies_feature_provider_protocol(self):
|
|
"""Provider should satisfy FeatureProviderProtocol."""
|
|
assert isinstance(loyalty_feature_provider, FeatureProviderProtocol)
|
|
|
|
def test_singleton_is_correct_type(self):
|
|
"""Singleton instance should be a LoyaltyFeatureProvider."""
|
|
assert isinstance(loyalty_feature_provider, LoyaltyFeatureProvider)
|