import { describe, expect, it } from 'vitest'; import { AI_QUOTA_PAYWALL_REASON_CONFIG, getAiQuotaPaywallReasonFromMessage } from './quota-paywall'; describe('getAiQuotaPaywallReasonFromMessage', () => { it('returns no_seat_assigned for various seat-missing phrasings', () => { expect(getAiQuotaPaywallReasonFromMessage('no_seat_assigned')).toBe('no_seat_assigned'); expect(getAiQuotaPaywallReasonFromMessage("You don't have an AI Builder license assigned. Ask your admin to assign a license.")).toBe( 'no_seat_assigned' ); expect(getAiQuotaPaywallReasonFromMessage("user doesn't have an AI Builder license assigned")).toBe('no_seat_assigned'); expect(getAiQuotaPaywallReasonFromMessage('user does not have an AI Builder seat assigned')).toBe('no_seat_assigned'); expect(getAiQuotaPaywallReasonFromMessage('No builder seat assigned to user')).toBe('no_seat_assigned'); }); it('returns payment_past_due for the dedicated phrasings, including the dashboard-chat router copy', () => { expect(getAiQuotaPaywallReasonFromMessage('payment_past_due')).toBe('payment_past_due'); expect(getAiQuotaPaywallReasonFromMessage('Your subscription payment failed. Update your payment method to continue.')).toBe( 'payment_past_due' ); expect(getAiQuotaPaywallReasonFromMessage('Your payment is past due')).toBe('payment_past_due'); }); it('returns trial_expired for the dashboard-chat router copy and other variants', () => { expect(getAiQuotaPaywallReasonFromMessage('trial_expired')).toBe('trial_expired'); expect(getAiQuotaPaywallReasonFromMessage('Your AI trial period has ended')).toBe('trial_expired'); expect(getAiQuotaPaywallReasonFromMessage('Your free trial has ended')).toBe('trial_expired'); }); it('returns token_limit_exceeded for the generic fallback message', () => { expect(getAiQuotaPaywallReasonFromMessage('token_limit_exceeded')).toBe('token_limit_exceeded'); expect(getAiQuotaPaywallReasonFromMessage('AI usage limit reached')).toBe('token_limit_exceeded'); }); it('returns deploy_quota_exceeded for the deploy variant', () => { expect(getAiQuotaPaywallReasonFromMessage('deploy_quota_exceeded')).toBe('deploy_quota_exceeded'); }); it('returns credit_limit_exceeded when the org credit copy fires', () => { expect(getAiQuotaPaywallReasonFromMessage('credit_limit_exceeded')).toBe('credit_limit_exceeded'); expect(getAiQuotaPaywallReasonFromMessage('Your organization has reached its AI usage limit')).toBe('credit_limit_exceeded'); }); it('is case insensitive', () => { expect(getAiQuotaPaywallReasonFromMessage('PAYMENT_PAST_DUE')).toBe('payment_past_due'); expect(getAiQuotaPaywallReasonFromMessage('YOUR FREE TRIAL HAS ENDED')).toBe('trial_expired'); }); it('returns provisioning_error for the provisioning phrasings, including the server 402/429 copy', () => { expect(getAiQuotaPaywallReasonFromMessage('provisioning_error')).toBe('provisioning_error'); expect(getAiQuotaPaywallReasonFromMessage("Your organization's plan hasn't finished provisioning. Try again in a few minutes.")).toBe( 'provisioning_error' ); expect(getAiQuotaPaywallReasonFromMessage('The plan has not finished provisioning')).toBe('provisioning_error'); }); it('returns undefined when the message does not match any known phrasing', () => { expect(getAiQuotaPaywallReasonFromMessage('Connection refused')).toBeUndefined(); expect(getAiQuotaPaywallReasonFromMessage('')).toBeUndefined(); }); }); describe('AI_QUOTA_PAYWALL_REASON_CONFIG', () => { it('has a config entry for every AiQuotaPaywallReason', () => { // If a new reason is added without a config entry, the modal will fall back to // undefined `title`/`subtitle` strings — better to fail at compile time AND at // runtime here. const expectedReasons = [ 'credit_limit_exceeded', 'deploy_quota_exceeded', 'no_seat_assigned', 'payment_past_due', 'provisioning_error', 'token_limit_exceeded', 'trial_expired' ] as const; for (const r of expectedReasons) { expect(AI_QUOTA_PAYWALL_REASON_CONFIG[r]).toBeDefined(); expect(AI_QUOTA_PAYWALL_REASON_CONFIG[r].title.length).toBeGreaterThan(0); expect(AI_QUOTA_PAYWALL_REASON_CONFIG[r].subtitle.length).toBeGreaterThan(0); } }); it('payment_past_due copy mentions payment / subscription', () => { const cfg = AI_QUOTA_PAYWALL_REASON_CONFIG.payment_past_due; const text = `${cfg.title} ${cfg.subtitle}`.toLowerCase(); expect(text).toContain('payment'); expect(text).toMatch(/subscription|paid features/); }); });