dsh-im-ops/test/channels/wecom/config-and-qr.test.mjs
2026-08-15 20:53:35 +08:00

68 lines
2.9 KiB
JavaScript

import assert from 'node:assert/strict';
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import {
deriveWecomBotIdentity,
WecomConfigStore,
} from '../../../src/channels/wecom/config-store.mjs';
import { WecomQrAuth } from '../../../src/channels/wecom/qr-auth.mjs';
test('Enterprise WeChat config stores only non-secret bot identity with mode 0600', async (t) => {
const root = await mkdtemp(join(tmpdir(), 'dsh-im-wecom-config-'));
t.after(() => rm(root, { recursive: true, force: true }));
const path = join(root, 'config.json');
const identity = deriveWecomBotIdentity('bot-enterprise-1');
const store = await new WecomConfigStore(path).load();
await store.save({
...identity,
remoteBotId: 'bot-enterprise-1',
createdAt: '2026-08-15T00:00:00.000Z',
});
const document = await readFile(path, 'utf8');
assert.match(document, /bot-enterprise-1/);
assert.doesNotMatch(document, /private-secret|"secret"/);
assert.equal((await stat(path)).mode & 0o777, 0o600);
assert.equal(store.get(identity.botId).remoteBotId, 'bot-enterprise-1');
});
test('Enterprise WeChat QR auth uses fixed official endpoints and returns credentials only after success', async () => {
const requests = [];
const replies = [
{ data: { scode: 'opaque-scode', auth_url: 'https://work.weixin.qq.com/ai/qc/auth?ticket=opaque' } },
{ data: { status: 'init' } },
{ data: { status: 'success', bot_info: { botid: 'bot-enterprise-1', secret: 'private-secret' } } },
];
const auth = new WecomQrAuth({
clock: () => 1_000,
fetch: async (url, options) => {
requests.push({ url: String(url), options });
return { ok: true, status: 200, json: async () => replies.shift() };
},
});
const started = await auth.start();
assert.equal(started.verificationUrl, 'https://work.weixin.qq.com/ai/qc/auth?ticket=opaque');
assert.equal(started.expiresAt, 301_000);
assert.equal(await auth.poll({ scode: started.scode }).then((value) => value.status), 'waiting');
const completed = await auth.poll({ scode: started.scode });
assert.deepEqual(completed, {
status: 'success', remoteBotId: 'bot-enterprise-1', secret: 'private-secret',
});
assert.match(requests[0].url, /^https:\/\/work\.weixin\.qq\.com\/ai\/qc\/generate\?/);
assert.match(requests[0].url, /source=deepseek-harness/);
assert.match(requests[1].url, /^https:\/\/work\.weixin\.qq\.com\/ai\/qc\/query_result\?/);
assert.equal(requests.every(({ options }) => options.redirect === 'error'), true);
});
test('Enterprise WeChat QR auth rejects an authorization URL outside the official host', async () => {
const auth = new WecomQrAuth({
fetch: async () => ({
ok: true,
status: 200,
json: async () => ({ data: { scode: 'opaque', auth_url: 'https://example.com/steal' } }),
}),
});
await assert.rejects(() => auth.start(), /invalid data/);
});