mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 04:13:17 +08:00
fix: remember direct recipients for connection tests
This commit is contained in:
parent
fb61c4235f
commit
3ac70a7c65
34 changed files with 397 additions and 178 deletions
|
|
@ -126,7 +126,7 @@ test('connection-test feedback uses fixed client-owned messages', () => {
|
|||
);
|
||||
assert.equal(
|
||||
connectionTestFeedback({ sent: false, code: 'test-target-unavailable' }),
|
||||
'钉钉连接检查完成。请先私聊机器人发送 /status,再重新检查。',
|
||||
'连接检查完成。机器人尚未收到可用于测试的私聊消息。',
|
||||
);
|
||||
assert.equal(
|
||||
connectionTestFeedback({ sent: false, code: 'test-message-failed' }),
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const PNG_BYTES = Buffer.from([
|
|||
0x01, 0x02, 0x03,
|
||||
]);
|
||||
|
||||
test('DingTalk remembers only a private /status session webhook for connection tests', async () => {
|
||||
test('DingTalk remembers any private inbound session webhook for connection tests', async () => {
|
||||
const privateFixture = stateFixture();
|
||||
const privateSent = [];
|
||||
const privateBridge = new DingtalkHarnessBridge({
|
||||
|
|
@ -92,11 +92,11 @@ test('DingTalk remembers only a private /status session webhook for connection t
|
|||
harness: { ensureRunning: async () => true },
|
||||
state: privateFixture.state,
|
||||
});
|
||||
await privateBridge.accept(message('status-private', '/status'));
|
||||
await privateBridge.accept(message('help-private', '/help'));
|
||||
assert.deepEqual(connectionTestTarget(privateFixture.state), {
|
||||
sessionWebhook: 'https://oapi.dingtalk.com/robot/reply?ticket=status-private',
|
||||
sessionWebhook: 'https://oapi.dingtalk.com/robot/reply?ticket=help-private',
|
||||
});
|
||||
assert.equal(privateSent.at(-1).text, '钉钉机器人与 DeepSeek Harness 连接正常。');
|
||||
assert.match(privateSent.at(-1).text, /\/help/);
|
||||
|
||||
const groupFixture = stateFixture();
|
||||
const groupBridge = new DingtalkHarnessBridge({
|
||||
|
|
@ -106,9 +106,9 @@ test('DingTalk remembers only a private /status session webhook for connection t
|
|||
harness: { ensureRunning: async () => true },
|
||||
state: groupFixture.state,
|
||||
});
|
||||
await groupBridge.accept(message('status-group', '/status', {
|
||||
await groupBridge.accept(message('help-group', '/help', {
|
||||
conversationType: '2',
|
||||
conversationId: 'group-status',
|
||||
conversationId: 'group-help',
|
||||
isInAtList: true,
|
||||
}));
|
||||
assert.equal(connectionTestTarget(groupFixture.state), null);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ function stateFixture() {
|
|||
};
|
||||
}
|
||||
|
||||
test('runtime sends a DingTalk connection test only through the remembered private status webhook', async () => {
|
||||
test('runtime sends a DingTalk connection test only through the remembered private webhook', async () => {
|
||||
const state = stateFixture();
|
||||
const sends = [];
|
||||
const client = {
|
||||
|
|
@ -70,13 +70,13 @@ test('runtime sends a DingTalk connection test only through the remembered priva
|
|||
code: 'test-target-unavailable',
|
||||
});
|
||||
rememberConnectionTestTarget(state, {
|
||||
sessionWebhook: 'https://oapi.dingtalk.com/robot/reply?ticket=status-private',
|
||||
sessionWebhook: 'https://oapi.dingtalk.com/robot/reply?ticket=inbound-private',
|
||||
});
|
||||
assert.deepEqual(await runtime.sendConnectionTest('连接测试'), { sent: true });
|
||||
assert.equal(sends.length, 1);
|
||||
assert.equal(sends[0].clientId, 'ding-client');
|
||||
assert.equal(sends[0].clientSecret, 'host-secret');
|
||||
assert.equal(sends[0].sessionWebhook, 'https://oapi.dingtalk.com/robot/reply?ticket=status-private');
|
||||
assert.equal(sends[0].sessionWebhook, 'https://oapi.dingtalk.com/robot/reply?ticket=inbound-private');
|
||||
assert.equal(sends[0].text, '连接测试');
|
||||
await runtime.stop();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
|||
import { Readable } from 'node:stream';
|
||||
import { FeishuHarnessBridge } from '../../../src/channels/feishu/bridge.mjs';
|
||||
import { DEFAULT_IMAGE_PROMPT } from '../../../src/channels/shared/image-prompt.mjs';
|
||||
import { connectionTestTarget } from '../../../src/channels/shared/connection-test.mjs';
|
||||
|
||||
const PNG_1X1 = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
||||
|
|
@ -86,6 +87,54 @@ function textClient(sendText) {
|
|||
};
|
||||
}
|
||||
|
||||
test('Feishu remembers any authorized private inbound message as a connection-test target', async () => {
|
||||
const groupFixture = stateFixture();
|
||||
const groupBridge = new FeishuHarnessBridge({
|
||||
client: textClient(async () => {}),
|
||||
channel: {},
|
||||
harness: { ensureRunning: async () => true },
|
||||
state: groupFixture.state,
|
||||
status: bridgeStatus(),
|
||||
allowedSenderOpenIds: new Set(['ou_user']),
|
||||
});
|
||||
await groupBridge.accept(event('target-group', '/help', {
|
||||
chat_type: 'group',
|
||||
chat_id: 'oc_group',
|
||||
}));
|
||||
await groupBridge.waitForIdle();
|
||||
assert.equal(connectionTestTarget(groupFixture.state), null);
|
||||
|
||||
const rejectedFixture = stateFixture();
|
||||
const rejectedBridge = new FeishuHarnessBridge({
|
||||
client: textClient(async () => {}),
|
||||
channel: {},
|
||||
harness: { ensureRunning: async () => true },
|
||||
state: rejectedFixture.state,
|
||||
status: bridgeStatus(),
|
||||
allowedSenderOpenIds: new Set(['ou_owner']),
|
||||
});
|
||||
await rejectedBridge.accept(event('target-rejected', '/help', {
|
||||
senderOpenId: 'ou_other',
|
||||
}));
|
||||
await rejectedBridge.waitForIdle();
|
||||
assert.equal(connectionTestTarget(rejectedFixture.state), null);
|
||||
|
||||
const privateFixture = stateFixture();
|
||||
const privateBridge = new FeishuHarnessBridge({
|
||||
client: textClient(async () => {}),
|
||||
channel: {},
|
||||
harness: { ensureRunning: async () => true },
|
||||
state: privateFixture.state,
|
||||
status: bridgeStatus(),
|
||||
allowedSenderOpenIds: new Set(['*']),
|
||||
});
|
||||
await privateBridge.accept(event('target-private', '/help', {
|
||||
chat_id: 'oc_private',
|
||||
}));
|
||||
await privateBridge.waitForIdle();
|
||||
assert.deepEqual(connectionTestTarget(privateFixture.state), { chatId: 'oc_private' });
|
||||
});
|
||||
|
||||
test('Feishu executes /compact for the bound Session without prompting the model', async () => {
|
||||
const fixture = stateFixture([['p2p:ou_user', 'session-compact']]);
|
||||
const sent = [];
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ test('Feishu connection check requests and displays test-message feedback', asyn
|
|||
import.meta.url,
|
||||
), 'utf8');
|
||||
assert.match(source, /FEISHU_ENDPOINTS\.reconnectBot, \{ botId, sendTest: true \}/);
|
||||
assert.match(source, /机器人尚未收到可用于测试的私聊消息/);
|
||||
assert.doesNotMatch(source, /请先私聊机器人发送 \/status/);
|
||||
|
||||
const markup = renderToStaticMarkup(React.createElement(BotCard, {
|
||||
connection: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { FeishuRuntime } from '../../../src/channels/feishu/feishu-runtime.mjs';
|
||||
import { rememberConnectionTestTarget } from '../../../src/channels/shared/connection-test.mjs';
|
||||
|
||||
class FakeClient {
|
||||
static instances = [];
|
||||
|
|
@ -86,7 +87,7 @@ test('FeishuRuntime becomes chat-ready only after Harness and Feishu are connect
|
|||
lark: fakeLark(),
|
||||
appId: 'cli_test',
|
||||
appSecret: 'secret',
|
||||
ownerOpenId: 'ou_owner',
|
||||
ownerOpenIds: ['*', 'ou_owner'],
|
||||
harness: {
|
||||
async ensureRunning(options) {
|
||||
harnessChecks += 1;
|
||||
|
|
@ -133,6 +134,40 @@ test('FeishuRuntime becomes chat-ready only after Harness and Feishu are connect
|
|||
assert.equal(harnessSignal.aborted, true);
|
||||
});
|
||||
|
||||
test('FeishuRuntime uses a remembered private target for wildcard-only manual bots', async () => {
|
||||
const state = { hasSeen: () => false };
|
||||
const runtime = new FeishuRuntime({
|
||||
lark: fakeLark(),
|
||||
appId: 'cli_manual',
|
||||
appSecret: 'secret',
|
||||
ownerOpenIds: ['*'],
|
||||
harness: { async ensureRunning() {} },
|
||||
state,
|
||||
});
|
||||
|
||||
const starting = runtime.start();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
FakeWSClient.instances[0].becomeReady();
|
||||
await starting;
|
||||
|
||||
await assert.rejects(
|
||||
runtime.sendConnectionTest('连接测试'),
|
||||
(error) => error?.code === 'test-target-unavailable',
|
||||
);
|
||||
rememberConnectionTestTarget(state, { chatId: 'oc_manual_private' });
|
||||
assert.deepEqual(await runtime.sendConnectionTest('连接测试'), { sent: true });
|
||||
assert.deepEqual(FakeClient.sent, [{
|
||||
params: { receive_id_type: 'chat_id' },
|
||||
data: {
|
||||
receive_id: 'oc_manual_private',
|
||||
msg_type: 'text',
|
||||
content: JSON.stringify({ text: '连接测试' }),
|
||||
},
|
||||
}]);
|
||||
|
||||
await runtime.stop();
|
||||
});
|
||||
|
||||
test('FeishuRuntime fails closed when the initial WebSocket handshake times out', async () => {
|
||||
const runtime = new FeishuRuntime({
|
||||
lark: fakeLark(),
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ test('QQ executes /compact for the bound Session without prompting the model', a
|
|||
assert.equal(fixture.seen.has('compact-qq'), true);
|
||||
});
|
||||
|
||||
test('QQ remembers a connection-test target only for an authorized private /status', async () => {
|
||||
test('QQ remembers any authorized private inbound as a connection-test target', async () => {
|
||||
const fixture = stateFixture();
|
||||
const sent = [];
|
||||
const bridge = new QqHarnessBridge({
|
||||
|
|
@ -270,32 +270,32 @@ test('QQ remembers a connection-test target only for an authorized private /stat
|
|||
});
|
||||
|
||||
await bridge.accept(message({
|
||||
messageId: 'status-rejected',
|
||||
messageId: 'help-rejected',
|
||||
senderId: 'other-openid',
|
||||
content: '/status',
|
||||
replyTarget: { scope: 'c2c', targetId: 'other-openid', msgId: 'status-rejected' },
|
||||
content: '/help',
|
||||
replyTarget: { scope: 'c2c', targetId: 'other-openid', msgId: 'help-rejected' },
|
||||
}));
|
||||
await bridge.accept(message({
|
||||
kind: 'group',
|
||||
rawEventType: 'GROUP_AT_MESSAGE_CREATE',
|
||||
groupOpenid: 'group-1',
|
||||
messageId: 'status-group',
|
||||
content: '/status',
|
||||
replyTarget: { scope: 'group', targetId: 'group-1', msgId: 'status-group' },
|
||||
messageId: 'help-group',
|
||||
content: '/help',
|
||||
replyTarget: { scope: 'group', targetId: 'group-1', msgId: 'help-group' },
|
||||
}));
|
||||
assert.equal(connectionTestTarget(fixture.state), null);
|
||||
|
||||
const privateTarget = {
|
||||
scope: 'c2c', targetId: 'owner-openid', msgId: 'status-private',
|
||||
scope: 'c2c', targetId: 'owner-openid', msgId: 'help-private',
|
||||
};
|
||||
await bridge.accept(message({
|
||||
messageId: 'status-private',
|
||||
content: '/status',
|
||||
messageId: 'help-private',
|
||||
content: '/help',
|
||||
replyTarget: privateTarget,
|
||||
}));
|
||||
|
||||
assert.deepEqual(connectionTestTarget(fixture.state), privateTarget);
|
||||
assert.equal(sent.filter(({ text }) => text.includes('连接正常')).length, 2);
|
||||
assert.equal(sent.length, 2);
|
||||
});
|
||||
|
||||
test('QQ private messages stream Harness snapshots and finalize once', async () => {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ test('QQ client normalizes and presents connection-test outcomes', () => {
|
|||
assert.deepEqual(unavailable.testMessage, {
|
||||
sent: false, code: 'test-target-unavailable',
|
||||
});
|
||||
assert.match(connectionTestFeedback(unavailable.testMessage), /\/status/);
|
||||
assert.equal(
|
||||
connectionTestFeedback(unavailable.testMessage),
|
||||
'连接检查完成。机器人尚未收到可用于测试的私聊消息。',
|
||||
);
|
||||
|
||||
const invalid = normalizeSnapshot({
|
||||
bots: [], testMessage: { sent: false, code: 'private-provider-error' },
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ test('QQ runtime sends a proactive connection test to the explicit owner fallbac
|
|||
await runtime.stop();
|
||||
});
|
||||
|
||||
test('QQ runtime requires /status for wildcard owners and strips the remembered reply id', async () => {
|
||||
test('QQ runtime requires a remembered private target for wildcard owners and strips the reply id', async () => {
|
||||
const bot = new FakeBot();
|
||||
const state = {};
|
||||
const runtime = new QqRuntime({
|
||||
|
|
@ -119,7 +119,7 @@ test('QQ runtime requires /status for wildcard owners and strips the remembered
|
|||
(error) => error?.code === 'test-target-unavailable',
|
||||
);
|
||||
rememberConnectionTestTarget(state, {
|
||||
scope: 'c2c', targetId: 'recent-user', msgId: 'old-status-message',
|
||||
scope: 'c2c', targetId: 'recent-user', msgId: 'old-inbound-message',
|
||||
});
|
||||
await runtime.sendConnectionTest('remembered-target');
|
||||
assert.deepEqual(bot.sent, [{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
|
|||
import test from 'node:test';
|
||||
|
||||
import { DiscordHarnessBridge } from '../../../src/channels/discord/discord-bridge.mjs';
|
||||
import { connectionTestTarget } from '../../../src/channels/shared/connection-test.mjs';
|
||||
import { TextHarnessBridge } from '../../../src/channels/shared/text-harness-bridge.mjs';
|
||||
import { SlackHarnessBridge } from '../../../src/channels/slack/slack-bridge.mjs';
|
||||
import { TelegramHarnessBridge } from '../../../src/channels/telegram/telegram-bridge.mjs';
|
||||
|
|
@ -149,6 +150,78 @@ test('all four shared text channels execute /compact outside the model prompt pa
|
|||
}
|
||||
});
|
||||
|
||||
test('Slack, Telegram, and Discord remember any valid direct message per bot', async () => {
|
||||
for (const [name, Bridge] of [
|
||||
['slack', SlackHarnessBridge],
|
||||
['telegram', TelegramHarnessBridge],
|
||||
['discord', DiscordHarnessBridge],
|
||||
]) {
|
||||
let sessionSequence = 0;
|
||||
const harness = {
|
||||
createSession: async () => `${name}-session-${++sessionSequence}`,
|
||||
ask: async () => `${name} reply`,
|
||||
};
|
||||
const first = stateFixture();
|
||||
const second = stateFixture();
|
||||
const firstSent = [];
|
||||
const secondSent = [];
|
||||
const firstBot = {
|
||||
sendText: async (target, text) => firstSent.push({ target, text }),
|
||||
};
|
||||
const secondBot = {
|
||||
sendText: async (target, text) => secondSent.push({ target, text }),
|
||||
};
|
||||
const firstBridge = new Bridge({ bot: firstBot, harness, state: first.state });
|
||||
const secondBridge = new Bridge({ bot: secondBot, harness, state: second.state });
|
||||
const firstTarget = { channelId: `${name}-private-a` };
|
||||
const secondTarget = { channelId: `${name}-private-b` };
|
||||
|
||||
await firstBridge.accept(message(`${name}-direct-a`, 'hello', {
|
||||
conversationId: `${name}-private-a`,
|
||||
replyTarget: { ...firstTarget, replyToMessageId: 'reply-a' },
|
||||
connectionTestTarget: firstTarget,
|
||||
}));
|
||||
assert.deepEqual(connectionTestTarget(first.state), firstTarget, name);
|
||||
|
||||
await firstBridge.accept(message(`${name}-group`, 'hello group', {
|
||||
kind: 'group',
|
||||
conversationId: `${name}-group`,
|
||||
addressed: true,
|
||||
replyTarget: { channelId: `${name}-group` },
|
||||
connectionTestTarget: { channelId: `${name}-group` },
|
||||
}));
|
||||
assert.deepEqual(connectionTestTarget(first.state), firstTarget, `${name} group`);
|
||||
|
||||
await firstBridge.accept(message(`${name}-direct-a`, 'duplicate', {
|
||||
conversationId: `${name}-private-replay`,
|
||||
replyTarget: { channelId: `${name}-private-replay` },
|
||||
connectionTestTarget: { channelId: `${name}-private-replay` },
|
||||
}));
|
||||
assert.deepEqual(connectionTestTarget(first.state), firstTarget, `${name} duplicate`);
|
||||
|
||||
await secondBridge.accept(message(`${name}-direct-b`, 'hello', {
|
||||
conversationId: `${name}-private-b`,
|
||||
replyTarget: { ...secondTarget, replyToMessageId: 'reply-b' },
|
||||
connectionTestTarget: secondTarget,
|
||||
}));
|
||||
assert.deepEqual(connectionTestTarget(second.state), secondTarget, `${name} second bot`);
|
||||
|
||||
const reconnectedFirstBridge = new Bridge({ bot: firstBot, harness, state: first.state });
|
||||
await reconnectedFirstBridge.sendConnectionTest(`${name} first test`);
|
||||
await secondBridge.sendConnectionTest(`${name} second test`);
|
||||
assert.deepEqual(
|
||||
firstSent.find(({ text }) => text === `${name} first test`)?.target,
|
||||
firstTarget,
|
||||
`${name} reconnect`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
secondSent.find(({ text }) => text === `${name} second test`)?.target,
|
||||
secondTarget,
|
||||
`${name} bot isolation`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('answers a multi-question interaction on the fast lane with canonical values', async () => {
|
||||
const fixture = stateFixture();
|
||||
const sent = [];
|
||||
|
|
|
|||
|
|
@ -454,26 +454,15 @@ test('Telegram bridge ignores unaddressed groups and streams direct replies', as
|
|||
assert.equal(askCount, 0);
|
||||
await bridge.accept({
|
||||
messageId: '2', senderId: 'u1', kind: 'direct', conversationId: 'u1', content: 'hello',
|
||||
addressed: true, replyTarget: {},
|
||||
addressed: true,
|
||||
replyTarget: { chatId: 88, replyToMessageId: 7 },
|
||||
connectionTestTarget: { chatId: 88 },
|
||||
});
|
||||
assert.equal(askCount, 1);
|
||||
assert.deepEqual(updates, ['正在使用搜索…', '处理中']);
|
||||
assert.deepEqual(sent, ['完成']);
|
||||
await assert.rejects(
|
||||
() => bridge.sendConnectionTest('card test'),
|
||||
(error) => error?.code === 'test-target-unavailable',
|
||||
);
|
||||
|
||||
const statusTarget = { chatId: 88, replyToMessageId: 7 };
|
||||
await bridge.accept({
|
||||
messageId: '3', senderId: 'u1', kind: 'direct', conversationId: 'u1', content: '/status',
|
||||
addressed: true, replyTarget: statusTarget, connectionTestTarget: { chatId: 88 },
|
||||
});
|
||||
await bridge.sendConnectionTest('card test');
|
||||
assert.deepEqual(sent.slice(-2), [
|
||||
'Telegram机器人与 DeepSeek Harness 连接正常。',
|
||||
'card test',
|
||||
]);
|
||||
assert.equal(sent.at(-1), 'card test');
|
||||
assert.deepEqual(sentTargets.at(-1), { chatId: 88 });
|
||||
const reconnectedBridge = new TelegramHarnessBridge({ bot, harness, state });
|
||||
await reconnectedBridge.sendConnectionTest('after reconnect');
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ function questionInteraction({
|
|||
};
|
||||
}
|
||||
|
||||
test('Enterprise WeChat remembers only a private /status chat as a connection-test target', async () => {
|
||||
test('Enterprise WeChat remembers any private inbound as a connection-test target', async () => {
|
||||
const privateState = state();
|
||||
const privateTransport = testClient();
|
||||
const privateBridge = new WecomHarnessBridge({
|
||||
|
|
@ -109,7 +109,7 @@ test('Enterprise WeChat remembers only a private /status chat as a connection-te
|
|||
harness: { ensureRunning: async () => true },
|
||||
state: privateState,
|
||||
});
|
||||
await privateBridge.accept(frame({ msgid: 'status-private', text: { content: '/status' } }));
|
||||
await privateBridge.accept(frame({ msgid: 'help-private', text: { content: '/help' } }));
|
||||
assert.deepEqual(connectionTestTarget(privateState), { chatId: 'member-1' });
|
||||
|
||||
const groupState = state();
|
||||
|
|
@ -120,10 +120,10 @@ test('Enterprise WeChat remembers only a private /status chat as a connection-te
|
|||
state: groupState,
|
||||
});
|
||||
await groupBridge.accept(frame({
|
||||
msgid: 'status-group',
|
||||
msgid: 'help-group',
|
||||
chattype: 'group',
|
||||
chatid: 'group-1',
|
||||
text: { content: '@机器人 /status' },
|
||||
text: { content: '@机器人 /help' },
|
||||
}));
|
||||
assert.equal(connectionTestTarget(groupState), null);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -134,5 +134,7 @@ test('Enterprise WeChat connection feedback is scoped to the checked bot', async
|
|||
test('Enterprise WeChat reconnect failure uses fixed translatable copy', async () => {
|
||||
const source = await readFile(CLIENT_URL, 'utf8');
|
||||
assert.match(source, /'连接检查失败,请稍后重试。'/);
|
||||
assert.match(source, /'连接检查完成。机器人尚未收到可用于测试的私聊消息。'/);
|
||||
assert.doesNotMatch(source, /请先私聊机器人发送 \/status/);
|
||||
assert.doesNotMatch(source, /连接检查失败:\$\{presentError\(error\)\.message\}/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class FakeClient extends EventEmitter {
|
|||
async sendMessage(chatId, body) { this.sent.push({ chatId, body }); }
|
||||
}
|
||||
|
||||
test('Enterprise WeChat runtime sends a connection test only to the remembered private status target', async () => {
|
||||
test('Enterprise WeChat runtime sends a connection test only to the remembered private target', async () => {
|
||||
const client = new FakeClient();
|
||||
const state = {};
|
||||
const runtime = new WecomRuntime({
|
||||
|
|
|
|||
|
|
@ -127,5 +127,7 @@ test('Weixin connection feedback is scoped to the checked bot', async (t) => {
|
|||
test('Weixin reconnect failure uses fixed translatable copy', async () => {
|
||||
const source = await readFile(CLIENT_URL, 'utf8');
|
||||
assert.match(source, /'连接检查失败,请稍后重试。'/);
|
||||
assert.match(source, /'连接检查完成。机器人尚未收到可用于测试的私聊消息。'/);
|
||||
assert.doesNotMatch(source, /请先私聊机器人发送 \/status/);
|
||||
assert.doesNotMatch(source, /连接检查失败:\$\{presentError\(error\)\.message\}/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const PNG_BYTES = Buffer.from([
|
|||
0x01, 0x02, 0x03,
|
||||
]);
|
||||
|
||||
test('Weixin remembers only the authorized owner /status as a connection-test target', async () => {
|
||||
test('Weixin remembers any authorized private inbound as a connection-test target', async () => {
|
||||
const fixture = stateFixture();
|
||||
const sent = [];
|
||||
const bridge = new WeixinHarnessBridge({
|
||||
|
|
@ -81,20 +81,20 @@ test('Weixin remembers only the authorized owner /status as a connection-test ta
|
|||
state: fixture.state,
|
||||
});
|
||||
|
||||
await bridge.accept(message('status-owner', '/status'));
|
||||
await bridge.accept(message('help-owner', '/help'));
|
||||
assert.deepEqual(connectionTestTarget(fixture.state), { toUserId: 'owner-user' });
|
||||
assert.equal(sent.at(-1).text, '微信与 DeepSeek Harness 连接正常。');
|
||||
assert.match(sent.at(-1).text, /\/help/);
|
||||
|
||||
const rejectedFixture = stateFixture();
|
||||
const rejectedBridge = new WeixinHarnessBridge({
|
||||
api: { sendText: async () => assert.fail('unauthorized status must not be answered') },
|
||||
api: { sendText: async () => assert.fail('unauthorized message must not be answered') },
|
||||
baseUrl: 'https://ilinkai.weixin.qq.com/',
|
||||
token: 'host-token',
|
||||
ownerUserId: 'owner-user',
|
||||
harness: { ensureRunning: async () => true },
|
||||
state: rejectedFixture.state,
|
||||
});
|
||||
await rejectedBridge.accept(message('status-other', '/status', { from_user_id: 'other-user' }));
|
||||
await rejectedBridge.accept(message('help-other', '/help', { from_user_id: 'other-user' }));
|
||||
assert.equal(connectionTestTarget(rejectedFixture.state), null);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -532,6 +532,50 @@ test('connection check requests a test message and shows its delivery result', a
|
|||
await act(async () => { renderer.unmount(); });
|
||||
});
|
||||
|
||||
test('shared token target-unavailable feedback asks for any direct message', async (t) => {
|
||||
const previousWindow = globalThis.window;
|
||||
globalThis.window = { setInterval() { return 1; }, clearInterval() {} };
|
||||
setImTranslator((key) => en[key] ?? key);
|
||||
t.after(() => {
|
||||
setImTranslator(null);
|
||||
if (previousWindow === undefined) delete globalThis.window;
|
||||
else globalThis.window = previousWindow;
|
||||
});
|
||||
const snapshot = discordSnapshot('/workspace/current');
|
||||
const rpcCall = async (endpoint) => {
|
||||
if (endpoint === 'connection.status') return { ok: true, value: snapshot };
|
||||
if (endpoint === 'bot.reconnect') {
|
||||
return {
|
||||
ok: true,
|
||||
value: {
|
||||
...snapshot,
|
||||
testMessage: { sent: false, code: 'test-target-unavailable' },
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
||||
};
|
||||
|
||||
let renderer;
|
||||
await act(async () => {
|
||||
renderer = create(React.createElement(DiscordSettingsTab, { rpcCall }));
|
||||
await flushMicrotasks();
|
||||
});
|
||||
const card = renderer.root.findByProps({ 'data-bot-id': 'discord_test' });
|
||||
await act(async () => {
|
||||
buttonNamed(card, 'Check connection').props.onClick();
|
||||
await flushMicrotasks();
|
||||
});
|
||||
|
||||
const notice = textOf(renderer.root.findByProps({ role: 'status' }));
|
||||
assert.equal(
|
||||
notice,
|
||||
'Connection check completed. The bot has not received a direct message it can use for testing.',
|
||||
);
|
||||
assert.doesNotMatch(notice, /\/status|[\p{Script=Han}]/u);
|
||||
await act(async () => { renderer.unmount(); });
|
||||
});
|
||||
|
||||
test('shared token connection failures render a fixed English-safe notice', async (t) => {
|
||||
const previousWindow = globalThis.window;
|
||||
globalThis.window = { setInterval() { return 1; }, clearInterval() {} };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue