mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-10 16:40:46 +08:00
feat(feishu): add group message permission authorization
This commit is contained in:
parent
7b892c02f5
commit
7bf12628ce
17 changed files with 1581 additions and 258 deletions
|
|
@ -53,7 +53,8 @@ test('Feishu connection check requests and displays test-message feedback', asyn
|
|||
assert.match(markup, /aria-label="修复飞书测试机器人的卡片按钮"/);
|
||||
assert.match(markup, /<select[^>]*aria-label="群聊响应方式"/);
|
||||
assert.match(markup, /仅在 @机器人时响应(推荐)/);
|
||||
assert.match(markup, /响应所有群消息(需飞书敏感权限)/);
|
||||
assert.match(markup, /响应所有群消息/);
|
||||
assert.match(markup, /选择全部消息后会打开飞书官方授权流程/);
|
||||
});
|
||||
|
||||
test('Feishu bot card saves group response mode from a dropdown', async () => {
|
||||
|
|
@ -91,6 +92,221 @@ test('Feishu bot card saves group response mode from a dropdown', async () => {
|
|||
await act(async () => renderer.unmount());
|
||||
});
|
||||
|
||||
test('Feishu bot card offers authorization recovery for all-message mode', () => {
|
||||
const baseConnection = {
|
||||
botId: 'bot-permission-recovery',
|
||||
state: 'connected',
|
||||
connected: true,
|
||||
groupResponseMode: 'all',
|
||||
bot: { name: '权限恢复机器人', appIdMasked: 'cli_reco••••very' },
|
||||
health: { summary: '长连接运行正常', lastCheckedAt: Date.now() },
|
||||
};
|
||||
const reauthorizeMarkup = renderToStaticMarkup(React.createElement(BotCard, {
|
||||
connection: { ...baseConnection, groupMessagePermissionGranted: true },
|
||||
onReconnect() {},
|
||||
onRequestRemove() {},
|
||||
onConfirmRemove() {},
|
||||
onCancelRemove() {},
|
||||
}));
|
||||
assert.match(reauthorizeMarkup, /aria-label="重新授权群消息权限"/);
|
||||
assert.match(reauthorizeMarkup, />重新授权</);
|
||||
|
||||
const legacyMarkup = renderToStaticMarkup(React.createElement(BotCard, {
|
||||
connection: { ...baseConnection, groupMessagePermissionGranted: false },
|
||||
onReconnect() {},
|
||||
onRequestRemove() {},
|
||||
onConfirmRemove() {},
|
||||
onCancelRemove() {},
|
||||
}));
|
||||
assert.match(legacyMarkup, /尚未确认“获取群组中所有消息”权限/);
|
||||
assert.match(legacyMarkup, />去授权</);
|
||||
});
|
||||
|
||||
test('selecting all group messages opens the official permission flow before saving mode', async (t) => {
|
||||
const previousWindow = globalThis.window;
|
||||
let nextTimer = 0;
|
||||
const frames = new Map();
|
||||
globalThis.window = {
|
||||
setInterval() { return ++nextTimer; },
|
||||
clearInterval() {},
|
||||
setTimeout() { return ++nextTimer; },
|
||||
clearTimeout() {},
|
||||
requestAnimationFrame(callback) {
|
||||
const id = ++nextTimer;
|
||||
frames.set(id, callback);
|
||||
queueMicrotask(() => {
|
||||
const pending = frames.get(id);
|
||||
if (!pending) return;
|
||||
frames.delete(id);
|
||||
pending();
|
||||
});
|
||||
return id;
|
||||
},
|
||||
cancelAnimationFrame(id) { frames.delete(id); },
|
||||
};
|
||||
t.after(() => {
|
||||
if (previousWindow === undefined) delete globalThis.window;
|
||||
else globalThis.window = previousWindow;
|
||||
});
|
||||
|
||||
const snapshot = {
|
||||
schemaVersion: 2,
|
||||
revision: 1,
|
||||
state: 'connected',
|
||||
bots: [{
|
||||
botId: 'bot_before_permission',
|
||||
state: 'connected',
|
||||
connected: true,
|
||||
configured: true,
|
||||
groupResponseMode: 'mention',
|
||||
groupMessagePermissionGranted: false,
|
||||
bot: { name: '前一个机器人', appIdMasked: 'cli_bef••••ore' },
|
||||
health: { status: 'healthy', summary: '长连接运行正常' },
|
||||
}, {
|
||||
botId: 'bot_group_permission',
|
||||
state: 'connected',
|
||||
connected: true,
|
||||
configured: true,
|
||||
groupResponseMode: 'mention',
|
||||
groupMessagePermissionGranted: false,
|
||||
bot: { name: '权限机器人', appIdMasked: 'cli_per••••sion' },
|
||||
health: { status: 'healthy', summary: '长连接运行正常' },
|
||||
}],
|
||||
};
|
||||
const calls = [];
|
||||
const rpcCall = async (endpoint, payload) => {
|
||||
calls.push({ endpoint, payload });
|
||||
if (endpoint === FEISHU_ENDPOINTS.status) return { ok: true, value: snapshot };
|
||||
if (endpoint === FEISHU_ENDPOINTS.beginGroupMessagePermission) {
|
||||
return {
|
||||
ok: true,
|
||||
value: {
|
||||
attemptId: 'reg_group_permission',
|
||||
operation: 'group_message_permission',
|
||||
botId: 'bot_group_permission',
|
||||
verificationUrl: 'https://open.feishu.cn/page/launcher?tp=sdk&clientID=cli_permission&addons=encoded',
|
||||
qrCodeDataUrl: 'data:image/png;base64,AAAA',
|
||||
expiresAt: Date.now() + 60_000,
|
||||
pollIntervalMs: 800,
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
||||
};
|
||||
|
||||
let renderer;
|
||||
await act(async () => {
|
||||
renderer = create(React.createElement(FeishuSettingsTab, { rpcCall }));
|
||||
await flushMicrotasks();
|
||||
});
|
||||
const targetCard = () => renderer.root.findByProps({ 'data-bot-id': 'bot_group_permission' });
|
||||
const select = targetCard().findByProps({ 'aria-label': '群聊响应方式' });
|
||||
await act(async () => {
|
||||
select.props.onChange({ target: { value: 'all' } });
|
||||
await flushMicrotasks();
|
||||
});
|
||||
|
||||
assert.ok(calls.some(({ endpoint, payload }) => (
|
||||
endpoint === FEISHU_ENDPOINTS.beginGroupMessagePermission
|
||||
&& payload.botId === 'bot_group_permission'
|
||||
)));
|
||||
assert.equal(calls.some(({ endpoint }) => endpoint === FEISHU_ENDPOINTS.setGroupResponseMode), false);
|
||||
const permissionPanel = targetCard().findByProps({
|
||||
'data-provision-for': 'bot_group_permission',
|
||||
});
|
||||
assert.equal(permissionPanel.findByType('a').props.href,
|
||||
'https://open.feishu.cn/page/launcher?tp=sdk&clientID=cli_permission&addons=encoded');
|
||||
assert.match(textOf(permissionPanel), /只增量开通“获取群组中所有消息”权限/);
|
||||
assert.equal(renderer.root.findByProps({ 'data-bot-id': 'bot_before_permission' })
|
||||
.findAllByProps({ 'data-provision-for': 'bot_group_permission' }).length, 0);
|
||||
assert.equal(targetCard().findByProps({ 'aria-label': '群聊响应方式' }).props.value, 'mention');
|
||||
await act(async () => renderer.unmount());
|
||||
});
|
||||
|
||||
test('reauthorizing all-message mode starts the same bot-scoped permission flow', async (t) => {
|
||||
const previousWindow = globalThis.window;
|
||||
let nextTimer = 0;
|
||||
const frames = new Map();
|
||||
globalThis.window = {
|
||||
setInterval() { return ++nextTimer; },
|
||||
clearInterval() {},
|
||||
setTimeout() { return ++nextTimer; },
|
||||
clearTimeout() {},
|
||||
requestAnimationFrame(callback) {
|
||||
const id = ++nextTimer;
|
||||
frames.set(id, callback);
|
||||
queueMicrotask(() => {
|
||||
const pending = frames.get(id);
|
||||
if (!pending) return;
|
||||
frames.delete(id);
|
||||
pending();
|
||||
});
|
||||
return id;
|
||||
},
|
||||
cancelAnimationFrame(id) { frames.delete(id); },
|
||||
};
|
||||
t.after(() => {
|
||||
if (previousWindow === undefined) delete globalThis.window;
|
||||
else globalThis.window = previousWindow;
|
||||
});
|
||||
|
||||
const snapshot = {
|
||||
schemaVersion: 2,
|
||||
revision: 1,
|
||||
state: 'connected',
|
||||
bots: [{
|
||||
botId: 'bot_reauthorize',
|
||||
state: 'connected',
|
||||
connected: true,
|
||||
configured: true,
|
||||
groupResponseMode: 'all',
|
||||
groupMessagePermissionGranted: true,
|
||||
bot: { name: '重新授权机器人', appIdMasked: 'cli_reau••••thorize' },
|
||||
health: { status: 'healthy', summary: '长连接运行正常' },
|
||||
}],
|
||||
};
|
||||
const calls = [];
|
||||
const rpcCall = async (endpoint, payload) => {
|
||||
calls.push({ endpoint, payload });
|
||||
if (endpoint === FEISHU_ENDPOINTS.status) return { ok: true, value: snapshot };
|
||||
if (endpoint === FEISHU_ENDPOINTS.beginGroupMessagePermission) {
|
||||
return {
|
||||
ok: true,
|
||||
value: {
|
||||
attemptId: 'reg_reauthorize',
|
||||
operation: 'group_message_permission',
|
||||
botId: 'bot_reauthorize',
|
||||
verificationUrl: 'https://open.feishu.cn/page/launcher?tp=sdk&clientID=cli_reauthorize&addons=encoded',
|
||||
qrCodeDataUrl: 'data:image/png;base64,AAAA',
|
||||
expiresAt: Date.now() + 60_000,
|
||||
pollIntervalMs: 800,
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
||||
};
|
||||
|
||||
let renderer;
|
||||
await act(async () => {
|
||||
renderer = create(React.createElement(FeishuSettingsTab, { rpcCall }));
|
||||
await flushMicrotasks();
|
||||
});
|
||||
const targetCard = () => renderer.root.findByProps({ 'data-bot-id': 'bot_reauthorize' });
|
||||
await act(async () => {
|
||||
targetCard().findByProps({ 'aria-label': '重新授权群消息权限' }).props.onClick();
|
||||
await flushMicrotasks();
|
||||
});
|
||||
|
||||
assert.ok(calls.some(({ endpoint, payload }) => (
|
||||
endpoint === FEISHU_ENDPOINTS.beginGroupMessagePermission
|
||||
&& payload.botId === 'bot_reauthorize'
|
||||
)));
|
||||
assert.equal(calls.some(({ endpoint }) => endpoint === FEISHU_ENDPOINTS.setGroupResponseMode), false);
|
||||
assert.match(textOf(targetCard().findByProps({ 'data-provision-for': 'bot_reauthorize' })),
|
||||
/正在为「重新授权机器人」开通群消息权限/);
|
||||
await act(async () => renderer.unmount());
|
||||
});
|
||||
|
||||
test('Feishu callback repair keeps a Host-submitted attempt when a stale QR cancel races saving', async (t) => {
|
||||
const previousWindow = globalThis.window;
|
||||
let nextTimer = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue