mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 04:13:17 +08:00
194 lines
6.2 KiB
JavaScript
194 lines
6.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import {
|
|
FEISHU_ENDPOINTS,
|
|
normalizeBotsSnapshot,
|
|
normalizeConnectionSnapshot,
|
|
normalizePollResult,
|
|
normalizeProvisioning,
|
|
presentError,
|
|
reconnectBot,
|
|
retryConnection,
|
|
screenFromSnapshot,
|
|
unwrapRpcResult,
|
|
} from '../plugin-src/client/channels/feishu/api.js';
|
|
|
|
test('multi-bot endpoints are bot-scoped and keep legacy operations separate', () => {
|
|
assert.equal(FEISHU_ENDPOINTS.reconnectBot, 'bot.reconnect');
|
|
assert.equal(FEISHU_ENDPOINTS.disconnectBot, 'bot.disconnect');
|
|
assert.equal(FEISHU_ENDPOINTS.deleteBot, 'bot.delete');
|
|
assert.equal(FEISHU_ENDPOINTS.testConnection, 'connection.test');
|
|
});
|
|
|
|
test('client normalizes multiple independent bots and derives authoritative totals', () => {
|
|
const snapshot = normalizeBotsSnapshot({
|
|
schemaVersion: 2,
|
|
revision: 9,
|
|
totals: { configured: 99, connected: 99 },
|
|
bots: [
|
|
{
|
|
botId: 'bot-a',
|
|
state: 'connected',
|
|
connected: true,
|
|
configured: true,
|
|
bot: {
|
|
name: '销售助手',
|
|
appIdMasked: 'cli_aaaa••••1111',
|
|
domain: 'feishu',
|
|
clientSecret: 'must-not-leak',
|
|
},
|
|
health: { status: 'healthy', summary: '长连接运行正常' },
|
|
},
|
|
{
|
|
botId: 'bot-b',
|
|
// A stale state label must not make an offline bot appear connected.
|
|
state: 'connected',
|
|
connected: false,
|
|
configured: true,
|
|
bot: { name: '研发助手', domain: 'lark' },
|
|
health: { status: 'offline', summary: '等待重连' },
|
|
error: { code: 'connection_failed', message: '连接失败' },
|
|
},
|
|
],
|
|
});
|
|
|
|
assert.equal(snapshot.schemaVersion, 2);
|
|
assert.equal(snapshot.revision, 9);
|
|
assert.deepEqual(snapshot.totals, { configured: 2, connected: 1 });
|
|
assert.equal(snapshot.bots[0].state, 'connected');
|
|
assert.equal(snapshot.bots[1].state, 'connecting');
|
|
assert.equal(snapshot.bots[1].bot.domain, 'lark');
|
|
assert.equal(snapshot.bots[1].error.message, '连接失败');
|
|
assert.equal('clientSecret' in snapshot.bots[0].bot, false);
|
|
});
|
|
|
|
test('multi-bot snapshot rejects entries without an opaque botId', () => {
|
|
assert.throws(
|
|
() => normalizeBotsSnapshot({ schemaVersion: 2, bots: [{ connected: true }] }),
|
|
/botId/,
|
|
);
|
|
});
|
|
|
|
test('provision polling preserves the newly connected botId', () => {
|
|
assert.deepEqual(normalizePollResult({
|
|
status: 'connected',
|
|
botId: 'bot-new',
|
|
}), {
|
|
status: 'connected',
|
|
botId: 'bot-new',
|
|
message: undefined,
|
|
connection: undefined,
|
|
provisioning: undefined,
|
|
});
|
|
});
|
|
|
|
test('reconnect helper scopes the mutation to one bot before refreshing the list', async () => {
|
|
const calls = [];
|
|
const status = { schemaVersion: 2, bots: [] };
|
|
const value = await reconnectBot(async (endpoint, payload) => {
|
|
calls.push({ endpoint, payload });
|
|
return endpoint === 'connection.status' ? status : { accepted: true };
|
|
}, 'bot-a');
|
|
|
|
assert.deepEqual(calls, [
|
|
{ endpoint: 'bot.reconnect', payload: { botId: 'bot-a' } },
|
|
{ endpoint: 'connection.status', payload: {} },
|
|
]);
|
|
assert.equal(value, status);
|
|
});
|
|
|
|
test('client only shows connected when the Host confirms connected=true', () => {
|
|
assert.equal(normalizeConnectionSnapshot({
|
|
state: 'connected',
|
|
connected: false,
|
|
}).state, 'connecting');
|
|
assert.equal(normalizeConnectionSnapshot({
|
|
state: 'connecting',
|
|
connected: true,
|
|
}).state, 'connected');
|
|
});
|
|
|
|
test('client accepts a Host-rendered QR code without exposing credentials', () => {
|
|
const provisioning = normalizeProvisioning({
|
|
attemptId: '7',
|
|
verificationUrl: 'https://accounts.feishu.cn/device',
|
|
qrCodeDataUrl: 'data:image/png;base64,AAAA',
|
|
expiresAt: 100_000,
|
|
pollIntervalMs: 1_800,
|
|
}, 1_000);
|
|
|
|
assert.equal(provisioning.attemptId, '7');
|
|
assert.equal(provisioning.qrCodeDataUrl, 'data:image/png;base64,AAAA');
|
|
assert.equal('clientSecret' in provisioning, false);
|
|
});
|
|
|
|
test('client unwraps RpcResult and redacts credential-shaped error text', () => {
|
|
assert.deepEqual(unwrapRpcResult({ ok: true, value: { connected: true } }), {
|
|
connected: true,
|
|
});
|
|
assert.throws(
|
|
() => unwrapRpcResult({
|
|
ok: false,
|
|
error: { code: 'internal', message: 'failed' },
|
|
}),
|
|
/failed/,
|
|
);
|
|
assert.doesNotMatch(
|
|
presentError(new Error('app_secret=do-not-show token=also-hidden')).message,
|
|
/do-not-show|also-hidden/,
|
|
);
|
|
});
|
|
|
|
test('configured offline and startup errors never become the create screen', () => {
|
|
const offline = normalizeConnectionSnapshot({
|
|
state: 'disconnected',
|
|
connected: false,
|
|
configured: true,
|
|
bot: { name: '北汇星河助手' },
|
|
health: { status: 'offline', summary: '长连接已断开' },
|
|
});
|
|
assert.equal(offline.configured, true);
|
|
assert.equal(screenFromSnapshot(offline).phase, 'offline');
|
|
|
|
const failed = screenFromSnapshot(normalizeConnectionSnapshot({
|
|
state: 'error',
|
|
connected: false,
|
|
configured: true,
|
|
error: { message: '启动失败' },
|
|
}));
|
|
assert.equal(failed.phase, 'error');
|
|
assert.equal(failed.retry, 'test');
|
|
assert.equal(failed.configured, true);
|
|
|
|
const failedBeforeConfiguration = screenFromSnapshot(normalizeConnectionSnapshot({
|
|
state: 'error',
|
|
connected: false,
|
|
configured: false,
|
|
error: { message: '二维码已过期' },
|
|
}));
|
|
assert.equal(failedBeforeConfiguration.phase, 'error');
|
|
assert.equal(failedBeforeConfiguration.retry, 'begin');
|
|
|
|
const fresh = screenFromSnapshot(normalizeConnectionSnapshot({
|
|
state: 'disconnected',
|
|
connected: false,
|
|
configured: false,
|
|
}));
|
|
assert.deepEqual(fresh, { phase: 'disconnected', configured: false });
|
|
});
|
|
|
|
test('retry connection calls connection.test before authoritative connection.status', async () => {
|
|
const calls = [];
|
|
const status = { state: 'connected', connected: true, configured: true };
|
|
const value = await retryConnection(async (endpoint, payload) => {
|
|
calls.push({ endpoint, payload });
|
|
return endpoint === 'connection.status' ? status : { accepted: true };
|
|
});
|
|
|
|
assert.deepEqual(calls, [
|
|
{ endpoint: 'connection.test', payload: {} },
|
|
{ endpoint: 'connection.status', payload: {} },
|
|
]);
|
|
assert.equal(value, status);
|
|
});
|