import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { RtmManagerAdapter } from '../../src/managers/rtm-manager-adapter' import { SttError } from '../../src/core/stt-error' import AgoraRTM from 'agora-rtm' describe('RtmManagerAdapter', () => { let manager: RtmManagerAdapter const mockAppId = 'test-app-id' beforeEach(() => { manager = new RtmManagerAdapter(mockAppId) }) afterEach(async () => { if (manager.isJoined) { await manager.destroy() } }) describe('join', () => { it('should join successfully with valid config', async () => { const config = { channel: 'test-channel', userId: 'test-user', userName: 'Test User', } await manager.join(config) expect(manager.isJoined).toBe(true) expect(manager.config).toEqual(config) expect(manager.userId).toBe('test-user') expect(manager.channel).toBe('test-channel') expect(manager.userList).toHaveLength(1) expect(manager.userList[0]).toEqual({ userId: 'test-user', userName: 'Test User', }) // 验证 RTM 客户端被正确创建 expect(AgoraRTM.RTM).toHaveBeenCalledWith(mockAppId, 'test-user', {}) }) it('should throw error when config is invalid', async () => { const config = { channel: '', userId: '', userName: '', } await expect(manager.join(config)).rejects.toThrow(SttError) await expect(manager.join(config)).rejects.toThrow( 'Missing required configuration parameters' ) }) it('should throw error when already joined', async () => { const config = { channel: 'test-channel', userId: 'test-user', userName: 'Test User', } await manager.join(config) await expect(manager.join(config)).rejects.toThrow(SttError) await expect(manager.join(config)).rejects.toThrow( 'RTM manager is already joined to a channel' ) }) it('should throw error when appId is not provided', async () => { const managerWithoutAppId = new RtmManagerAdapter() const config = { channel: 'test-channel', userId: 'test-user', userName: 'Test User', } await expect(managerWithoutAppId.join(config)).rejects.toThrow(SttError) await expect(managerWithoutAppId.join(config)).rejects.toThrow( 'App ID is required for RTM connection' ) }) it('should emit connected event', async () => { const connectedHandler = vi.fn() manager.on('connected', connectedHandler) const config = { channel: 'test-channel', userId: 'test-user', userName: 'Test User', } await manager.join(config) expect(connectedHandler).toHaveBeenCalledTimes(1) expect(connectedHandler).toHaveBeenCalledWith({ channel: 'test-channel', userId: 'test-user', }) }) it('should setup event listeners correctly', async () => { const config = { channel: 'test-channel', userId: 'test-user', userName: 'Test User', } await manager.join(config) // 获取创建的 RTM 客户端实例 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.addEventListener).toHaveBeenCalledWith('status', expect.any(Function)) expect(mockClient.addEventListener).toHaveBeenCalledWith('presence', expect.any(Function)) expect(mockClient.addEventListener).toHaveBeenCalledWith('storage', expect.any(Function)) }) }) describe('updateSttData', () => { beforeEach(async () => { await manager.join({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) }) it('should update STT data successfully', async () => { const updatingHandler = vi.fn() const updatedHandler = vi.fn() manager.on('metadataUpdating', updatingHandler) manager.on('metadataUpdated', updatedHandler) const data = { status: 'start', taskId: 'test-task-id', token: 'test-token', startTime: Date.now(), duration: 3600000, } await manager.updateSttData(data) expect(updatingHandler).toHaveBeenCalledTimes(1) expect(updatingHandler).toHaveBeenCalledWith({ data }) expect(updatedHandler).toHaveBeenCalledTimes(1) expect(updatedHandler).toHaveBeenCalledWith({ data }) // 验证存储方法被正确调用 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith( 'test-channel', 'MESSAGE', expect.arrayContaining([ { key: 'status', value: expect.any(String) }, { key: 'taskId', value: expect.any(String) }, { key: 'token', value: expect.any(String) }, { key: 'startTime', value: expect.any(String) }, { key: 'duration', value: expect.any(String) }, ]) ) }) it('should throw error when not joined', async () => { const unjoinedManager = new RtmManagerAdapter(mockAppId) const data = { status: 'start', taskId: 'test-task-id', } await expect(unjoinedManager.updateSttData(data)).rejects.toThrow(SttError) await expect(unjoinedManager.updateSttData(data)).rejects.toThrow( 'RTM manager must be joined to a channel before updating STT data' ) }) it('should handle empty data gracefully', async () => { await manager.updateSttData({}) // 验证存储方法没有被调用(因为数据为空) const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.storage.setChannelMetadata).not.toHaveBeenCalled() }) }) describe('updateLanguages', () => { beforeEach(async () => { await manager.join({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) }) it('should update languages successfully', async () => { const updatingHandler = vi.fn() const updatedHandler = vi.fn() manager.on('languagesUpdating', updatingHandler) manager.on('languagesUpdated', updatedHandler) const languages = [ { source: 'en-US', target: ['zh-CN', 'ja-JP'] }, { source: 'zh-CN', target: ['en-US'] }, ] await manager.updateLanguages(languages) expect(updatingHandler).toHaveBeenCalledTimes(1) expect(updatingHandler).toHaveBeenCalledWith({ languages }) expect(updatedHandler).toHaveBeenCalledTimes(1) expect(updatedHandler).toHaveBeenCalledWith({ languages }) // 验证 updateSttData 被正确调用 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith( 'test-channel', 'MESSAGE', expect.arrayContaining([ { key: 'transcribe1', value: expect.any(String) }, { key: 'translate1List', value: expect.any(String) }, { key: 'transcribe2', value: expect.any(String) }, { key: 'translate2List', value: expect.any(String) }, ]) ) }) it('should throw error when not joined', async () => { const unjoinedManager = new RtmManagerAdapter(mockAppId) const languages = [{ source: 'en-US' }] await expect(unjoinedManager.updateLanguages(languages)).rejects.toThrow(SttError) await expect(unjoinedManager.updateLanguages(languages)).rejects.toThrow( 'RTM manager must be joined to a channel before updating languages' ) }) it('should handle empty languages array', async () => { await manager.updateLanguages([]) // 验证 updateSttData 被调用但数据为空 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith( 'test-channel', 'MESSAGE', expect.arrayContaining([ { key: 'transcribe1', value: '""' }, { key: 'translate1List', value: '[]' }, { key: 'transcribe2', value: '""' }, { key: 'translate2List', value: '[]' }, ]) ) }) }) describe('acquireLock', () => { beforeEach(async () => { await manager.join({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) }) it('should acquire lock successfully', async () => { const acquiringHandler = vi.fn() const acquiredHandler = vi.fn() manager.on('lockAcquiring', acquiringHandler) manager.on('lockAcquired', acquiredHandler) await manager.acquireLock() expect(acquiringHandler).toHaveBeenCalledTimes(1) expect(acquiredHandler).toHaveBeenCalledTimes(1) // 验证锁方法被正确调用 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.lock.acquireLock).toHaveBeenCalledWith( 'test-channel', 'MESSAGE', 'lock_stt' ) }) it('should throw error when not joined', async () => { const unjoinedManager = new RtmManagerAdapter(mockAppId) await expect(unjoinedManager.acquireLock()).rejects.toThrow(SttError) await expect(unjoinedManager.acquireLock()).rejects.toThrow( 'RTM manager must be joined to a channel before acquiring lock' ) }) }) describe('releaseLock', () => { beforeEach(async () => { await manager.join({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) }) it('should release lock successfully', async () => { const releasingHandler = vi.fn() const releasedHandler = vi.fn() manager.on('lockReleasing', releasingHandler) manager.on('lockReleased', releasedHandler) await manager.releaseLock() expect(releasingHandler).toHaveBeenCalledTimes(1) expect(releasedHandler).toHaveBeenCalledTimes(1) // 验证锁方法被正确调用 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.lock.releaseLock).toHaveBeenCalledWith( 'test-channel', 'MESSAGE', 'lock_stt' ) }) it('should throw error when not joined', async () => { const unjoinedManager = new RtmManagerAdapter(mockAppId) await expect(unjoinedManager.releaseLock()).rejects.toThrow(SttError) await expect(unjoinedManager.releaseLock()).rejects.toThrow( 'RTM manager must be joined to a channel before releasing lock' ) }) }) describe('destroy', () => { it('should destroy manager successfully', async () => { await manager.join({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) const destroyingHandler = vi.fn() const destroyedHandler = vi.fn() manager.on('destroying', destroyingHandler) manager.on('destroyed', destroyedHandler) await manager.destroy() expect(manager.isJoined).toBe(false) expect(manager.config).toBeUndefined() expect(manager.userList).toHaveLength(0) expect(destroyingHandler).toHaveBeenCalledTimes(1) expect(destroyedHandler).toHaveBeenCalledTimes(1) // 验证 RTM 客户端被正确销毁 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value expect(mockClient.logout).toHaveBeenCalledTimes(1) }) it('should handle destroy when not joined', async () => { await expect(manager.destroy()).resolves.not.toThrow() }) it('should remove all event listeners', async () => { await manager.join({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) const testHandler = vi.fn() manager.on('connected', testHandler) await manager.destroy() // 验证事件监听器被移除 manager.emit('connected', { channel: 'test', userId: 'test' }) expect(testHandler).not.toHaveBeenCalled() }) }) describe('event handling', () => { beforeEach(async () => { await manager.join({ channel: 'test-channel', userId: 'test-user', userName: 'Test User', }) }) it('should handle presence events correctly', async () => { const userListChangedHandler = vi.fn() manager.on('userListChanged', userListChangedHandler) // 模拟 presence 事件 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value const presenceHandler = mockClient.addEventListener.mock.calls.find( (call: any) => call[0] === 'presence' )[1] // 模拟 SNAPSHOT 事件 presenceHandler({ channelName: 'test-channel', eventType: 'SNAPSHOT', snapshot: [ { states: { type: 'UserInfo', userId: 'other-user', userName: 'Other User', }, }, ], }) expect(userListChangedHandler).toHaveBeenCalledWith( expect.arrayContaining([ { userId: 'test-user', userName: 'Test User' }, { userId: 'other-user', userName: 'Other User' }, ]) ) }) it('should handle storage events correctly', async () => { const languagesChangedHandler = vi.fn() const sttDataChangedHandler = vi.fn() manager.on('languagesChanged', languagesChangedHandler) manager.on('sttDataChanged', sttDataChangedHandler) // 模拟 storage 事件 const mockClient = (AgoraRTM.RTM as any).mock.results[0].value const storageHandler = mockClient.addEventListener.mock.calls.find( (call: any) => call[0] === 'storage' )[1] // 模拟 UPDATE 事件 storageHandler({ channelName: 'test-channel', eventType: 'UPDATE', data: { metadata: { transcribe1: { value: '"en-US"' }, translate1List: { value: '["zh-CN","ja-JP"]' }, transcribe2: { value: '"zh-CN"' }, translate2List: { value: '["en-US"]' }, status: { value: '"start"' }, taskId: { value: '"test-task-id"' }, }, }, }) expect(languagesChangedHandler).toHaveBeenCalledWith({ transcribe1: 'en-US', translate1List: ['zh-CN', 'ja-JP'], transcribe2: 'zh-CN', translate2List: ['en-US'], }) expect(sttDataChangedHandler).toHaveBeenCalledWith({ status: 'start', taskId: 'test-task-id', }) }) }) })