'use strict'; const assert = require('node:assert/strict'); const { queryBilling } = require('../mcp/src/core/billing-client'); const { requestPaymentQr } = require('../mcp/src/core/recharge-client'); async function main() { const originalFetch = global.fetch; let billingCalls = 0; global.fetch = async (url, options) => { if (/\/api\/fmode\/billing\/query$/.test(String(url))) { billingCalls += 1; assert.equal(options.method, 'POST'); assert.match(options.headers.Authorization, /^Bearer sk-test$/); return new Response(JSON.stringify({ code: 200, data: { balance: { availableQuota: 1000 }, // NewAPI-key authentication intentionally omits this Session-bound // link. The client must build it locally from the current Session. balanceUrl: null, estimate: { quota: 2000, amountCny: 1, shortfallQuota: 1000, suggestedRechargeCny: 1.1, sufficient: false, operations: [] }, catalog: [] } }), { status: 200, headers: { 'content-type': 'application/json' } }); } if (/\/api\/fmode\/recharge\/order$/.test(String(url))) { assert.equal(options.method, 'POST'); assert.match(String(options.body || ''), /sessionToken/); return new Response(JSON.stringify({ code: 200, data: { tradeNo: 'trade-query' } }), { status: 200 }); } assert.match(String(url), /\/parse\/functions\/pay_code2$/); assert.equal(options.method, 'POST'); assert.match(options.headers['X-Parse-Session-Token'], /^r:/); return new Response(JSON.stringify({ result: { code_url: ['weixin://pay/query-test'], nonce_str: 'nonce-query' } }), { status: 200 }); }; const result = await queryBilling({ newapiToken: 'sk-test', baseUrl: 'https://billing.example.test', operations: [{ model: 'voc-social', count: 1 }] }); assert.equal(result.status, 'ok'); if (result.recharge) { assert.equal(result.recharge.paymentMode, 'balance_url_and_payment_request'); assert.equal(Object.prototype.hasOwnProperty.call(result.recharge.paymentRequest, 'headers'), false); assert.doesNotMatch(JSON.stringify(result), /r:[A-Za-z0-9_-]{8,}/); } const fallbackResult = await queryBilling({ newapiToken: 'sk-test', sessionToken: 'r:session-test', baseUrl: 'https://billing.example.test', operations: [{ model: 'voc-social', count: 1 }] }); assert.equal(fallbackResult.status, 'ok'); assert.equal(fallbackResult.recharge.paymentMode, 'balance_url_and_payment_request'); assert.match(fallbackResult.recharge.balanceUrl, /balance\/\?token=r%3Asession-test/); assert.match(fallbackResult.assistantMessage, /免登录余额页:https:\/\/app\.fmode\.cn\/dev\/studio\/balance/); assert.doesNotMatch(JSON.stringify(fallbackResult.recharge.paymentRequest), /r:[A-Za-z0-9_-]{8,}/); const qrResult = await queryBilling({ newapiToken: 'sk-test', sessionToken: 'r:session-test', baseUrl: 'https://billing.example.test', createPaymentQr: true, operations: [{ model: 'voc-social', count: 1 }] }); assert.equal(qrResult.status, 'ok'); assert.equal(qrResult.recharge.paymentMode, 'qr_and_balance_url'); assert.equal(qrResult.recharge.qrCodeUrl, 'weixin://pay/query-test'); assert.equal(qrResult.recharge.paymentRequest.nonceStr, 'nonce-query'); assert.doesNotMatch(JSON.stringify(qrResult.recharge.paymentRequest), /r:[A-Za-z0-9_-]{8,}/); const qr = await requestPaymentQr({ amount: 2, sessionToken: 'r:session-test', fetchImpl: async (_url, options) => { if (String(_url).endsWith('/api/fmode/recharge/order')) { assert.equal(options.method, 'POST'); return new Response(JSON.stringify({ code: 200, data: { tradeNo: 'trade-qr' } }), { status: 200 }); } assert.match(options.headers['X-Parse-Session-Token'], /^r:/); assert.equal(options.headers['X-Parse-Application-Id'], 'ncloudmaster'); return new Response(JSON.stringify({ result: { code_url: ['weixin://pay/test'], nonce_str: 'nonce' } }), { status: 200 }); } }); assert.equal(qr.qrCodeUrl, 'weixin://pay/test'); assert.equal(qr.amountCny, 2); global.fetch = originalFetch; console.log('billing-client smoke passed'); } main().catch(error => { console.error(error); process.exitCode = 1; });