chore: plan dynamic plan designer feature
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+74
-23
@@ -50,7 +50,7 @@ def test_default_tier_is_free():
|
||||
def test_get_tier_returns_correct_dict():
|
||||
t = get_tier("starter")
|
||||
assert t["id"] == "starter"
|
||||
assert t["price_monthly"] == 9
|
||||
assert t["price_monthly"] == 2.99
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -70,8 +70,10 @@ def test_get_all_tiers_returns_four():
|
||||
def test_all_tiers_have_required_fields():
|
||||
required = [
|
||||
"id", "name", "tagline", "price_monthly", "price_yearly",
|
||||
"trial_days",
|
||||
"lifetime_file_limit", "daily_upload_limit", "monthly_upload_limit",
|
||||
"max_storage_destinations", "max_ocr_pages_monthly", "max_file_size_mb",
|
||||
"max_mailboxes",
|
||||
"features", "cta",
|
||||
]
|
||||
for tid, tier in TIERS.items():
|
||||
@@ -81,19 +83,54 @@ def test_all_tiers_have_required_fields():
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_free_tier_has_lifetime_limit():
|
||||
"""Free tier must have a non-zero lifetime file limit."""
|
||||
assert TIERS["free"]["lifetime_file_limit"] > 0
|
||||
"""Free tier must have a non-zero lifetime file limit of 50."""
|
||||
assert TIERS["free"]["lifetime_file_limit"] == 50
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_business_tier_is_unlimited():
|
||||
"""Business tier must have 0 (unlimited) for all limits."""
|
||||
def test_free_tier_ocr_pages():
|
||||
"""Free tier must have 150 OCR pages."""
|
||||
assert TIERS["free"]["max_ocr_pages_monthly"] == 150
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_free_tier_has_no_mailboxes():
|
||||
"""Free tier must not allow email ingestion mailboxes."""
|
||||
assert TIERS["free"]["max_mailboxes"] == 0
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_business_tier_has_highest_limits():
|
||||
"""Business tier must have the highest limits of all paid tiers."""
|
||||
t = TIERS["business"]
|
||||
# lifetime, daily, monthly: no hard cap (0 = unlimited) for lifetime; daily/monthly capped
|
||||
assert t["lifetime_file_limit"] == 0
|
||||
assert t["daily_upload_limit"] == 0
|
||||
assert t["monthly_upload_limit"] == 0
|
||||
assert t["max_storage_destinations"] == 0
|
||||
assert t["max_ocr_pages_monthly"] == 0
|
||||
assert t["daily_upload_limit"] == 30
|
||||
assert t["monthly_upload_limit"] == 300
|
||||
assert t["max_ocr_pages_monthly"] == 1500
|
||||
# unlimited mailboxes
|
||||
assert t["max_mailboxes"] == 0
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_mailbox_limits_increase_by_tier():
|
||||
"""Mailbox limits must increase across tiers: free=0, starter=1, professional=3, business=0(∞)."""
|
||||
assert TIERS["free"]["max_mailboxes"] == 0
|
||||
assert TIERS["starter"]["max_mailboxes"] == 1
|
||||
assert TIERS["professional"]["max_mailboxes"] == 3
|
||||
assert TIERS["business"]["max_mailboxes"] == 0 # 0 means unlimited
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_paid_tiers_have_trial_days():
|
||||
"""All paid tiers must have a 30-day free trial."""
|
||||
for tid in ["starter", "professional", "business"]:
|
||||
assert TIERS[tid]["trial_days"] == 30, f"Tier '{tid}' missing 30-day trial"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_free_tier_has_no_trial():
|
||||
assert TIERS["free"]["trial_days"] == 0
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -160,16 +197,16 @@ def test_check_upload_skipped_without_tier():
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_check_upload_raises_when_lifetime_exceeded():
|
||||
"""Free tier: should raise QuotaExceeded when lifetime limit is hit."""
|
||||
"""Free tier: should raise QuotaExceeded when lifetime limit (50) is hit."""
|
||||
db = MagicMock()
|
||||
|
||||
with patch("app.utils.subscription.get_lifetime_file_count", return_value=25):
|
||||
with patch("app.utils.subscription.get_lifetime_file_count", return_value=50):
|
||||
with pytest.raises(QuotaExceeded) as exc_info:
|
||||
check_upload_allowed(db, "user@example.com", "free")
|
||||
|
||||
assert exc_info.value.limit_type == "lifetime"
|
||||
assert exc_info.value.limit_value == 25
|
||||
assert exc_info.value.current_value == 25
|
||||
assert exc_info.value.limit_value == 50
|
||||
assert exc_info.value.current_value == 50
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -181,11 +218,11 @@ def test_check_upload_passes_below_lifetime_limit():
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_check_upload_raises_when_daily_exceeded():
|
||||
"""Starter tier: should raise QuotaExceeded when daily limit is hit."""
|
||||
"""Starter tier: should raise QuotaExceeded when daily limit (5) is hit."""
|
||||
db = MagicMock()
|
||||
|
||||
with patch("app.utils.subscription.get_lifetime_file_count", return_value=0), \
|
||||
patch("app.utils.subscription.get_today_file_count", return_value=10):
|
||||
patch("app.utils.subscription.get_today_file_count", return_value=5):
|
||||
with pytest.raises(QuotaExceeded) as exc_info:
|
||||
check_upload_allowed(db, "user@example.com", "starter")
|
||||
|
||||
@@ -194,12 +231,12 @@ def test_check_upload_raises_when_daily_exceeded():
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_check_upload_raises_when_monthly_exceeded():
|
||||
"""Starter tier: should raise QuotaExceeded when monthly limit is hit."""
|
||||
"""Starter tier: should raise QuotaExceeded when monthly limit (50) is hit."""
|
||||
db = MagicMock()
|
||||
|
||||
with patch("app.utils.subscription.get_lifetime_file_count", return_value=0), \
|
||||
patch("app.utils.subscription.get_today_file_count", return_value=0), \
|
||||
patch("app.utils.subscription.get_month_file_count", return_value=100):
|
||||
patch("app.utils.subscription.get_month_file_count", return_value=50):
|
||||
with pytest.raises(QuotaExceeded) as exc_info:
|
||||
check_upload_allowed(db, "user@example.com", "starter")
|
||||
|
||||
@@ -207,16 +244,30 @@ def test_check_upload_raises_when_monthly_exceeded():
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_check_upload_business_tier_never_raises():
|
||||
"""Business tier has no limits — check_upload_allowed must never raise."""
|
||||
def test_check_upload_business_tier_within_limits():
|
||||
"""Business tier: upload is allowed as long as counts are below the capped limits."""
|
||||
db = MagicMock()
|
||||
# Even with absurdly high counts, business tier is unlimited
|
||||
with patch("app.utils.subscription.get_lifetime_file_count", return_value=999999), \
|
||||
patch("app.utils.subscription.get_today_file_count", return_value=999999), \
|
||||
patch("app.utils.subscription.get_month_file_count", return_value=999999):
|
||||
# Use counts well below Business limits (30/day, 300/mo)
|
||||
with patch("app.utils.subscription.get_lifetime_file_count", return_value=0), \
|
||||
patch("app.utils.subscription.get_today_file_count", return_value=10), \
|
||||
patch("app.utils.subscription.get_month_file_count", return_value=100):
|
||||
check_upload_allowed(db, "user@example.com", "business") # must not raise
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_check_upload_business_tier_raises_when_daily_exceeded():
|
||||
"""Business tier: should raise QuotaExceeded when daily limit (30) is hit."""
|
||||
db = MagicMock()
|
||||
|
||||
with patch("app.utils.subscription.get_lifetime_file_count", return_value=0), \
|
||||
patch("app.utils.subscription.get_today_file_count", return_value=30):
|
||||
with pytest.raises(QuotaExceeded) as exc_info:
|
||||
check_upload_allowed(db, "user@example.com", "business")
|
||||
|
||||
assert exc_info.value.limit_type == "daily"
|
||||
assert exc_info.value.limit_value == 30
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_user_usage (mocked DB)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user