test(server): add node --test harness with failing free-tier billing tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -828,4 +828,10 @@ module.exports = {
|
|||||||
syncRevenueCatCustomerInfo,
|
syncRevenueCatCustomerInfo,
|
||||||
syncRevenueCatWebhookEvent,
|
syncRevenueCatWebhookEvent,
|
||||||
storeEndpointResponse,
|
storeEndpointResponse,
|
||||||
|
// exported for tests
|
||||||
|
buildDefaultAccount,
|
||||||
|
alignAccountToCurrentCycle,
|
||||||
|
getAvailableCredits,
|
||||||
|
consumeCredits,
|
||||||
|
buildBillingSummary,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"rebuild:batches": "node scripts/rebuild-from-batches.js",
|
"rebuild:batches": "node scripts/rebuild-from-batches.js",
|
||||||
"diagnostics": "node scripts/plant-diagnostics.js",
|
"diagnostics": "node scripts/plant-diagnostics.js",
|
||||||
"images:download": "node scripts/download-plant-images.js",
|
"images:download": "node scripts/download-plant-images.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "node --test test/"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
74
server/test/billing.test.js
Normal file
74
server/test/billing.test.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const {
|
||||||
|
buildDefaultAccount,
|
||||||
|
alignAccountToCurrentCycle,
|
||||||
|
getAvailableCredits,
|
||||||
|
consumeCredits,
|
||||||
|
getMonthlyAllowanceForPlan,
|
||||||
|
} = 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);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user