Files
memind/billing-recharge.test.mjs
T

116 lines
3.7 KiB
JavaScript
Raw Normal View History

import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildInsufficientBalancePayload,
createRechargeService,
isAllowedRechargeAmount,
loadRechargeConfig,
} from './billing-recharge.mjs';
test('loadRechargeConfig parses tier list', () => {
const previous = process.env.H5_RECHARGE_TIERS_CENTS;
process.env.H5_RECHARGE_TIERS_CENTS = '500,1000,3000';
try {
const config = loadRechargeConfig();
assert.deepEqual(config.tiersCents, [500, 1000, 3000]);
assert.equal(config.minRechargeCents, 500);
} finally {
if (previous === undefined) delete process.env.H5_RECHARGE_TIERS_CENTS;
else process.env.H5_RECHARGE_TIERS_CENTS = previous;
}
});
test('isAllowedRechargeAmount only accepts configured tiers', () => {
const config = { tiersCents: [500, 1000, 3000], minRechargeCents: 500 };
assert.equal(isAllowedRechargeAmount(1000, config), true);
assert.equal(isAllowedRechargeAmount(2000, config), false);
});
test('buildInsufficientBalancePayload includes recharge hints', () => {
const config = { tiersCents: [500, 1000], minRechargeCents: 500 };
const payload = buildInsufficientBalancePayload(0, config);
assert.equal(payload.code, 'INSUFFICIENT_BALANCE');
assert.equal(payload.balanceCents, 0);
assert.deepEqual(payload.suggestedTiers, [500, 1000]);
});
test('fulfillOrder routes template_purchase to template catalog service', async () => {
const orderRow = {
id: 'order-tpl-1',
user_id: 'user-1',
amount_cents: 990,
channel: 'wechat',
status: 'pending',
pay_mode: 'native',
out_trade_no: 'TPTEST001',
provider_txn: null,
paid_at: null,
expire_at: Date.now() + 60_000,
created_at: Date.now(),
code_url: null,
h5_url: null,
order_purpose: 'template_purchase',
order_purpose_ref: 'page-template-travel',
};
const pool = {
async getConnection() {
const conn = {
async beginTransaction() {},
async commit() {},
async rollback() {},
async release() {},
async query(sql, params = []) {
const text = String(sql);
if (text.includes('FROM h5_payment_orders WHERE id = ? FOR UPDATE')) {
return [[orderRow], []];
}
if (text.includes('UPDATE h5_payment_orders') && text.includes("status = 'paid'")) {
orderRow.status = 'paid';
return [{ affectedRows: 1 }, []];
}
throw new Error(`Unhandled SQL: ${text.slice(0, 100)}`);
},
};
return conn;
},
};
let templateFulfillArgs = null;
const templateCatalogService = {
async fulfillWechatPurchaseLocked(args) {
templateFulfillArgs = args;
return {
ok: true,
skillName: 'page-template-travel',
grantedSkills: ['page-template-travel'],
balanceCents: 1200,
};
},
};
const userAuth = {
async getUserById(userId) {
return { id: userId, balance_cents: 1200 };
},
async recharge() {
throw new Error('recharge should not run for template_purchase');
},
};
const service = createRechargeService(pool, { userAuth, templateCatalogService });
const result = await service.fulfillOrder(
{
id: orderRow.id,
userId: orderRow.user_id,
amountCents: orderRow.amount_cents,
status: 'pending',
orderPurpose: 'template_purchase',
orderPurposeRef: 'page-template-travel',
},
{ trade_state: 'SUCCESS', amount: { total: 990 }, transaction_id: 'wx-txn-1' },
);
assert.equal(result.ok, true);
assert.equal(result.skillName, 'page-template-travel');
assert.deepEqual(result.grantedSkills, ['page-template-travel']);
assert.equal(result.balanceCents, 1200);
assert.ok(templateFulfillArgs);
assert.equal(orderRow.status, 'paid');
});