Files
doodle-store/apps/server/test/wechat-client.test.ts
T

166 lines
6.2 KiB
TypeScript

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',
);
});
test('微信白名单拒绝记录有限错误码和出口 IP,可按 requestId 排查且不泄漏原文', async () => {
const diagnostics: unknown[] = [];
const http = responding({
errcode: 40164,
errmsg: 'invalid ip 98.98.125.169, not in whitelist secret=private-secret',
session_key: 'private-session-key',
});
await assert.rejects(
new WechatClient(testConfig, http, (entry) =>
diagnostics.push(entry),
).exchangeCode('private-once-code', 'req_diagnostic'),
(error: unknown) =>
error instanceof ApiException &&
error.getStatus() === 503 &&
error.reason === 'AUTH_UPSTREAM_UNAVAILABLE',
);
assert.deepEqual(diagnostics, [
{
request_id: 'req_diagnostic',
failure_type: 'wechat_error',
http_status: 200,
wechat_errcode: 40164,
egress_ip: '98.98.125.169',
},
]);
const output = JSON.stringify(diagnostics);
for (const value of [
'private-secret',
'private-session-key',
'private-once-code',
testConfig.wechatMiniAppSecret,
])
assert.ok(!output.includes(value));
});
test('网络、HTTP 和响应解析诊断不携带原异常或微信响应', async () => {
const diagnostics: unknown[] = [];
for (const http of [
(async () => {
throw new Error('https://api.weixin.qq.com?secret=private-secret');
}) as WechatFetch,
responding({}, 502),
(async () => new Response('private-invalid-json')) as WechatFetch,
])
await assert.rejects(
new WechatClient(testConfig, http, (entry) =>
diagnostics.push(entry),
).exchangeCode('private-code'),
);
assert.deepEqual(diagnostics, [
{ request_id: undefined, failure_type: 'network' },
{ request_id: undefined, failure_type: 'http', http_status: 502 },
{ request_id: undefined, failure_type: 'json', http_status: 200 },
]);
assert.ok(!JSON.stringify(diagnostics).includes('private-'));
});