rtm-manager-adapter.test.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
  2. import { RtmManagerAdapter } from '../../src/managers/rtm-manager-adapter'
  3. import { SttError } from '../../src/core/stt-error'
  4. import AgoraRTM from 'agora-rtm'
  5. describe('RtmManagerAdapter', () => {
  6. let manager: RtmManagerAdapter
  7. const mockAppId = 'test-app-id'
  8. beforeEach(() => {
  9. manager = new RtmManagerAdapter(mockAppId)
  10. })
  11. afterEach(async () => {
  12. if (manager.isJoined) {
  13. await manager.destroy()
  14. }
  15. })
  16. describe('join', () => {
  17. it('should join successfully with valid config', async () => {
  18. const config = {
  19. channel: 'test-channel',
  20. userId: 'test-user',
  21. userName: 'Test User',
  22. }
  23. await manager.join(config)
  24. expect(manager.isJoined).toBe(true)
  25. expect(manager.config).toEqual(config)
  26. expect(manager.userId).toBe('test-user')
  27. expect(manager.channel).toBe('test-channel')
  28. expect(manager.userList).toHaveLength(1)
  29. expect(manager.userList[0]).toEqual({
  30. userId: 'test-user',
  31. userName: 'Test User',
  32. })
  33. // 验证 RTM 客户端被正确创建
  34. expect(AgoraRTM.RTM).toHaveBeenCalledWith(mockAppId, 'test-user', {})
  35. })
  36. it('should throw error when config is invalid', async () => {
  37. const config = {
  38. channel: '',
  39. userId: '',
  40. userName: '',
  41. }
  42. await expect(manager.join(config)).rejects.toThrow(SttError)
  43. await expect(manager.join(config)).rejects.toThrow(
  44. 'Missing required configuration parameters'
  45. )
  46. })
  47. it('should throw error when already joined', async () => {
  48. const config = {
  49. channel: 'test-channel',
  50. userId: 'test-user',
  51. userName: 'Test User',
  52. }
  53. await manager.join(config)
  54. await expect(manager.join(config)).rejects.toThrow(SttError)
  55. await expect(manager.join(config)).rejects.toThrow(
  56. 'RTM manager is already joined to a channel'
  57. )
  58. })
  59. it('should throw error when appId is not provided', async () => {
  60. const managerWithoutAppId = new RtmManagerAdapter()
  61. const config = {
  62. channel: 'test-channel',
  63. userId: 'test-user',
  64. userName: 'Test User',
  65. }
  66. await expect(managerWithoutAppId.join(config)).rejects.toThrow(SttError)
  67. await expect(managerWithoutAppId.join(config)).rejects.toThrow(
  68. 'App ID is required for RTM connection'
  69. )
  70. })
  71. it('should emit connected event', async () => {
  72. const connectedHandler = vi.fn()
  73. manager.on('connected', connectedHandler)
  74. const config = {
  75. channel: 'test-channel',
  76. userId: 'test-user',
  77. userName: 'Test User',
  78. }
  79. await manager.join(config)
  80. expect(connectedHandler).toHaveBeenCalledTimes(1)
  81. expect(connectedHandler).toHaveBeenCalledWith({
  82. channel: 'test-channel',
  83. userId: 'test-user',
  84. })
  85. })
  86. it('should setup event listeners correctly', async () => {
  87. const config = {
  88. channel: 'test-channel',
  89. userId: 'test-user',
  90. userName: 'Test User',
  91. }
  92. await manager.join(config)
  93. // 获取创建的 RTM 客户端实例
  94. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  95. expect(mockClient.addEventListener).toHaveBeenCalledWith('status', expect.any(Function))
  96. expect(mockClient.addEventListener).toHaveBeenCalledWith('presence', expect.any(Function))
  97. expect(mockClient.addEventListener).toHaveBeenCalledWith('storage', expect.any(Function))
  98. })
  99. })
  100. describe('updateSttData', () => {
  101. beforeEach(async () => {
  102. await manager.join({
  103. channel: 'test-channel',
  104. userId: 'test-user',
  105. userName: 'Test User',
  106. })
  107. })
  108. it('should update STT data successfully', async () => {
  109. const updatingHandler = vi.fn()
  110. const updatedHandler = vi.fn()
  111. manager.on('metadataUpdating', updatingHandler)
  112. manager.on('metadataUpdated', updatedHandler)
  113. const data = {
  114. status: 'start',
  115. taskId: 'test-task-id',
  116. token: 'test-token',
  117. startTime: Date.now(),
  118. duration: 3600000,
  119. }
  120. await manager.updateSttData(data)
  121. expect(updatingHandler).toHaveBeenCalledTimes(1)
  122. expect(updatingHandler).toHaveBeenCalledWith({ data })
  123. expect(updatedHandler).toHaveBeenCalledTimes(1)
  124. expect(updatedHandler).toHaveBeenCalledWith({ data })
  125. // 验证存储方法被正确调用
  126. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  127. expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith(
  128. 'test-channel',
  129. 'MESSAGE',
  130. expect.arrayContaining([
  131. { key: 'status', value: expect.any(String) },
  132. { key: 'taskId', value: expect.any(String) },
  133. { key: 'token', value: expect.any(String) },
  134. { key: 'startTime', value: expect.any(String) },
  135. { key: 'duration', value: expect.any(String) },
  136. ])
  137. )
  138. })
  139. it('should throw error when not joined', async () => {
  140. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  141. const data = {
  142. status: 'start',
  143. taskId: 'test-task-id',
  144. }
  145. await expect(unjoinedManager.updateSttData(data)).rejects.toThrow(SttError)
  146. await expect(unjoinedManager.updateSttData(data)).rejects.toThrow(
  147. 'RTM manager must be joined to a channel before updating STT data'
  148. )
  149. })
  150. it('should handle empty data gracefully', async () => {
  151. await manager.updateSttData({})
  152. // 验证存储方法没有被调用(因为数据为空)
  153. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  154. expect(mockClient.storage.setChannelMetadata).not.toHaveBeenCalled()
  155. })
  156. })
  157. describe('updateLanguages', () => {
  158. beforeEach(async () => {
  159. await manager.join({
  160. channel: 'test-channel',
  161. userId: 'test-user',
  162. userName: 'Test User',
  163. })
  164. })
  165. it('should update languages successfully', async () => {
  166. const updatingHandler = vi.fn()
  167. const updatedHandler = vi.fn()
  168. manager.on('languagesUpdating', updatingHandler)
  169. manager.on('languagesUpdated', updatedHandler)
  170. const languages = [
  171. { source: 'en-US', target: ['zh-CN', 'ja-JP'] },
  172. { source: 'zh-CN', target: ['en-US'] },
  173. ]
  174. await manager.updateLanguages(languages)
  175. expect(updatingHandler).toHaveBeenCalledTimes(1)
  176. expect(updatingHandler).toHaveBeenCalledWith({ languages })
  177. expect(updatedHandler).toHaveBeenCalledTimes(1)
  178. expect(updatedHandler).toHaveBeenCalledWith({ languages })
  179. // 验证 updateSttData 被正确调用
  180. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  181. expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith(
  182. 'test-channel',
  183. 'MESSAGE',
  184. expect.arrayContaining([
  185. { key: 'transcribe1', value: expect.any(String) },
  186. { key: 'translate1List', value: expect.any(String) },
  187. { key: 'transcribe2', value: expect.any(String) },
  188. { key: 'translate2List', value: expect.any(String) },
  189. ])
  190. )
  191. })
  192. it('should throw error when not joined', async () => {
  193. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  194. const languages = [{ source: 'en-US' }]
  195. await expect(unjoinedManager.updateLanguages(languages)).rejects.toThrow(SttError)
  196. await expect(unjoinedManager.updateLanguages(languages)).rejects.toThrow(
  197. 'RTM manager must be joined to a channel before updating languages'
  198. )
  199. })
  200. it('should handle empty languages array', async () => {
  201. await manager.updateLanguages([])
  202. // 验证 updateSttData 被调用但数据为空
  203. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  204. expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith(
  205. 'test-channel',
  206. 'MESSAGE',
  207. expect.arrayContaining([
  208. { key: 'transcribe1', value: '""' },
  209. { key: 'translate1List', value: '[]' },
  210. { key: 'transcribe2', value: '""' },
  211. { key: 'translate2List', value: '[]' },
  212. ])
  213. )
  214. })
  215. })
  216. describe('acquireLock', () => {
  217. beforeEach(async () => {
  218. await manager.join({
  219. channel: 'test-channel',
  220. userId: 'test-user',
  221. userName: 'Test User',
  222. })
  223. })
  224. it('should acquire lock successfully', async () => {
  225. const acquiringHandler = vi.fn()
  226. const acquiredHandler = vi.fn()
  227. manager.on('lockAcquiring', acquiringHandler)
  228. manager.on('lockAcquired', acquiredHandler)
  229. await manager.acquireLock()
  230. expect(acquiringHandler).toHaveBeenCalledTimes(1)
  231. expect(acquiredHandler).toHaveBeenCalledTimes(1)
  232. // 验证锁方法被正确调用
  233. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  234. expect(mockClient.lock.acquireLock).toHaveBeenCalledWith(
  235. 'test-channel',
  236. 'MESSAGE',
  237. 'lock_stt'
  238. )
  239. })
  240. it('should throw error when not joined', async () => {
  241. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  242. await expect(unjoinedManager.acquireLock()).rejects.toThrow(SttError)
  243. await expect(unjoinedManager.acquireLock()).rejects.toThrow(
  244. 'RTM manager must be joined to a channel before acquiring lock'
  245. )
  246. })
  247. })
  248. describe('releaseLock', () => {
  249. beforeEach(async () => {
  250. await manager.join({
  251. channel: 'test-channel',
  252. userId: 'test-user',
  253. userName: 'Test User',
  254. })
  255. })
  256. it('should release lock successfully', async () => {
  257. const releasingHandler = vi.fn()
  258. const releasedHandler = vi.fn()
  259. manager.on('lockReleasing', releasingHandler)
  260. manager.on('lockReleased', releasedHandler)
  261. await manager.releaseLock()
  262. expect(releasingHandler).toHaveBeenCalledTimes(1)
  263. expect(releasedHandler).toHaveBeenCalledTimes(1)
  264. // 验证锁方法被正确调用
  265. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  266. expect(mockClient.lock.releaseLock).toHaveBeenCalledWith(
  267. 'test-channel',
  268. 'MESSAGE',
  269. 'lock_stt'
  270. )
  271. })
  272. it('should throw error when not joined', async () => {
  273. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  274. await expect(unjoinedManager.releaseLock()).rejects.toThrow(SttError)
  275. await expect(unjoinedManager.releaseLock()).rejects.toThrow(
  276. 'RTM manager must be joined to a channel before releasing lock'
  277. )
  278. })
  279. })
  280. describe('destroy', () => {
  281. it('should destroy manager successfully', async () => {
  282. await manager.join({
  283. channel: 'test-channel',
  284. userId: 'test-user',
  285. userName: 'Test User',
  286. })
  287. const destroyingHandler = vi.fn()
  288. const destroyedHandler = vi.fn()
  289. manager.on('destroying', destroyingHandler)
  290. manager.on('destroyed', destroyedHandler)
  291. await manager.destroy()
  292. expect(manager.isJoined).toBe(false)
  293. expect(manager.config).toBeUndefined()
  294. expect(manager.userList).toHaveLength(0)
  295. expect(destroyingHandler).toHaveBeenCalledTimes(1)
  296. expect(destroyedHandler).toHaveBeenCalledTimes(1)
  297. // 验证 RTM 客户端被正确销毁
  298. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  299. expect(mockClient.logout).toHaveBeenCalledTimes(1)
  300. })
  301. it('should handle destroy when not joined', async () => {
  302. await expect(manager.destroy()).resolves.not.toThrow()
  303. })
  304. it('should remove all event listeners', async () => {
  305. await manager.join({
  306. channel: 'test-channel',
  307. userId: 'test-user',
  308. userName: 'Test User',
  309. })
  310. const testHandler = vi.fn()
  311. manager.on('connected', testHandler)
  312. await manager.destroy()
  313. // 验证事件监听器被移除
  314. manager.emit('connected', { channel: 'test', userId: 'test' })
  315. expect(testHandler).not.toHaveBeenCalled()
  316. })
  317. })
  318. describe('event handling', () => {
  319. beforeEach(async () => {
  320. await manager.join({
  321. channel: 'test-channel',
  322. userId: 'test-user',
  323. userName: 'Test User',
  324. })
  325. })
  326. it('should handle presence events correctly', async () => {
  327. const userListChangedHandler = vi.fn()
  328. manager.on('userListChanged', userListChangedHandler)
  329. // 模拟 presence 事件
  330. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  331. const presenceHandler = mockClient.addEventListener.mock.calls.find(
  332. (call: any) => call[0] === 'presence'
  333. )[1]
  334. // 模拟 SNAPSHOT 事件
  335. presenceHandler({
  336. channelName: 'test-channel',
  337. eventType: 'SNAPSHOT',
  338. snapshot: [
  339. {
  340. states: {
  341. type: 'UserInfo',
  342. userId: 'other-user',
  343. userName: 'Other User',
  344. },
  345. },
  346. ],
  347. })
  348. expect(userListChangedHandler).toHaveBeenCalledWith(
  349. expect.arrayContaining([
  350. { userId: 'test-user', userName: 'Test User' },
  351. { userId: 'other-user', userName: 'Other User' },
  352. ])
  353. )
  354. })
  355. it('should handle storage events correctly', async () => {
  356. const languagesChangedHandler = vi.fn()
  357. const sttDataChangedHandler = vi.fn()
  358. manager.on('languagesChanged', languagesChangedHandler)
  359. manager.on('sttDataChanged', sttDataChangedHandler)
  360. // 模拟 storage 事件
  361. const mockClient = (AgoraRTM.RTM as any).mock.results[0].value
  362. const storageHandler = mockClient.addEventListener.mock.calls.find(
  363. (call: any) => call[0] === 'storage'
  364. )[1]
  365. // 模拟 UPDATE 事件
  366. storageHandler({
  367. channelName: 'test-channel',
  368. eventType: 'UPDATE',
  369. data: {
  370. metadata: {
  371. transcribe1: { value: '"en-US"' },
  372. translate1List: { value: '["zh-CN","ja-JP"]' },
  373. transcribe2: { value: '"zh-CN"' },
  374. translate2List: { value: '["en-US"]' },
  375. status: { value: '"start"' },
  376. taskId: { value: '"test-task-id"' },
  377. },
  378. },
  379. })
  380. expect(languagesChangedHandler).toHaveBeenCalledWith({
  381. transcribe1: 'en-US',
  382. translate1List: ['zh-CN', 'ja-JP'],
  383. transcribe2: 'zh-CN',
  384. translate2List: ['en-US'],
  385. })
  386. expect(sttDataChangedHandler).toHaveBeenCalledWith({
  387. status: 'start',
  388. taskId: 'test-task-id',
  389. })
  390. })
  391. })
  392. })