mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-13 08:45:24 +00:00
- Add new routers: admin, appointments, campaigns - Add storage service and logging config - Add migrations directory and test suite with pytest config - Add supabase_migration_features.sql - Update models, dependencies, config, and existing routers - Remove whatsapp_service (deleted) - Update pyproject.toml and uv.lock dependencies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
286 lines
10 KiB
Python
286 lines
10 KiB
Python
"""
|
|
Tests for PLAN_LIMITS in config.py.
|
|
|
|
Ensures each plan has the correct feature gates and that the pricing
|
|
tiers are properly differentiated.
|
|
"""
|
|
import pytest
|
|
from app.config import PLAN_LIMITS, MODEL_CATALOG, DEFAULT_MODELS, MODEL_PROVIDERS
|
|
|
|
|
|
class TestPlanStructure:
|
|
"""Every plan must have all required keys."""
|
|
|
|
REQUIRED_KEYS = {
|
|
"max_chatbots", "max_published", "max_documents_per_chatbot",
|
|
"max_document_size_mb", "models", "conversations_limit",
|
|
"code_export", "analytics", "gap_suggestions", "channels",
|
|
"url_sources", "leads_per_month", "inbox_replies", "leads_editing",
|
|
"show_branding", "appointments", "appointments_chatbots",
|
|
"campaigns", "campaigns_per_month", "max_campaign_recipients",
|
|
}
|
|
|
|
@pytest.mark.parametrize("plan", ["free", "starter", "business", "agency", "enterprise"])
|
|
def test_all_required_keys_present(self, plan):
|
|
config = PLAN_LIMITS[plan]
|
|
missing = self.REQUIRED_KEYS - set(config.keys())
|
|
assert not missing, f"Plan '{plan}' missing keys: {missing}"
|
|
|
|
@pytest.mark.parametrize("plan", ["free", "starter", "business", "agency", "enterprise"])
|
|
def test_models_list_is_non_empty(self, plan):
|
|
models = PLAN_LIMITS[plan]["models"]
|
|
assert len(models) > 0
|
|
|
|
@pytest.mark.parametrize("plan", ["free", "starter", "business", "agency", "enterprise"])
|
|
def test_default_model_exists_for_plan(self, plan):
|
|
default = DEFAULT_MODELS.get(plan)
|
|
assert default is not None, f"No default model for {plan}"
|
|
|
|
|
|
class TestFreePlanRestrictions:
|
|
def setup_method(self):
|
|
self.plan = PLAN_LIMITS["free"]
|
|
|
|
def test_max_published_is_one(self):
|
|
assert self.plan["max_published"] == 1
|
|
|
|
def test_no_inbox_replies(self):
|
|
assert self.plan["inbox_replies"] is False
|
|
|
|
def test_no_leads_editing(self):
|
|
assert self.plan["leads_editing"] is False
|
|
|
|
def test_no_appointments(self):
|
|
assert self.plan["appointments"] is False
|
|
assert self.plan["appointments_chatbots"] == 0
|
|
|
|
def test_no_campaigns(self):
|
|
assert self.plan["campaigns"] is False
|
|
assert self.plan["campaigns_per_month"] == 0
|
|
assert self.plan["max_campaign_recipients"] == 0
|
|
|
|
def test_show_branding(self):
|
|
assert self.plan["show_branding"] is True
|
|
|
|
def test_no_analytics(self):
|
|
assert self.plan["analytics"] is False
|
|
|
|
def test_no_gap_suggestions(self):
|
|
assert self.plan["gap_suggestions"] is False
|
|
|
|
def test_no_channels(self):
|
|
assert self.plan["channels"] == []
|
|
|
|
def test_no_url_sources(self):
|
|
assert self.plan["url_sources"] == 0
|
|
|
|
def test_no_leads(self):
|
|
assert self.plan["leads_per_month"] == 0
|
|
|
|
def test_only_free_model(self):
|
|
models = self.plan["models"]
|
|
assert len(models) == 1
|
|
assert "llama" in models[0].lower()
|
|
|
|
|
|
class TestStarterPlan:
|
|
def setup_method(self):
|
|
self.plan = PLAN_LIMITS["starter"]
|
|
|
|
def test_max_published_is_three(self):
|
|
assert self.plan["max_published"] == 3
|
|
|
|
def test_has_inbox_replies(self):
|
|
assert self.plan["inbox_replies"] is True
|
|
|
|
def test_has_leads_editing(self):
|
|
assert self.plan["leads_editing"] is True
|
|
|
|
def test_has_appointments(self):
|
|
assert self.plan["appointments"] is True
|
|
|
|
def test_appointments_limited_to_one_chatbot(self):
|
|
assert self.plan["appointments_chatbots"] == 1
|
|
|
|
def test_has_campaigns(self):
|
|
assert self.plan["campaigns"] is True
|
|
|
|
def test_campaigns_limited_per_month(self):
|
|
assert 0 < self.plan["campaigns_per_month"] < 999999
|
|
|
|
def test_campaign_recipients_limited(self):
|
|
assert 0 < self.plan["max_campaign_recipients"] < 999999
|
|
|
|
def test_show_branding(self):
|
|
# Starter still shows branding
|
|
assert self.plan["show_branding"] is True
|
|
|
|
def test_has_analytics(self):
|
|
assert self.plan["analytics"] is True
|
|
|
|
def test_no_gap_suggestions(self):
|
|
assert self.plan["gap_suggestions"] is False
|
|
|
|
def test_has_telegram_channel(self):
|
|
assert "telegram" in self.plan["channels"]
|
|
|
|
def test_fireworks_models_only(self):
|
|
models = self.plan["models"]
|
|
for m in models:
|
|
assert "fireworks" in m
|
|
|
|
def test_no_premium_models(self):
|
|
premium = {"gpt-4o", "gpt-4o-mini", "claude-haiku-4-5-20251001",
|
|
"gemini-2.5-flash", "gemini-2.5-lite", "gemini-2.5-pro"}
|
|
assert not premium.intersection(set(self.plan["models"]))
|
|
|
|
|
|
class TestBusinessPlan:
|
|
def setup_method(self):
|
|
self.plan = PLAN_LIMITS["business"]
|
|
|
|
def test_max_published_is_ten(self):
|
|
assert self.plan["max_published"] == 10
|
|
|
|
def test_can_remove_branding(self):
|
|
assert self.plan["show_branding"] is False
|
|
|
|
def test_unlimited_appointments_chatbots(self):
|
|
assert self.plan["appointments_chatbots"] == 999999
|
|
|
|
def test_unlimited_campaigns_per_month(self):
|
|
assert self.plan["campaigns_per_month"] == 999999
|
|
|
|
def test_has_campaign_recipient_limit(self):
|
|
# Business is capped below Agency/Enterprise
|
|
assert self.plan["max_campaign_recipients"] < PLAN_LIMITS["agency"]["max_campaign_recipients"]
|
|
|
|
def test_has_gap_suggestions(self):
|
|
assert self.plan["gap_suggestions"] is True
|
|
|
|
def test_has_premium_models(self):
|
|
premium = {"gpt-4o", "gpt-4o-mini", "claude-haiku-4-5-20251001"}
|
|
assert premium.issubset(set(self.plan["models"]))
|
|
|
|
def test_has_google_models(self):
|
|
google = {"gemini-2.5-flash", "gemini-2.5-pro"}
|
|
assert google.issubset(set(self.plan["models"]))
|
|
|
|
|
|
class TestAgencyPlan:
|
|
def setup_method(self):
|
|
self.plan = PLAN_LIMITS["agency"]
|
|
|
|
def test_unlimited_published(self):
|
|
assert self.plan["max_published"] == 999999
|
|
|
|
def test_unlimited_campaign_recipients(self):
|
|
assert self.plan["max_campaign_recipients"] == 999999
|
|
|
|
def test_code_export_enabled(self):
|
|
assert self.plan["code_export"] is True
|
|
|
|
def test_gap_suggestions_enabled(self):
|
|
assert self.plan["gap_suggestions"] is True
|
|
|
|
def test_no_branding(self):
|
|
assert self.plan["show_branding"] is False
|
|
|
|
|
|
class TestEnterprisePlan:
|
|
def setup_method(self):
|
|
self.plan = PLAN_LIMITS["enterprise"]
|
|
|
|
def test_wildcard_models(self):
|
|
assert "*" in self.plan["models"]
|
|
|
|
def test_all_features_enabled(self):
|
|
assert self.plan["appointments"] is True
|
|
assert self.plan["campaigns"] is True
|
|
assert self.plan["inbox_replies"] is True
|
|
assert self.plan["leads_editing"] is True
|
|
assert self.plan["gap_suggestions"] is True
|
|
assert self.plan["code_export"] is True
|
|
assert self.plan["show_branding"] is False
|
|
|
|
def test_unlimited_everything(self):
|
|
BIG = 999999
|
|
assert self.plan["max_published"] == BIG
|
|
assert self.plan["appointments_chatbots"] == BIG
|
|
assert self.plan["campaigns_per_month"] == BIG
|
|
assert self.plan["max_campaign_recipients"] == BIG
|
|
|
|
|
|
class TestTierProgression:
|
|
"""Each higher tier must be strictly better than the tier below it."""
|
|
|
|
def test_max_published_increases_with_tier(self):
|
|
assert PLAN_LIMITS["free"]["max_published"] \
|
|
<= PLAN_LIMITS["starter"]["max_published"] \
|
|
<= PLAN_LIMITS["business"]["max_published"] \
|
|
<= PLAN_LIMITS["agency"]["max_published"]
|
|
|
|
def test_conversation_limits_increase_with_tier(self):
|
|
assert PLAN_LIMITS["free"]["conversations_limit"] \
|
|
< PLAN_LIMITS["starter"]["conversations_limit"] \
|
|
< PLAN_LIMITS["business"]["conversations_limit"] \
|
|
< PLAN_LIMITS["agency"]["conversations_limit"]
|
|
|
|
def test_business_has_more_models_than_starter(self):
|
|
starter_models = set(PLAN_LIMITS["starter"]["models"])
|
|
business_models = set(PLAN_LIMITS["business"]["models"])
|
|
assert starter_models.issubset(business_models)
|
|
assert len(business_models) > len(starter_models)
|
|
|
|
def test_appointment_chatbots_increases_with_tier(self):
|
|
assert PLAN_LIMITS["free"]["appointments_chatbots"] \
|
|
<= PLAN_LIMITS["starter"]["appointments_chatbots"] \
|
|
<= PLAN_LIMITS["business"]["appointments_chatbots"]
|
|
|
|
def test_campaign_recipients_increases_with_tier(self):
|
|
assert PLAN_LIMITS["free"]["max_campaign_recipients"] \
|
|
< PLAN_LIMITS["starter"]["max_campaign_recipients"] \
|
|
< PLAN_LIMITS["business"]["max_campaign_recipients"] \
|
|
<= PLAN_LIMITS["agency"]["max_campaign_recipients"]
|
|
|
|
|
|
class TestModelCatalog:
|
|
"""MODEL_CATALOG and MODEL_PROVIDERS consistency checks."""
|
|
|
|
def test_all_catalog_models_have_required_fields(self):
|
|
for model_id, meta in MODEL_CATALOG.items():
|
|
assert "name" in meta, f"{model_id} missing 'name'"
|
|
assert "provider" in meta, f"{model_id} missing 'provider'"
|
|
assert "badge" in meta, f"{model_id} missing 'badge'"
|
|
|
|
def test_all_catalog_models_have_provider_mapping(self):
|
|
for model_id in MODEL_CATALOG:
|
|
assert model_id in MODEL_PROVIDERS, \
|
|
f"{model_id} in MODEL_CATALOG but not in MODEL_PROVIDERS"
|
|
|
|
def test_provider_values_are_known(self):
|
|
known = {"fireworks", "openai", "anthropic", "google"}
|
|
for model_id, provider in MODEL_PROVIDERS.items():
|
|
assert provider in known, \
|
|
f"{model_id} has unknown provider '{provider}'"
|
|
|
|
def test_non_enterprise_plan_models_are_in_catalog(self):
|
|
for plan_name, plan in PLAN_LIMITS.items():
|
|
if plan_name == "enterprise":
|
|
continue
|
|
for model_id in plan["models"]:
|
|
assert model_id in MODEL_CATALOG, \
|
|
f"Plan '{plan_name}' references '{model_id}' not in MODEL_CATALOG"
|
|
|
|
def test_default_models_are_in_catalog(self):
|
|
for plan, model_id in DEFAULT_MODELS.items():
|
|
assert model_id in MODEL_CATALOG, \
|
|
f"DEFAULT_MODELS[{plan}] = '{model_id}' not in MODEL_CATALOG"
|
|
|
|
def test_default_models_are_in_plan_limits(self):
|
|
for plan, model_id in DEFAULT_MODELS.items():
|
|
plan_models = PLAN_LIMITS[plan]["models"]
|
|
if "*" not in plan_models:
|
|
assert model_id in plan_models, \
|
|
f"Default model '{model_id}' for plan '{plan}' not in that plan's models list"
|