mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 11:37:06 +08:00
193 lines
6.8 KiB
TypeScript
193 lines
6.8 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { VoiceRecognitionUseCase } from '../../src/main/voice-pipeline/voice-recognition-use-case'
|
|
|
|
const { countVoiceMessagesAsync, listContactsAsync, listMessagesAsync } = vi.hoisted(() => ({
|
|
countVoiceMessagesAsync: vi.fn(),
|
|
listContactsAsync: vi.fn(),
|
|
listMessagesAsync: vi.fn()
|
|
}))
|
|
|
|
vi.mock('../../src/main/services/chat-service', () => ({
|
|
countVoiceMessagesAsync,
|
|
listContactsAsync,
|
|
listMessagesAsync
|
|
}))
|
|
|
|
import { VoiceBatchService } from '../../src/main/voice-pipeline/voice-batch-service'
|
|
|
|
const readyStatus = {
|
|
modelId: 'sensevoice-small-int8',
|
|
version: 'fixture',
|
|
state: 'ready' as const,
|
|
downloadedBytes: 1,
|
|
totalBytes: 1,
|
|
progress: 1,
|
|
platform: 'win32' as const,
|
|
architecture: 'x64',
|
|
supported: true
|
|
}
|
|
|
|
function makeRecognition(): VoiceRecognitionUseCase {
|
|
return {
|
|
accountIdentity: 'account-a',
|
|
getModelStatus: vi.fn().mockResolvedValue(readyStatus),
|
|
getTranscriptSnapshot: vi.fn().mockReturnValue({ state: 'pending' }),
|
|
recognize: vi.fn().mockResolvedValue({ success: true, transcript: '转写结果', cached: false }),
|
|
publishTranscriptSnapshot: vi.fn().mockResolvedValue(undefined)
|
|
} as unknown as VoiceRecognitionUseCase
|
|
}
|
|
|
|
describe('VoiceBatchService', () => {
|
|
beforeEach(() => {
|
|
listContactsAsync.mockReset()
|
|
listMessagesAsync.mockReset()
|
|
countVoiceMessagesAsync.mockReset()
|
|
listContactsAsync.mockResolvedValue([
|
|
{ md5: 'contact-a', m_nsUsrName: 'contact-a-id', m_nsNickName: '联系人 A', type: 'user' },
|
|
{ md5: 'group-b', m_nsUsrName: 'group-b@chatroom', m_nsNickName: '群聊 B', type: 'group' }
|
|
])
|
|
listMessagesAsync.mockImplementation(async (conversationId: string) => [
|
|
{
|
|
id: `${conversationId}-voice`,
|
|
type: '语音',
|
|
content: '[语音消息]',
|
|
isSender: false,
|
|
sessionId: conversationId === 'contact-a' ? 'contact-a-id' : 'group-b@chatroom',
|
|
localId: conversationId === 'contact-a' ? 11 : 22,
|
|
createTime: 1_785_895_200
|
|
},
|
|
{
|
|
id: `${conversationId}-text`,
|
|
type: '普通文本',
|
|
content: '不会进入语音任务',
|
|
isSender: false,
|
|
createTime: 1_785_895_201
|
|
}
|
|
])
|
|
countVoiceMessagesAsync.mockImplementation(async (conversationId: string) =>
|
|
conversationId === 'contact-a' ? 3 : 7
|
|
)
|
|
})
|
|
|
|
it('limits a batch to selected contacts and groups, then schedules each item as background work', async () => {
|
|
const recognition = makeRecognition()
|
|
const service = new VoiceBatchService(recognition)
|
|
const preflight = await service.preflight({
|
|
conversationIds: ['contact-a', 'group-b'],
|
|
range: 'recent_30_days'
|
|
})
|
|
|
|
expect(preflight).toMatchObject({
|
|
conversationCount: 2,
|
|
voiceMessageCount: 2,
|
|
cachedCount: 0,
|
|
pendingCount: 2,
|
|
modelReady: true
|
|
})
|
|
expect(listMessagesAsync).toHaveBeenCalledTimes(2)
|
|
expect(listMessagesAsync.mock.calls[0][1]).toEqual(expect.any(Number))
|
|
|
|
await service.start({ conversationIds: ['contact-a'], range: 'selected_history' })
|
|
await vi.waitFor(() => expect(service.getProgress().state).toBe('completed'))
|
|
expect(recognition.recognize).toHaveBeenCalledWith(
|
|
expect.objectContaining({ sessionId: 'contact-a-id', localId: 11 }),
|
|
{ priority: 'background', publishTranscriptUpdate: false }
|
|
)
|
|
expect(recognition.recognize).toHaveBeenCalledTimes(1)
|
|
await vi.waitFor(() =>
|
|
expect(recognition.publishTranscriptSnapshot).toHaveBeenCalledWith(
|
|
expect.objectContaining({ sessionId: 'contact-a-id', localId: 11 })
|
|
)
|
|
)
|
|
})
|
|
|
|
it('defers knowledge notifications until the batch finishes and sends one per conversation', async () => {
|
|
listMessagesAsync.mockResolvedValue([
|
|
{
|
|
id: 'contact-a-voice-1',
|
|
type: '语音',
|
|
content: '[语音消息]',
|
|
isSender: false,
|
|
sessionId: 'contact-a-id',
|
|
localId: 11,
|
|
createTime: 1_785_895_200
|
|
},
|
|
{
|
|
id: 'contact-a-voice-2',
|
|
type: '语音',
|
|
content: '[语音消息]',
|
|
isSender: false,
|
|
sessionId: 'contact-a-id',
|
|
localId: 12,
|
|
createTime: 1_785_895_201
|
|
}
|
|
])
|
|
const recognition = makeRecognition()
|
|
let releaseKnowledgeRefresh: (() => void) | undefined
|
|
vi.mocked(recognition.publishTranscriptSnapshot).mockImplementationOnce(
|
|
() =>
|
|
new Promise<void>((resolve) => {
|
|
releaseKnowledgeRefresh = resolve
|
|
})
|
|
)
|
|
const service = new VoiceBatchService(recognition)
|
|
|
|
await service.start({ conversationIds: ['contact-a'], range: 'selected_history' })
|
|
await vi.waitFor(() => expect(recognition.publishTranscriptSnapshot).toHaveBeenCalledTimes(1))
|
|
expect(service.getProgress().state).toBe('processing')
|
|
releaseKnowledgeRefresh?.()
|
|
await vi.waitFor(() => expect(service.getProgress().state).toBe('completed'))
|
|
|
|
expect(recognition.recognize).toHaveBeenCalledTimes(2)
|
|
expect(recognition.recognize).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.anything(),
|
|
expect.objectContaining({ publishTranscriptUpdate: false })
|
|
)
|
|
expect(recognition.publishTranscriptSnapshot).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('does not start when the selected range contains a stale conversation id', async () => {
|
|
const service = new VoiceBatchService(makeRecognition())
|
|
|
|
await expect(
|
|
service.preflight({ conversationIds: ['missing-conversation'], range: 'recent_30_days' })
|
|
).rejects.toThrow('选择的会话已不可用')
|
|
})
|
|
|
|
it('reports cache hits separately from newly recognized items', async () => {
|
|
const recognition = makeRecognition()
|
|
vi.mocked(recognition.getTranscriptSnapshot).mockReturnValue({
|
|
state: 'transcribed',
|
|
transcript: '旧缓存'
|
|
})
|
|
vi.mocked(recognition.recognize).mockResolvedValue({
|
|
success: true,
|
|
transcript: '旧缓存',
|
|
cached: true
|
|
})
|
|
const service = new VoiceBatchService(recognition)
|
|
|
|
await service.start({ conversationIds: ['contact-a'], range: 'selected_history' })
|
|
await vi.waitFor(() => expect(service.getProgress().state).toBe('completed'))
|
|
expect(service.getProgress()).toMatchObject({ cached: 1, succeeded: 0, failed: 0 })
|
|
})
|
|
|
|
it('counts visible conversations through the lightweight voice-count path without loading messages', async () => {
|
|
const service = new VoiceBatchService(makeRecognition())
|
|
|
|
await expect(
|
|
service.conversationSummaries({
|
|
conversationIds: ['contact-a', 'group-b'],
|
|
range: 'recent_30_days'
|
|
})
|
|
).resolves.toEqual([
|
|
{ conversationId: 'contact-a', voiceMessageCount: 3 },
|
|
{ conversationId: 'group-b', voiceMessageCount: 7 }
|
|
])
|
|
|
|
expect(countVoiceMessagesAsync).toHaveBeenCalledTimes(2)
|
|
expect(listMessagesAsync).not.toHaveBeenCalled()
|
|
})
|
|
})
|