Files
goodbuddy/src/main/channels/wecom-channel-driver.test.ts
T

173 lines
4.9 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { WeComChannelDriver } from './wecom-channel-driver'
import type { WeComSdkTransport } from './wecom-driver'
type MessageListener = (frame: unknown) => void
type ErrorListener = (error: Error) => void
type AuthenticatedListener = () => void
class FakeTransport implements WeComSdkTransport {
readonly connect = vi.fn(() => {
this.authenticatedListener?.()
})
readonly disconnect = vi.fn()
readonly replyStream = vi.fn<WeComSdkTransport['replyStream']>(
async () => ({})
)
private messageListener?: MessageListener
private authenticatedListener?: AuthenticatedListener
on(event: 'message', listener: MessageListener): unknown
on(event: 'error', listener: ErrorListener): unknown
on(event: 'authenticated', listener: AuthenticatedListener): unknown
on(
event: 'message' | 'error' | 'authenticated',
listener: MessageListener | ErrorListener | AuthenticatedListener
): unknown {
if (event === 'message') {
this.messageListener = listener as MessageListener
} else if (event === 'authenticated') {
this.authenticatedListener = listener as AuthenticatedListener
}
return this
}
off(event: 'message', listener: MessageListener): unknown
off(event: 'error', listener: ErrorListener): unknown
off(event: 'authenticated', listener: AuthenticatedListener): unknown
off(event: 'message' | 'error' | 'authenticated'): unknown {
if (event === 'message') {
this.messageListener = undefined
} else if (event === 'authenticated') {
this.authenticatedListener = undefined
}
return this
}
emit(frame: unknown): void {
this.messageListener?.(frame)
}
}
function groupFrame(
eventId: string,
requestId: string
): Record<string, unknown> {
return {
cmd: 'aibot_msg_callback',
headers: { req_id: requestId },
body: {
msgid: eventId,
aibotid: 'bot-1',
chatid: 'group-1',
chattype: 'group',
from: { userid: 'user-1' },
create_time: 1_700_000_000,
msgtype: 'text',
text: { content: '@GoodBuddy 请规划下一步' }
}
}
}
describe('WeComChannelDriver', () => {
it('adapts mentioned group messages and bounds reply contexts', async () => {
const transport = new FakeTransport()
const driver = new WeComChannelDriver({
botId: 'bot-1',
secret: 'secret',
transportFactory: () => transport,
maximumReplyContexts: 1
})
const messages: unknown[] = []
await driver.start((message) => {
messages.push(message)
})
transport.emit(groupFrame('event-1', 'request-1'))
transport.emit(groupFrame('event-2', 'request-2'))
expect(messages[0]).toEqual({
channel: 'wecom',
accountId: 'bot-1',
eventId: 'event-1',
senderId: 'user-1',
conversationId: 'group-1',
conversationType: 'group',
text: '@GoodBuddy 请规划下一步',
mentioned: true,
workMode: 'ask',
receivedAt: 1_700_000_000
})
await expect(
driver.send(
{
channel: 'wecom',
eventId: 'event-1',
conversationId: 'group-1',
recipientId: 'user-1',
status: 'completed',
output: '旧回复'
},
new AbortController().signal
)
).rejects.toThrow('上下文无效')
await driver.send(
{
channel: 'wecom',
eventId: 'event-2',
conversationId: 'group-1',
recipientId: 'user-1',
status: 'completed',
output: '新回复'
},
new AbortController().signal
)
expect(transport.replyStream).toHaveBeenCalledWith(
{ headers: { req_id: 'request-2' } },
expect.stringMatching(/^goodbuddy_/u),
'新回复',
true
)
await driver.stop()
expect(transport.disconnect).toHaveBeenCalledOnce()
})
it('rejects unsupported attachments without consuming the reply context', async () => {
const transport = new FakeTransport()
const driver = new WeComChannelDriver({
botId: 'bot-1',
secret: 'secret',
transportFactory: () => transport
})
await driver.start(() => undefined)
transport.emit(groupFrame('media-event', 'media-request'))
const message = {
channel: 'wecom' as const,
eventId: 'media-event',
conversationId: 'group-1',
recipientId: 'user-1',
status: 'completed',
output: '文件已生成',
attachments: [
{
name: 'result.txt',
mimeType: 'text/plain',
size: 2,
kind: 'file' as const,
dataBase64: 'b2s='
}
]
}
await expect(
driver.send(message, new AbortController().signal)
).rejects.toThrow('回复失败')
await driver.send(
{ ...message, attachments: undefined },
new AbortController().signal
)
expect(transport.replyStream).toHaveBeenCalledOnce()
await driver.stop()
})
})