import assert from 'node:assert/strict'; import { test } from 'node:test'; import { WechatClient, type WechatFetch } from '../src/auth/wechat-client'; import { ApiException } from '../src/common/api-exception'; import { testConfig } from './test-config'; const responding = (payload: unknown, status = 200): WechatFetch => async () => new Response(JSON.stringify(payload), { status, headers: { 'Content-Type': 'application/json' }, }); test('微信兑换只向固定地址发送服务端凭据,忽略 session_key,不重试一次性 code', async () => { let calls = 0; const http: WechatFetch = async (input, init) => { calls += 1; const url = new URL(String(input)); assert.equal(url.origin, 'https://api.weixin.qq.com'); assert.equal(url.pathname, '/sns/jscode2session'); assert.equal(url.searchParams.get('appid'), testConfig.wechatMiniAppId); assert.equal( url.searchParams.get('secret'), testConfig.wechatMiniAppSecret, ); assert.equal(url.searchParams.get('js_code'), 'once-code'); assert.equal(init?.redirect, 'error'); assert.ok(init?.signal); return responding({ openid: 'OpenIdA', unionid: 'UnionA', session_key: 'private-session-key', })(input, init); }; assert.deepEqual( await new WechatClient(testConfig, http).exchangeCode('once-code'), { openid: 'OpenIdA', unionid: 'UnionA' }, ); assert.equal(calls, 1); for (const unionid of [undefined, null, '']) { const result = await new WechatClient( testConfig, responding({ openid: 'A', unionid, errcode: 0 }), ).exchangeCode('code'); assert.equal(result.unionid, null); } }); test('微信错误、畸形响应和网络失败映射为有限安全错误,不传播原始消息', async () => { for (const [payload, status, reason] of [ [{ errcode: 40029 }, 400, 'WECHAT_CODE_INVALID'], [{ errcode: 40163 }, 400, 'WECHAT_CODE_INVALID'], [{ errcode: 40226 }, 403, 'WECHAT_LOGIN_BLOCKED'], [{ errcode: 45011 }, 429, 'RATE_LIMITED'], [{ errcode: -1 }, 503, 'AUTH_UPSTREAM_UNAVAILABLE'], [ { errcode: 40013, errmsg: 'private-secret' }, 503, 'AUTH_UPSTREAM_UNAVAILABLE', ], [{ openid: '' }, 503, 'AUTH_UPSTREAM_UNAVAILABLE'], [{ openid: 'A', unionid: 123 }, 503, 'AUTH_UPSTREAM_UNAVAILABLE'], [{ openid: 'x'.repeat(129) }, 503, 'AUTH_UPSTREAM_UNAVAILABLE'], [{ openid: ' A' }, 503, 'AUTH_UPSTREAM_UNAVAILABLE'], [null, 503, 'AUTH_UPSTREAM_UNAVAILABLE'], [[], 503, 'AUTH_UPSTREAM_UNAVAILABLE'], ] as const) { await assert.rejects( new WechatClient(testConfig, responding(payload)).exchangeCode( 'code', ), (error: unknown) => { assert.ok(error instanceof ApiException); assert.equal(error.getStatus(), status); assert.equal(error.reason, reason); assert.ok( !JSON.stringify(error.getResponse()).includes( 'private-secret', ), ); return true; }, ); } let calls = 0; const failed: WechatFetch = async () => { calls += 1; throw new Error('https://api.weixin.qq.com?secret=private-secret'); }; await assert.rejects( new WechatClient(testConfig, failed).exchangeCode('code'), (error: unknown) => error instanceof ApiException && error.reason === 'AUTH_UPSTREAM_UNAVAILABLE', ); assert.equal(calls, 1); for (const http of [ responding({}, 502), (async () => new Response('not-json')) as WechatFetch, ]) await assert.rejects( new WechatClient(testConfig, http).exchangeCode('code'), (error: unknown) => error instanceof ApiException && error.reason === 'AUTH_UPSTREAM_UNAVAILABLE', ); });