mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 00:33:20 +08:00
fix: surface actionable channel message errors
This commit is contained in:
parent
b42fc411c2
commit
7cbed3aefb
59 changed files with 2405 additions and 607 deletions
149
test/client-last-message-error.test.mjs
Normal file
149
test/client-last-message-error.test.mjs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import React from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
|
||||
import { normalizeLastMessageError } from '../plugin-src/client/last-message-error.js';
|
||||
import { normalizeBotsSnapshot as normalizeFeishu } from '../plugin-src/client/channels/feishu/api.js';
|
||||
import { normalizeSnapshot as normalizeDingtalk } from '../plugin-src/client/channels/dingtalk/api.js';
|
||||
import { normalizeSnapshot as normalizeWeixin } from '../plugin-src/client/channels/weixin/api.js';
|
||||
import { normalizeSnapshot as normalizeWecom } from '../plugin-src/client/channels/wecom/api.js';
|
||||
import { normalizeSnapshot as normalizeQq } from '../plugin-src/client/channels/qq/api.js';
|
||||
import { normalizeSnapshot as normalizeSlack } from '../plugin-src/client/channels/slack/api.js';
|
||||
import { normalizeSnapshot as normalizeTelegram } from '../plugin-src/client/channels/telegram/api.js';
|
||||
import { normalizeSnapshot as normalizeDiscord } from '../plugin-src/client/channels/discord/api.js';
|
||||
import { normalizeSnapshot as normalizeWhatsapp } from '../plugin-src/client/channels/whatsapp/api.js';
|
||||
import { BotCard as FeishuBotCard } from '../plugin-src/client/channels/feishu/index.js';
|
||||
import { AccountCard as DingtalkAccountCard } from '../plugin-src/client/channels/dingtalk/index.js';
|
||||
import { AccountCard as WeixinAccountCard } from '../plugin-src/client/channels/weixin/index.js';
|
||||
import { AccountCard as WecomAccountCard } from '../plugin-src/client/channels/wecom/index.js';
|
||||
import { AccountCard as QqAccountCard } from '../plugin-src/client/channels/qq/index.js';
|
||||
import { SlackAccountCard } from '../plugin-src/client/channels/slack/index.js';
|
||||
import { TelegramAccountCard } from '../plugin-src/client/channels/telegram/index.js';
|
||||
import { DiscordAccountCard } from '../plugin-src/client/channels/discord/index.js';
|
||||
import { WhatsappAccountCard } from '../plugin-src/client/channels/whatsapp/index.js';
|
||||
|
||||
const publicFailure = Object.freeze({
|
||||
code: 'MODEL_RATE_LIMIT',
|
||||
reason: 'MODEL_RATE_LIMIT',
|
||||
message: '模型服务正在限流,本次任务未完成。请稍后重试。',
|
||||
referenceId: 'MF-12AB34CD',
|
||||
at: Date.UTC(2026, 7, 25, 7, 30),
|
||||
});
|
||||
|
||||
function rawBot() {
|
||||
return {
|
||||
botId: 'bot_safe',
|
||||
connected: true,
|
||||
configured: true,
|
||||
state: 'connected',
|
||||
workspace: '/workspace/current',
|
||||
groupResponseMode: 'mention',
|
||||
bot: {
|
||||
name: 'Harness Bot',
|
||||
username: 'harness_bot',
|
||||
appIdMasked: 'app••••01',
|
||||
clientIdMasked: 'client••••01',
|
||||
accountIdMasked: 'account••••01',
|
||||
idMasked: 'bot••••01',
|
||||
},
|
||||
health: {
|
||||
status: 'healthy',
|
||||
summary: '连接运行正常',
|
||||
lastCheckedAt: publicFailure.at,
|
||||
},
|
||||
lastMessageError: {
|
||||
...publicFailure,
|
||||
providerDetail: 'must-not-cross-client-normalization',
|
||||
stack: '/private/path/that/must/not/render',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('last-message error normalizer keeps only the bounded public contract', () => {
|
||||
assert.deepEqual(normalizeLastMessageError({
|
||||
...publicFailure,
|
||||
providerDetail: 'secret provider response',
|
||||
token: 'secret token',
|
||||
}), publicFailure);
|
||||
|
||||
const bounded = normalizeLastMessageError({
|
||||
code: 'C'.repeat(80),
|
||||
reason: 'R'.repeat(80),
|
||||
message: 'M'.repeat(600),
|
||||
referenceId: 'F'.repeat(60),
|
||||
at: 1,
|
||||
});
|
||||
assert.deepEqual({
|
||||
code: bounded.code.length,
|
||||
reason: bounded.reason.length,
|
||||
message: bounded.message.length,
|
||||
referenceId: bounded.referenceId.length,
|
||||
}, { code: 64, reason: 64, message: 500, referenceId: 40 });
|
||||
|
||||
for (const invalid of [
|
||||
null,
|
||||
[],
|
||||
{ ...publicFailure, referenceId: '' },
|
||||
{ ...publicFailure, at: '2026-08-25T07:30:00Z' },
|
||||
{ ...publicFailure, at: Number.POSITIVE_INFINITY },
|
||||
]) assert.equal(normalizeLastMessageError(invalid), null);
|
||||
});
|
||||
|
||||
test('all channel snapshot normalizers retain the same safe message failure', () => {
|
||||
const normalizers = [
|
||||
['Feishu', normalizeFeishu],
|
||||
['DingTalk', normalizeDingtalk],
|
||||
['WeChat', normalizeWeixin],
|
||||
['WeCom', normalizeWecom],
|
||||
['QQ', normalizeQq],
|
||||
['Slack', normalizeSlack],
|
||||
['Telegram', normalizeTelegram],
|
||||
['Discord', normalizeDiscord],
|
||||
['WhatsApp', normalizeWhatsapp],
|
||||
];
|
||||
|
||||
for (const [channel, normalize] of normalizers) {
|
||||
const snapshot = normalize({ bots: [rawBot()] });
|
||||
assert.deepEqual(snapshot.bots[0].lastMessageError, publicFailure, channel);
|
||||
assert.doesNotMatch(
|
||||
JSON.stringify(snapshot.bots[0].lastMessageError),
|
||||
/providerDetail|private|token/i,
|
||||
channel,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('all channel cards show message failures independently from healthy connections', () => {
|
||||
const account = rawBot();
|
||||
const callbacks = {
|
||||
onReconnect() {},
|
||||
onRequestRemove() {},
|
||||
onConfirmRemove() {},
|
||||
onCancelRemove() {},
|
||||
};
|
||||
const cards = [
|
||||
['Feishu', FeishuBotCard, { connection: account }],
|
||||
['DingTalk', DingtalkAccountCard, { account }],
|
||||
['WeChat', WeixinAccountCard, { account }],
|
||||
['WeCom', WecomAccountCard, { account }],
|
||||
['QQ', QqAccountCard, { account }],
|
||||
['Slack', SlackAccountCard, { account }],
|
||||
['Telegram', TelegramAccountCard, { account }],
|
||||
['Discord', DiscordAccountCard, { account }],
|
||||
['WhatsApp', WhatsappAccountCard, { account }],
|
||||
];
|
||||
|
||||
for (const [channel, Card, props] of cards) {
|
||||
const markup = renderToStaticMarkup(React.createElement(Card, { ...callbacks, ...props }));
|
||||
assert.match(markup, /运行正常/, channel);
|
||||
assert.match(markup, /最近一条消息处理失败/, channel);
|
||||
assert.match(markup, /模型服务正在限流/, channel);
|
||||
assert.match(markup, /MODEL_RATE_LIMIT/, channel);
|
||||
assert.match(markup, /MF-12AB34CD/, channel);
|
||||
assert.match(markup, /role="status"/, channel);
|
||||
assert.match(markup, /<time dateTime="2026-08-25T07:30:00.000Z">/, channel);
|
||||
assert.doesNotMatch(markup, /providerDetail|must-not-cross|private\/path/, channel);
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue