import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { SttManagerAdapter } from '../../src/managers/stt-manager-adapter' import { SttError } from '../../src/core/stt-error' import type { RtmManagerAdapter } from '../../src/managers/rtm-manager-adapter' describe('SttManagerAdapter', () => { let manager: SttManagerAdapter let mockRtmManager: RtmManagerAdapter const mockAppId = 'test-app-id' const mockCertificate = 'test-certificate' beforeEach(() => { // 创建模拟的 RTM 管理器 mockRtmManager = { join: vi.fn().mockResolvedValue(undefined), updateSttData: vi.fn().mockResolvedValue(undefined), updateLanguages: vi.fn().mockResolvedValue(undefined), acquireLock: vi.fn().mockResolvedValue(undefined), releaseLock: vi.fn().mockResolvedValue(undefined), destroy: vi.fn().mockResolvedValue(undefined), isJoined: false, config: undefined, userId: '', channel: '', userList: [], on: vi.fn().mockReturnThis(), off: vi.fn().mockReturnThis(), emit: vi.fn().mockReturnThis(), } as any manager = new SttManagerAdapter(mockRtmManager, mockAppId, mockCertificate) }) afterEach(async () => { if (manager.isInitialized) { await manager.destroy() } }) describe('init', () => { it('should initialize successfully with valid config', async () => { const config = { userId: 'test-user', channel: 'test-channel', userName: 'Test User', } await manager.init(config) expect(manager.isInitialized).toBe(true) expect(manager.config).toEqual(config) expect(manager.userId).toBe('test-user') expect(manager.channel).toBe('test-channel') expect(mockRtmManager.join).toHaveBeenCalledWith({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) }) it('should throw error when config is invalid', async () => { const config = { userId: '', channel: '', userName: '', } await expect(manager.init(config)).rejects.toThrow(SttError) await expect(manager.init(config)).rejects.toThrow( 'Missing required configuration parameters' ) }) it('should throw error when appId is not provided', async () => { const managerWithoutAppId = new SttManagerAdapter(mockRtmManager, '', 'test-certificate') const config = { userId: 'test-user', channel: 'test-channel', userName: 'Test User', } await expect(managerWithoutAppId.init(config)).rejects.toThrow(SttError) await expect(managerWithoutAppId.init(config)).rejects.toThrow( 'App ID is required for STT operations' ) }) it('should emit initialized event', async () => { const initializedHandler = vi.fn() manager.on('initialized', initializedHandler) const config = { userId: 'test-user', channel: 'test-channel', userName: 'Test User', } await manager.init(config) expect(initializedHandler).toHaveBeenCalledTimes(1) expect(initializedHandler).toHaveBeenCalledWith({ userId: 'test-user', channel: 'test-channel', }) }) }) describe('startTranscription', () => { beforeEach(async () => { await manager.init({ userId: 'test-user', channel: 'test-channel', userName: 'Test User', }) }) it('should start transcription successfully', async () => { const startingHandler = vi.fn() const startedHandler = vi.fn() manager.on('transcriptionStarting', startingHandler) manager.on('transcriptionStarted', startedHandler) const options = { languages: [{ source: 'en-US' }], } await manager.startTranscription(options) expect(startingHandler).toHaveBeenCalledTimes(1) expect(startingHandler).toHaveBeenCalledWith({ languages: [{ source: 'en-US' }] }) expect(startedHandler).toHaveBeenCalledTimes(1) expect(startedHandler).toHaveBeenCalledWith({ taskId: 'test-task-id', languages: [{ source: 'en-US' }], }) // 验证 RTM 管理器被正确调用 expect(mockRtmManager.acquireLock).toHaveBeenCalledTimes(1) expect(mockRtmManager.releaseLock).toHaveBeenCalledTimes(1) expect(mockRtmManager.updateLanguages).toHaveBeenCalledWith([{ source: 'en-US' }]) expect(mockRtmManager.updateSttData).toHaveBeenCalledWith({ status: 'start', taskId: 'test-task-id', token: 'test-token', startTime: expect.any(Number), duration: 3600000, }) }) it('should throw error when not initialized', async () => { const uninitializedManager = new SttManagerAdapter(mockRtmManager, mockAppId) const options = { languages: [{ source: 'en-US' }], } await expect(uninitializedManager.startTranscription(options)).rejects.toThrow(SttError) await expect(uninitializedManager.startTranscription(options)).rejects.toThrow( 'SttManager must be initialized before starting transcription' ) }) it('should throw error when languages are empty', async () => { const options = { languages: [], } await expect(manager.startTranscription(options)).rejects.toThrow(SttError) await expect(manager.startTranscription(options)).rejects.toThrow( 'At least one language must be provided' ) }) it('should handle API errors gracefully', async () => { // 模拟 API 调用失败 global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 500, json: vi.fn().mockResolvedValue({ message: 'Internal Server Error' }), }) const options = { languages: [{ source: 'en-US' }], } await expect(manager.startTranscription(options)).rejects.toThrow() // 恢复默认的 fetch 模拟 global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, json: vi.fn().mockResolvedValue({ tokenName: 'test-token', taskId: 'test-task-id', }), }) }) }) describe('stopTranscription', () => { beforeEach(async () => { await manager.init({ userId: 'test-user', channel: 'test-channel', userName: 'Test User', }) // 先启动转录以设置 taskId 和 token await manager.startTranscription({ languages: [{ source: 'en-US' }], }) }) it('should stop transcription successfully', async () => { const stoppingHandler = vi.fn() const stoppedHandler = vi.fn() manager.on('transcriptionStopping', stoppingHandler) manager.on('transcriptionStopped', stoppedHandler) await manager.stopTranscription() expect(stoppingHandler).toHaveBeenCalledTimes(1) expect(stoppedHandler).toHaveBeenCalledTimes(1) // 验证 RTM 管理器被正确调用 expect(mockRtmManager.acquireLock).toHaveBeenCalledTimes(2) // start 和 stop 各一次 expect(mockRtmManager.releaseLock).toHaveBeenCalledTimes(2) expect(mockRtmManager.updateSttData).toHaveBeenCalledWith({ status: 'end', }) }) it('should throw error when not initialized', async () => { const uninitializedManager = new SttManagerAdapter(mockRtmManager, mockAppId, mockCertificate) await expect(uninitializedManager.stopTranscription()).rejects.toThrow(SttError) await expect(uninitializedManager.stopTranscription()).rejects.toThrow( 'SttManager must be initialized before stopping transcription' ) }) it('should throw error when no active task found', async () => { const managerWithoutTask = new SttManagerAdapter(mockRtmManager, mockAppId, mockCertificate) await managerWithoutTask.init({ userId: 'test-user', channel: 'test-channel', userName: 'Test User', }) await expect(managerWithoutTask.stopTranscription()).rejects.toThrow(SttError) await expect(managerWithoutTask.stopTranscription()).rejects.toThrow( 'No active transcription task found' ) }) }) describe('queryTranscription', () => { beforeEach(async () => { await manager.init({ userId: 'test-user', channel: 'test-channel', userName: 'Test User', }) // 先启动转录以设置 taskId 和 token await manager.startTranscription({ languages: [{ source: 'en-US' }], }) }) it('should query transcription successfully', async () => { const result = await manager.queryTranscription() expect(result).toEqual({ tokenName: 'test-token', taskId: 'test-task-id', }) }) it('should throw error when not initialized', async () => { const uninitializedManager = new SttManagerAdapter(mockRtmManager, mockAppId) await expect(uninitializedManager.queryTranscription()).rejects.toThrow(SttError) await expect(uninitializedManager.queryTranscription()).rejects.toThrow( 'SttManager must be initialized before querying transcription' ) }) it('should throw error when no active task found', async () => { const managerWithoutTask = new SttManagerAdapter(mockRtmManager, mockAppId) await managerWithoutTask.init({ userId: 'test-user', channel: 'test-channel', userName: 'Test User', }) await expect(managerWithoutTask.queryTranscription()).rejects.toThrow(SttError) await expect(managerWithoutTask.queryTranscription()).rejects.toThrow( 'No active transcription task found' ) }) }) describe('destroy', () => { it('should destroy manager successfully', async () => { await manager.init({ userId: 'test-user', channel: 'test-channel', userName: 'Test User', }) const destroyingHandler = vi.fn() const destroyedHandler = vi.fn() manager.on('destroying', destroyingHandler) manager.on('destroyed', destroyedHandler) await manager.destroy() expect(manager.isInitialized).toBe(false) expect(manager.config).toBeUndefined() expect(destroyingHandler).toHaveBeenCalledTimes(1) expect(destroyedHandler).toHaveBeenCalledTimes(1) expect(mockRtmManager.destroy).toHaveBeenCalledTimes(1) }) it('should handle destroy when not initialized', async () => { await expect(manager.destroy()).resolves.not.toThrow() }) }) describe('extendDuration', () => { beforeEach(async () => { await manager.init({ userId: 'test-user', channel: 'test-channel', userName: 'Test User', }) }) it('should extend duration successfully', async () => { const extendingHandler = vi.fn() const extendedHandler = vi.fn() manager.on('durationExtending', extendingHandler) manager.on('durationExtended', extendedHandler) const options = { startTime: Date.now(), duration: 7200000, // 2小时 } await manager.extendDuration(options) expect(extendingHandler).toHaveBeenCalledTimes(1) expect(extendingHandler).toHaveBeenCalledWith(options) expect(extendedHandler).toHaveBeenCalledTimes(1) expect(extendedHandler).toHaveBeenCalledWith(options) expect(mockRtmManager.updateSttData).toHaveBeenCalledWith(options) }) it('should throw error when not initialized', async () => { const uninitializedManager = new SttManagerAdapter(mockRtmManager, mockAppId) await expect(uninitializedManager.extendDuration({})).rejects.toThrow(SttError) await expect(uninitializedManager.extendDuration({})).rejects.toThrow( 'SttManager must be initialized before extending duration' ) }) }) })