release: prepare 0629001 portal updates

This commit is contained in:
john
2026-06-29 22:20:04 +08:00
parent 18ea4f82fd
commit a40e340a41
84 changed files with 17516 additions and 2475 deletions
+89 -2
View File
@@ -3,6 +3,7 @@ import { describe, it } from 'node:test';
import {
PLAN_CATALOG,
createSubscriptionService,
ensurePlanCatalogSchema,
getPlanDef,
tokensToCallsApprox,
} from './billing-subscription.mjs';
@@ -20,6 +21,12 @@ describe('PLAN_CATALOG', () => {
assert.equal(PLAN_CATALOG.free.priceCents, 0);
});
it('uses expanded token allowance defaults', () => {
assert.equal(PLAN_CATALOG.free.periodTokens, 450_000);
assert.equal(PLAN_CATALOG.lite.periodTokens, 3_600_000);
assert.equal(PLAN_CATALOG.standard.periodTokens, 13_500_000);
});
it('pro plan has unlimited tokens (0)', () => {
assert.equal(PLAN_CATALOG.pro.periodTokens, 0);
});
@@ -47,8 +54,31 @@ describe('tokensToCallsApprox', () => {
});
it('calculates approximate calls', () => {
assert.equal(tokensToCallsApprox(150_000), 50); // free ~50 calls
assert.equal(tokensToCallsApprox(1_200_000), 400); // lite ~400 calls
assert.equal(tokensToCallsApprox(450_000), 150); // free ~150 calls
assert.equal(tokensToCallsApprox(3_600_000), 1200); // lite ~1200 calls
});
});
describe('ensurePlanCatalogSchema', () => {
it('expands legacy default token allowances without overwriting custom values', async () => {
const queries = [];
const pool = {
async query(sql, params = []) {
queries.push({ sql, params });
if (sql.includes('COUNT(*) AS cnt')) return [[{ cnt: 1 }]];
return [{ affectedRows: 1 }];
},
};
await ensurePlanCatalogSchema(pool);
const planUpdates = queries.filter(({ sql }) => sql.includes('UPDATE h5_plan_catalog'));
const subUpdates = queries.filter(({ sql }) => sql.includes('UPDATE h5_subscriptions'));
assert.ok(planUpdates.some(({ params }) => params[0] === 450_000 && params[2] === 'free' && params[3] === 150_000));
assert.ok(planUpdates.some(({ params }) => params[0] === 3_600_000 && params[2] === 'lite' && params[3] === 1_200_000));
assert.ok(planUpdates.some(({ params }) => params[0] === 13_500_000 && params[2] === 'standard' && params[3] === 4_500_000));
assert.equal(subUpdates.length, 3);
});
});
@@ -210,4 +240,61 @@ describe('createSubscriptionService', () => {
assert.match(result.message, /未知套餐/);
});
});
describe('processAutoRenewals', () => {
it('uses DB-backed plan definitions for renewed subscriptions', async () => {
const expiredSub = makeSubRow({
plan_type: 'lite',
auto_renew: 1,
expires_at: Date.now() - 1000,
balance_cents: 9999,
});
const conn = {
queries: [],
async query(sql, params) {
this.queries.push({ sql, params });
if (sql.includes('SELECT balance_cents')) return [[{ balance_cents: 9999 }]];
return [{ affectedRows: 1 }];
},
async beginTransaction() {},
async commit() {},
async rollback() {},
release() {},
};
const pool = {
async query(sql) {
if (sql.includes('FROM h5_subscriptions s')) return [[expiredSub]];
return [[]];
},
async getConnection() { return conn; },
};
const svc = createSubscriptionService(pool, {
getPlanAsync: async (planType) => planType === 'lite'
? {
name: '轻量版',
priceCents: 990,
periodTokens: 9_999_000,
periodImages: 50,
modelTier: 'basic',
overageRate: 0.45,
periodDays: 30,
}
: null,
});
const originalLog = console.log;
console.log = () => {};
try {
const result = await svc.processAutoRenewals();
assert.equal(result.renewed, 1);
} finally {
console.log = originalLog;
}
const insert = conn.queries.find(({ sql }) => sql.includes('INSERT INTO h5_subscriptions'));
assert.ok(insert);
assert.equal(insert.params[3], 9_999_000);
assert.equal(insert.params[8], 0.45);
});
});
});