# tests/unit/services/test_usage_service.py """Unit tests for UsageService.""" import pytest from app.services.usage_service import UsageService, usage_service from models.database.product import Product from models.database.subscription import SubscriptionTier, VendorSubscription from models.database.vendor import VendorUser @pytest.mark.unit @pytest.mark.usage class TestUsageServiceGetUsage: """Test suite for get_vendor_usage operation.""" def setup_method(self): """Initialize service instance before each test.""" self.service = UsageService() def test_get_vendor_usage_basic(self, db, test_vendor_with_subscription): """Test getting basic usage data.""" vendor_id = test_vendor_with_subscription.id usage = self.service.get_vendor_usage(db, vendor_id) assert usage.tier.code == "essential" assert usage.tier.name == "Essential" assert len(usage.usage) == 3 def test_get_vendor_usage_metrics(self, db, test_vendor_with_subscription): """Test usage metrics are calculated correctly.""" vendor_id = test_vendor_with_subscription.id usage = self.service.get_vendor_usage(db, vendor_id) orders_metric = next((m for m in usage.usage if m.name == "orders"), None) assert orders_metric is not None assert orders_metric.current == 10 assert orders_metric.limit == 100 assert orders_metric.percentage == 10.0 assert orders_metric.is_unlimited is False def test_get_vendor_usage_at_limit(self, db, test_vendor_at_limit): """Test usage shows at limit correctly.""" vendor_id = test_vendor_at_limit.id usage = self.service.get_vendor_usage(db, vendor_id) orders_metric = next((m for m in usage.usage if m.name == "orders"), None) assert orders_metric.is_at_limit is True assert usage.has_limits_reached is True def test_get_vendor_usage_approaching_limit(self, db, test_vendor_approaching_limit): """Test usage shows approaching limit correctly.""" vendor_id = test_vendor_approaching_limit.id usage = self.service.get_vendor_usage(db, vendor_id) orders_metric = next((m for m in usage.usage if m.name == "orders"), None) assert orders_metric.is_approaching_limit is True assert usage.has_limits_approaching is True def test_get_vendor_usage_upgrade_available( self, db, test_vendor_with_subscription, test_professional_tier ): """Test upgrade info when not on highest tier.""" vendor_id = test_vendor_with_subscription.id usage = self.service.get_vendor_usage(db, vendor_id) assert usage.upgrade_available is True assert usage.upgrade_tier is not None assert usage.upgrade_tier.code == "professional" def test_get_vendor_usage_highest_tier(self, db, test_vendor_on_professional): """Test no upgrade when on highest tier.""" vendor_id = test_vendor_on_professional.id usage = self.service.get_vendor_usage(db, vendor_id) assert usage.tier.is_highest_tier is True assert usage.upgrade_available is False assert usage.upgrade_tier is None @pytest.mark.unit @pytest.mark.usage class TestUsageServiceCheckLimit: """Test suite for check_limit operation.""" def setup_method(self): """Initialize service instance before each test.""" self.service = UsageService() def test_check_orders_limit_can_proceed(self, db, test_vendor_with_subscription): """Test checking orders limit when under limit.""" vendor_id = test_vendor_with_subscription.id result = self.service.check_limit(db, vendor_id, "orders") assert result.can_proceed is True assert result.current == 10 assert result.limit == 100 def test_check_products_limit(self, db, test_vendor_with_products): """Test checking products limit.""" vendor_id = test_vendor_with_products.id result = self.service.check_limit(db, vendor_id, "products") assert result.can_proceed is True assert result.current == 5 assert result.limit == 500 def test_check_team_members_limit(self, db, test_vendor_with_team): """Test checking team members limit when at limit.""" vendor_id = test_vendor_with_team.id result = self.service.check_limit(db, vendor_id, "team_members") # At limit (2/2) - can_proceed should be False assert result.can_proceed is False assert result.current == 2 assert result.limit == 2 assert result.percentage == 100.0 def test_check_unknown_limit_type(self, db, test_vendor_with_subscription): """Test checking unknown limit type.""" vendor_id = test_vendor_with_subscription.id result = self.service.check_limit(db, vendor_id, "unknown") assert result.can_proceed is True assert "Unknown limit type" in result.message def test_check_limit_upgrade_info_when_blocked(self, db, test_vendor_at_limit): """Test upgrade info is provided when at limit.""" vendor_id = test_vendor_at_limit.id result = self.service.check_limit(db, vendor_id, "orders") assert result.can_proceed is False assert result.upgrade_tier_code == "professional" assert result.upgrade_tier_name == "Professional" # ==================== Fixtures ==================== @pytest.fixture def test_essential_tier(db): """Create essential subscription tier.""" tier = SubscriptionTier( code="essential", name="Essential", description="Essential plan", price_monthly_cents=4900, price_annual_cents=49000, orders_per_month=100, products_limit=500, team_members=2, features=["basic_reports"], is_active=True, display_order=1, ) db.add(tier) db.commit() db.refresh(tier) return tier @pytest.fixture def test_professional_tier(db, test_essential_tier): """Create professional subscription tier.""" tier = SubscriptionTier( code="professional", name="Professional", description="Professional plan", price_monthly_cents=9900, price_annual_cents=99000, orders_per_month=500, products_limit=2000, team_members=10, features=["basic_reports", "api_access", "analytics_dashboard"], is_active=True, display_order=2, ) db.add(tier) db.commit() db.refresh(tier) return tier @pytest.fixture def test_vendor_with_subscription(db, test_vendor, test_essential_tier): """Create vendor with active subscription.""" from datetime import datetime, timezone now = datetime.now(timezone.utc) subscription = VendorSubscription( vendor_id=test_vendor.id, tier="essential", tier_id=test_essential_tier.id, status="active", period_start=now, period_end=now.replace(month=now.month + 1 if now.month < 12 else 1), orders_this_period=10, ) db.add(subscription) db.commit() db.refresh(test_vendor) return test_vendor @pytest.fixture def test_vendor_at_limit(db, test_vendor, test_essential_tier, test_professional_tier): """Create vendor at order limit.""" from datetime import datetime, timezone now = datetime.now(timezone.utc) subscription = VendorSubscription( vendor_id=test_vendor.id, tier="essential", tier_id=test_essential_tier.id, status="active", period_start=now, period_end=now.replace(month=now.month + 1 if now.month < 12 else 1), orders_this_period=100, # At limit ) db.add(subscription) db.commit() db.refresh(test_vendor) return test_vendor @pytest.fixture def test_vendor_approaching_limit(db, test_vendor, test_essential_tier): """Create vendor approaching order limit (>=80%).""" from datetime import datetime, timezone now = datetime.now(timezone.utc) subscription = VendorSubscription( vendor_id=test_vendor.id, tier="essential", tier_id=test_essential_tier.id, status="active", period_start=now, period_end=now.replace(month=now.month + 1 if now.month < 12 else 1), orders_this_period=85, # 85% of 100 ) db.add(subscription) db.commit() db.refresh(test_vendor) return test_vendor @pytest.fixture def test_vendor_on_professional(db, test_vendor, test_professional_tier): """Create vendor on highest tier.""" from datetime import datetime, timezone now = datetime.now(timezone.utc) subscription = VendorSubscription( vendor_id=test_vendor.id, tier="professional", tier_id=test_professional_tier.id, status="active", period_start=now, period_end=now.replace(month=now.month + 1 if now.month < 12 else 1), orders_this_period=50, ) db.add(subscription) db.commit() db.refresh(test_vendor) return test_vendor @pytest.fixture def test_vendor_with_products(db, test_vendor_with_subscription, marketplace_product_factory): """Create vendor with products.""" for i in range(5): # Create marketplace product first mp = marketplace_product_factory(db, title=f"Test Product {i}") product = Product( vendor_id=test_vendor_with_subscription.id, marketplace_product_id=mp.id, price_cents=1000, is_active=True, ) db.add(product) db.commit() return test_vendor_with_subscription @pytest.fixture def test_vendor_with_team(db, test_vendor_with_subscription, test_user, other_user): """Create vendor with team members (owner + team member = 2).""" from models.database.vendor import VendorUserType # Add owner owner = VendorUser( vendor_id=test_vendor_with_subscription.id, user_id=test_user.id, user_type=VendorUserType.OWNER.value, is_active=True, ) db.add(owner) # Add team member team_member = VendorUser( vendor_id=test_vendor_with_subscription.id, user_id=other_user.id, user_type=VendorUserType.TEAM_MEMBER.value, is_active=True, ) db.add(team_member) db.commit() return test_vendor_with_subscription