const test = require('node:test'); const assert = require('node:assert/strict'); const { buildDefaultAccount, alignAccountToCurrentCycle, getAvailableCredits, consumeCredits, ensureSufficientCredits, getMonthlyAllowanceForPlan, getValidProEntitlement, applyRevenueCatEntitlementState, AVAILABLE_PRODUCTS, } = require('../lib/billing'); const NOW = new Date('2026-07-06T12:00:00Z'); const freeAccount = (overrides = {}) => ({ ...buildDefaultAccount('user-1', NOW), ...overrides, }); test('free plan gets 3 monthly credits', () => { assert.equal(getMonthlyAllowanceForPlan('free'), 3); assert.equal(buildDefaultAccount('u', NOW).monthlyAllowance, 3); }); test('free account has available credits', () => { const account = freeAccount({ monthlyAllowance: 3, usedThisCycle: 1 }); assert.equal(getAvailableCredits(account), 2); }); test('free account topup balance counts as available', () => { const account = freeAccount({ monthlyAllowance: 3, usedThisCycle: 3, topupBalance: 10 }); assert.equal(getAvailableCredits(account), 10); }); test('consumeCredits charges a free account from the monthly allowance', () => { const account = freeAccount({ monthlyAllowance: 3, usedThisCycle: 0 }); const charged = consumeCredits(account, 1); assert.equal(charged, 1); assert.equal(account.usedThisCycle, 1); }); test('consumeCredits throws 402 for an exhausted free account', () => { const account = freeAccount({ monthlyAllowance: 3, usedThisCycle: 3, topupBalance: 0 }); assert.throws(() => consumeCredits(account, 1), (error) => { assert.equal(error.code, 'INSUFFICIENT_CREDITS'); assert.equal(error.status, 402); assert.deepEqual(error.metadata, { required: 1, available: 0 }); return true; }); }); test('legacy free account with allowance 0 is migrated to 3', () => { const account = freeAccount({ monthlyAllowance: 0 }); const aligned = alignAccountToCurrentCycle(account, NOW); assert.equal(aligned.monthlyAllowance, 3); }); test('pro and trial allowances are unchanged', () => { assert.equal(getMonthlyAllowanceForPlan('pro'), 100); const trial = freeAccount({ plan: 'pro', monthlyAllowance: 30, usedThisCycle: 5 }); const aligned = alignAccountToCurrentCycle(trial, NOW); assert.equal(aligned.monthlyAllowance, 30); // trial allowance stays allowed assert.equal(getAvailableCredits(aligned), 25); }); test('monthly cycle rollover resets free usage', () => { const account = freeAccount({ monthlyAllowance: 3, usedThisCycle: 3, cycleEndsAt: '2026-07-01T00:00:00.000Z', }); const aligned = alignAccountToCurrentCycle(account, NOW); assert.equal(aligned.usedThisCycle, 0); assert.equal(aligned.monthlyAllowance, 3); assert.equal(getAvailableCredits(aligned), 3); }); test('ensureSufficientCredits throws 402 when balance is below cost', () => { const account = freeAccount({ monthlyAllowance: 3, usedThisCycle: 2, topupBalance: 0 }); assert.throws(() => ensureSufficientCredits(account, 2), (error) => { assert.equal(error.code, 'INSUFFICIENT_CREDITS'); assert.equal(error.status, 402); assert.deepEqual(error.metadata, { required: 2, available: 1 }); return true; }); }); test('ensureSufficientCredits passes when balance covers cost', () => { const account = freeAccount({ monthlyAllowance: 3, usedThisCycle: 1, topupBalance: 0 }); assert.doesNotThrow(() => ensureSufficientCredits(account, 2)); }); const proEntitlement = (productIdentifier, extra = {}) => ({ entitlements: { active: { pro: { productIdentifier, ...extra } } }, allPurchasedProductIdentifiers: [productIdentifier], }); test('weekly_pro is an offered product', () => { assert.ok(AVAILABLE_PRODUCTS.includes('weekly_pro')); }); test('weekly_pro backs a valid pro entitlement', () => { // Ohne diesen Eintrag in SUPPORTED_SUBSCRIPTION_PRODUCTS verwirft der Sync das // aktive Entitlement und der Wochenabonnent bleibt nach dem Kauf auf 'free'. const entitlement = getValidProEntitlement(proEntitlement('weekly_pro')); assert.ok(entitlement); assert.equal(entitlement.productIdentifier, 'weekly_pro'); }); test('monthly_pro and yearly_pro still back a valid pro entitlement', () => { assert.ok(getValidProEntitlement(proEntitlement('monthly_pro'))); assert.ok(getValidProEntitlement(proEntitlement('yearly_pro'))); }); test('an unrelated product does not back a pro entitlement', () => { // Regressionsschutz: die Produktliste darf sich nicht zu einem Freibrief entwickeln. assert.equal(getValidProEntitlement(proEntitlement('topup_small')), null); }); test('weekly_pro grants the full pro allowance, its trial only the trial allowance', () => { const paid = freeAccount(); applyRevenueCatEntitlementState(paid, { active: true, isTrial: false, renewsAt: null }); assert.equal(paid.plan, 'pro'); assert.equal(paid.monthlyAllowance, 100); const trial = freeAccount(); applyRevenueCatEntitlementState(trial, { active: true, isTrial: true, renewsAt: null }); assert.equal(trial.plan, 'pro'); assert.equal(trial.monthlyAllowance, 30); });