test: add service tests and fix architecture violations
- Add comprehensive unit tests for FeatureService (24 tests) - Add comprehensive unit tests for UsageService (11 tests) - Fix API-002/API-003 architecture violations in feature/usage APIs - Move database queries from API layer to service layer - Create UsageService for usage and limits management - Create custom exceptions (FeatureNotFoundError, TierNotFoundError) - Fix ValidationException usage in content_pages.py - Refactor vendor features API to use proper response models - All 35 new tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
367
tests/unit/services/test_feature_service.py
Normal file
367
tests/unit/services/test_feature_service.py
Normal file
@@ -0,0 +1,367 @@
|
||||
# tests/unit/services/test_feature_service.py
|
||||
"""Unit tests for FeatureService."""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.exceptions import FeatureNotFoundError, InvalidFeatureCodesError, TierNotFoundError
|
||||
from app.services.feature_service import FeatureService, feature_service
|
||||
from models.database.feature import Feature
|
||||
from models.database.subscription import SubscriptionTier, VendorSubscription
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.features
|
||||
class TestFeatureServiceAvailability:
|
||||
"""Test suite for feature availability checking."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Initialize service instance before each test."""
|
||||
self.service = FeatureService()
|
||||
|
||||
def test_has_feature_true(self, db, test_vendor_with_subscription):
|
||||
"""Test has_feature returns True for available feature."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
result = self.service.has_feature(db, vendor_id, "basic_reports")
|
||||
assert result is True
|
||||
|
||||
def test_has_feature_false(self, db, test_vendor_with_subscription):
|
||||
"""Test has_feature returns False for unavailable feature."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
result = self.service.has_feature(db, vendor_id, "api_access")
|
||||
assert result is False
|
||||
|
||||
def test_has_feature_no_subscription(self, db, test_vendor):
|
||||
"""Test has_feature returns False for vendor without subscription."""
|
||||
result = self.service.has_feature(db, test_vendor.id, "basic_reports")
|
||||
assert result is False
|
||||
|
||||
def test_get_vendor_feature_codes(self, db, test_vendor_with_subscription):
|
||||
"""Test getting all feature codes for vendor."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
features = self.service.get_vendor_feature_codes(db, vendor_id)
|
||||
|
||||
assert isinstance(features, set)
|
||||
assert "basic_reports" in features
|
||||
assert "api_access" not in features
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.features
|
||||
class TestFeatureServiceListing:
|
||||
"""Test suite for feature listing operations."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Initialize service instance before each test."""
|
||||
self.service = FeatureService()
|
||||
|
||||
def test_get_vendor_features(self, db, test_vendor_with_subscription, test_features):
|
||||
"""Test getting all features with availability."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
features = self.service.get_vendor_features(db, vendor_id)
|
||||
|
||||
assert len(features) > 0
|
||||
basic_reports = next((f for f in features if f.code == "basic_reports"), None)
|
||||
assert basic_reports is not None
|
||||
assert basic_reports.is_available is True
|
||||
|
||||
api_access = next((f for f in features if f.code == "api_access"), None)
|
||||
assert api_access is not None
|
||||
assert api_access.is_available is False
|
||||
|
||||
def test_get_vendor_features_by_category(
|
||||
self, db, test_vendor_with_subscription, test_features
|
||||
):
|
||||
"""Test filtering features by category."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
features = self.service.get_vendor_features(db, vendor_id, category="analytics")
|
||||
|
||||
assert all(f.category == "analytics" for f in features)
|
||||
|
||||
def test_get_vendor_features_available_only(
|
||||
self, db, test_vendor_with_subscription, test_features
|
||||
):
|
||||
"""Test getting only available features."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
features = self.service.get_vendor_features(
|
||||
db, vendor_id, include_unavailable=False
|
||||
)
|
||||
|
||||
assert all(f.is_available for f in features)
|
||||
|
||||
def test_get_available_feature_codes(self, db, test_vendor_with_subscription):
|
||||
"""Test getting simple list of available codes."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
codes = self.service.get_available_feature_codes(db, vendor_id)
|
||||
|
||||
assert isinstance(codes, list)
|
||||
assert "basic_reports" in codes
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.features
|
||||
class TestFeatureServiceMetadata:
|
||||
"""Test suite for feature metadata operations."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Initialize service instance before each test."""
|
||||
self.service = FeatureService()
|
||||
|
||||
def test_get_feature_by_code(self, db, test_features):
|
||||
"""Test getting feature by code."""
|
||||
feature = self.service.get_feature_by_code(db, "basic_reports")
|
||||
|
||||
assert feature is not None
|
||||
assert feature.code == "basic_reports"
|
||||
assert feature.name == "Basic Reports"
|
||||
|
||||
def test_get_feature_by_code_not_found(self, db, test_features):
|
||||
"""Test getting non-existent feature returns None."""
|
||||
feature = self.service.get_feature_by_code(db, "nonexistent")
|
||||
assert feature is None
|
||||
|
||||
def test_get_feature_upgrade_info(self, db, test_features, test_subscription_tiers):
|
||||
"""Test getting upgrade info for locked feature."""
|
||||
info = self.service.get_feature_upgrade_info(db, "api_access")
|
||||
|
||||
assert info is not None
|
||||
assert info.feature_code == "api_access"
|
||||
assert info.required_tier_code == "professional"
|
||||
|
||||
def test_get_feature_upgrade_info_no_minimum_tier(self, db, test_features):
|
||||
"""Test upgrade info for feature without minimum tier."""
|
||||
# basic_reports has no minimum tier in fixtures
|
||||
info = self.service.get_feature_upgrade_info(db, "basic_reports")
|
||||
assert info is None
|
||||
|
||||
def test_get_all_features(self, db, test_features):
|
||||
"""Test getting all features for admin."""
|
||||
features = self.service.get_all_features(db)
|
||||
assert len(features) >= 3
|
||||
|
||||
def test_get_all_features_by_category(self, db, test_features):
|
||||
"""Test filtering features by category."""
|
||||
features = self.service.get_all_features(db, category="analytics")
|
||||
assert all(f.category == "analytics" for f in features)
|
||||
|
||||
def test_get_categories(self, db, test_features):
|
||||
"""Test getting unique categories."""
|
||||
categories = self.service.get_categories(db)
|
||||
assert "analytics" in categories
|
||||
assert "integrations" in categories
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.features
|
||||
class TestFeatureServiceCache:
|
||||
"""Test suite for cache operations."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Initialize service instance before each test."""
|
||||
self.service = FeatureService()
|
||||
|
||||
def test_cache_invalidation(self, db, test_vendor_with_subscription):
|
||||
"""Test cache invalidation for vendor."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
|
||||
# Prime the cache
|
||||
self.service.get_vendor_feature_codes(db, vendor_id)
|
||||
assert self.service._cache.get(vendor_id) is not None
|
||||
|
||||
# Invalidate
|
||||
self.service.invalidate_vendor_cache(vendor_id)
|
||||
assert self.service._cache.get(vendor_id) is None
|
||||
|
||||
def test_cache_invalidate_all(self, db, test_vendor_with_subscription):
|
||||
"""Test invalidating entire cache."""
|
||||
vendor_id = test_vendor_with_subscription.id
|
||||
|
||||
# Prime the cache
|
||||
self.service.get_vendor_feature_codes(db, vendor_id)
|
||||
|
||||
# Invalidate all
|
||||
self.service.invalidate_all_cache()
|
||||
assert self.service._cache.get(vendor_id) is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.features
|
||||
class TestFeatureServiceAdmin:
|
||||
"""Test suite for admin operations."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Initialize service instance before each test."""
|
||||
self.service = FeatureService()
|
||||
|
||||
def test_get_all_tiers_with_features(self, db, test_subscription_tiers):
|
||||
"""Test getting all tiers."""
|
||||
tiers = self.service.get_all_tiers_with_features(db)
|
||||
|
||||
assert len(tiers) == 2
|
||||
assert tiers[0].code == "essential"
|
||||
assert tiers[1].code == "professional"
|
||||
|
||||
def test_update_tier_features(self, db, test_subscription_tiers, test_features):
|
||||
"""Test updating tier features."""
|
||||
tier = self.service.update_tier_features(
|
||||
db, "essential", ["basic_reports", "api_access"]
|
||||
)
|
||||
db.commit()
|
||||
|
||||
assert "api_access" in tier.features
|
||||
|
||||
def test_update_tier_features_invalid_codes(
|
||||
self, db, test_subscription_tiers, test_features
|
||||
):
|
||||
"""Test updating tier with invalid feature codes."""
|
||||
with pytest.raises(InvalidFeatureCodesError) as exc_info:
|
||||
self.service.update_tier_features(
|
||||
db, "essential", ["basic_reports", "nonexistent_feature"]
|
||||
)
|
||||
|
||||
assert "nonexistent_feature" in exc_info.value.invalid_codes
|
||||
|
||||
def test_update_tier_features_tier_not_found(self, db, test_features):
|
||||
"""Test updating non-existent tier."""
|
||||
with pytest.raises(TierNotFoundError) as exc_info:
|
||||
self.service.update_tier_features(db, "nonexistent", ["basic_reports"])
|
||||
|
||||
assert exc_info.value.tier_code == "nonexistent"
|
||||
|
||||
def test_update_feature(self, db, test_features):
|
||||
"""Test updating feature metadata."""
|
||||
feature = self.service.update_feature(
|
||||
db, "basic_reports", name="Updated Reports", description="New description"
|
||||
)
|
||||
db.commit()
|
||||
|
||||
assert feature.name == "Updated Reports"
|
||||
assert feature.description == "New description"
|
||||
|
||||
def test_update_feature_not_found(self, db, test_features):
|
||||
"""Test updating non-existent feature."""
|
||||
with pytest.raises(FeatureNotFoundError) as exc_info:
|
||||
self.service.update_feature(db, "nonexistent", name="Test")
|
||||
|
||||
assert exc_info.value.feature_code == "nonexistent"
|
||||
|
||||
def test_update_feature_minimum_tier(
|
||||
self, db, test_features, test_subscription_tiers
|
||||
):
|
||||
"""Test updating feature minimum tier."""
|
||||
feature = self.service.update_feature(
|
||||
db, "basic_reports", minimum_tier_code="professional"
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(feature)
|
||||
|
||||
assert feature.minimum_tier is not None
|
||||
assert feature.minimum_tier.code == "professional"
|
||||
|
||||
|
||||
# ==================== Fixtures ====================
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_subscription_tiers(db):
|
||||
"""Create multiple subscription tiers."""
|
||||
tiers = [
|
||||
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,
|
||||
),
|
||||
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=5,
|
||||
features=["basic_reports", "api_access", "analytics_dashboard"],
|
||||
is_active=True,
|
||||
display_order=2,
|
||||
),
|
||||
]
|
||||
for tier in tiers:
|
||||
db.add(tier)
|
||||
db.commit()
|
||||
for tier in tiers:
|
||||
db.refresh(tier)
|
||||
return tiers
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_vendor_with_subscription(db, test_vendor, test_subscription_tiers):
|
||||
"""Create a vendor with an active subscription."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
essential_tier = test_subscription_tiers[0] # Use the essential tier from tiers list
|
||||
now = datetime.now(timezone.utc)
|
||||
subscription = VendorSubscription(
|
||||
vendor_id=test_vendor.id,
|
||||
tier="essential",
|
||||
tier_id=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_features(db, test_subscription_tiers):
|
||||
"""Create test features."""
|
||||
features = [
|
||||
Feature(
|
||||
code="basic_reports",
|
||||
name="Basic Reports",
|
||||
description="View basic analytics reports",
|
||||
category="analytics",
|
||||
ui_location="sidebar",
|
||||
ui_icon="chart-bar",
|
||||
is_active=True,
|
||||
display_order=1,
|
||||
),
|
||||
Feature(
|
||||
code="api_access",
|
||||
name="API Access",
|
||||
description="Access the REST API",
|
||||
category="integrations",
|
||||
ui_location="settings",
|
||||
ui_icon="code",
|
||||
minimum_tier_id=test_subscription_tiers[1].id, # Professional
|
||||
is_active=True,
|
||||
display_order=2,
|
||||
),
|
||||
Feature(
|
||||
code="analytics_dashboard",
|
||||
name="Analytics Dashboard",
|
||||
description="Advanced analytics dashboard",
|
||||
category="analytics",
|
||||
ui_location="sidebar",
|
||||
ui_icon="presentation-chart-line",
|
||||
minimum_tier_id=test_subscription_tiers[1].id,
|
||||
is_active=True,
|
||||
display_order=3,
|
||||
),
|
||||
]
|
||||
for feature in features:
|
||||
db.add(feature)
|
||||
db.commit()
|
||||
for feature in features:
|
||||
db.refresh(feature)
|
||||
return features
|
||||
308
tests/unit/services/test_usage_service.py
Normal file
308
tests/unit/services/test_usage_service.py
Normal file
@@ -0,0 +1,308 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user