dsh-im-ops/test/channels/wecom/controller-and-rpc.test.mjs
2026-08-30 11:24:53 +08:00

208 lines
8.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict';
import test from 'node:test';
import { WecomController } from '../../../src/channels/wecom/wecom-controller.mjs';
import {
createWecomRpcHandler,
WECOM_ENDPOINTS,
} from '../../../plugin-src/host/channels/wecom/rpc.mjs';
test('Enterprise WeChat QR success stores Secret off-config and starts its runtime', async () => {
const values = new Map();
const configs = [];
let runtimeArgs;
const controller = new WecomController({
qrAuth: {
start: async () => ({
scode: 'host-only-code',
verificationUrl: 'https://work.weixin.qq.com/ai/qc/auth?ticket=opaque',
expiresAt: Date.now() + 10_000,
pollIntervalMs: 500,
}),
poll: async () => ({ status: 'success', remoteBotId: 'remote-bot', secret: 'private-secret' }),
},
credentials: {
resolve: async (ref) => values.has(ref) ? { value: values.get(ref) } : undefined,
set: async (ref, value) => values.set(ref, value),
unset: async (ref) => values.delete(ref),
},
configStore: {
list: () => [...configs],
get: (id) => configs.find((value) => value.botId === id) ?? null,
getByRemoteBotId: (id) => configs.find((value) => value.remoteBotId === id) ?? null,
save: async (value) => { configs.splice(0, configs.length, value); },
remove: async () => null,
},
createRuntime: async (args) => {
runtimeArgs = args;
return {
status: { ready: true, wecomConnectionState: 'connected', harnessReachable: true },
start: async () => {},
stop: async () => {},
};
},
});
const started = await controller.startProvisioning();
const completed = await controller.registrationStatus(started.attemptId);
assert.equal(completed.status, 'connected');
const status = controller.status();
assert.equal(status.bots[0].connected, true);
assert.equal(values.get(configs[0].secretRef), 'private-secret');
assert.equal(runtimeArgs.secret, 'private-secret');
assert.doesNotMatch(JSON.stringify(status), /private-secret|secretRef|remote-bot|host-only-code/);
});
test('Enterprise WeChat Bot ID and Secret binding stores credentials and starts its runtime', async () => {
const values = new Map();
const configs = [];
let runtimeArgs;
let connectionTestText;
const proactiveSends = [];
const controller = new WecomController({
qrAuth: { start: async () => ({}), poll: async () => ({ status: 'waiting' }) },
credentials: {
resolve: async (ref) => values.has(ref) ? { value: values.get(ref) } : undefined,
set: async (ref, value) => values.set(ref, value),
unset: async (ref) => values.delete(ref),
},
configStore: {
list: () => [...configs],
get: (id) => configs.find((value) => value.botId === id) ?? null,
getByRemoteBotId: (id) => configs.find((value) => value.remoteBotId === id) ?? null,
save: async (value) => { configs.splice(0, configs.length, value); },
remove: async () => null,
},
createRuntime: async (args) => {
runtimeArgs = args;
return {
status: { ready: true, wecomConnectionState: 'connected', harnessReachable: true },
start: async () => {}, stop: async () => {},
sendConnectionTest: async (text) => { connectionTestText = text; },
sendProactiveText: async (...args) => {
proactiveSends.push(args);
return { sent: true };
},
};
},
});
const status = await controller.bindCredentials({ botId: 'remote-manual', secret: 'manual-secret' });
assert.equal(status.totals.connected, 1);
assert.equal(values.get(configs[0].secretRef), 'manual-secret');
assert.equal(runtimeArgs.secret, 'manual-secret');
assert.doesNotMatch(JSON.stringify(status), /manual-secret|remote-manual|secretRef/);
await controller.sendConnectionTest(status.bots[0].botId);
assert.match(connectionTestText, /DeepSeek Harness 连接测试成功/);
assert.match(connectionTestText, /企业微信机器人(remote••••nual)/);
const target = { kind: 'group', route: { chatId: 'group-target' } };
assert.deepEqual(await controller.sendProactiveText(status.bots[0].botId, target, '主动投递'), {
sent: true,
});
assert.deepEqual(proactiveSends, [[target, '主动投递', {}]]);
await controller.close();
});
test('Enterprise WeChat RPC encodes the QR on Host and strips every authorization field', async () => {
const handler = createWecomRpcHandler({
status: () => ({ bots: [], totals: { configured: 0, connected: 0 } }),
startProvisioning: async () => ({
attemptId: 'attempt_1',
status: 'pending',
verificationUrl: 'https://work.weixin.qq.com/ai/qc/auth?ticket=opaque',
scode: 'never-public',
secret: 'never-public',
remoteBotId: 'never-public',
expiresAt: Date.now() + 1_000,
}),
registrationStatus: async () => null,
cancelProvisioning: async () => ({}),
bindCredentials: async () => ({ bots: [], totals: { configured: 0, connected: 0 } }),
reconnectBot: async () => ({}),
sendConnectionTest: async () => ({ sent: true }),
deleteBot: async () => ({}),
}, { encodeQr: async () => 'data:image/png;base64,YWJjZA==' });
const result = await handler(WECOM_ENDPOINTS.beginProvisioning, { locale: 'zh-CN' });
assert.equal(result.ok, true);
assert.equal(result.value.qrCodeDataUrl, 'data:image/png;base64,YWJjZA==');
assert.doesNotMatch(JSON.stringify(result), /work\.weixin|never-public|remoteBotId|scode|secret/);
});
test('Enterprise WeChat credential RPC accepts official Bot ID fields and redacts Secret', async () => {
let received;
const handler = createWecomRpcHandler({
status: () => ({ bots: [], totals: { configured: 0, connected: 0 } }),
startProvisioning: async () => ({}), registrationStatus: async () => null,
cancelProvisioning: async () => ({}), reconnectBot: async () => ({}),
sendConnectionTest: async () => ({ sent: true }), deleteBot: async () => ({}),
bindCredentials: async (payload) => {
received = payload;
return { bots: [], totals: { configured: 0, connected: 0 }, secret: payload.secret };
},
});
const result = await handler(WECOM_ENDPOINTS.bindCredentials, {
botId: 'remote-manual', secret: 'manual-secret',
});
assert.equal(result.ok, true);
assert.deepEqual(received, { botId: 'remote-manual', secret: 'manual-secret' });
assert.doesNotMatch(JSON.stringify(result), /manual-secret|"secret"/);
assert.equal((await handler(WECOM_ENDPOINTS.bindCredentials, { botId: 'remote-manual' })).ok, false);
});
test('Enterprise WeChat reconnect optionally sends a test message without changing connection success', async () => {
const sent = [];
const base = {
status: () => ({ bots: [] }),
startProvisioning: async () => ({}), registrationStatus: async () => null,
cancelProvisioning: async () => ({}), bindCredentials: async () => ({}),
deleteBot: async () => ({}),
};
const connected = {
bots: [{ botId: 'wecom_bot', connected: true }],
totals: { configured: 1, connected: 1 },
};
const handler = createWecomRpcHandler({
...base,
reconnectBot: async () => connected,
sendConnectionTest: async (botId) => { sent.push(botId); },
});
const success = await handler(WECOM_ENDPOINTS.reconnectBot, {
botId: 'wecom_bot', sendTest: true,
});
assert.equal(success.ok, true);
assert.deepEqual(success.value.testMessage, { sent: true });
assert.deepEqual(sent, ['wecom_bot']);
const failedSend = await createWecomRpcHandler({
...base,
reconnectBot: async () => connected,
sendConnectionTest: async () => { throw new Error('provider rejected'); },
})(WECOM_ENDPOINTS.reconnectBot, { botId: 'wecom_bot', sendTest: true });
assert.equal(failedSend.ok, true);
assert.deepEqual(failedSend.value.testMessage, { sent: false, code: 'test-message-failed' });
let offlineSendCalled = false;
const offline = await createWecomRpcHandler({
...base,
reconnectBot: async () => ({
bots: [{ botId: 'wecom_bot', connected: false }],
totals: { configured: 1, connected: 0 },
}),
sendConnectionTest: async () => { offlineSendCalled = true; },
})(WECOM_ENDPOINTS.reconnectBot, { botId: 'wecom_bot', sendTest: true });
assert.equal(offline.ok, true);
assert.deepEqual(offline.value.testMessage, {
sent: false, code: 'test-target-unavailable',
});
assert.equal(offlineSendCalled, false);
const missingMethod = await createWecomRpcHandler({
...base,
reconnectBot: async () => connected,
})(WECOM_ENDPOINTS.reconnectBot, { botId: 'wecom_bot', sendTest: true });
assert.equal(missingMethod.ok, true);
assert.deepEqual(missingMethod.value.testMessage, {
sent: false, code: 'test-target-unavailable',
});
assert.equal((await handler(WECOM_ENDPOINTS.reconnectBot, {
botId: 'wecom_bot', sendTest: false,
})).ok, false);
});