""" 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"