2
0

rtm-manager-adapter.test.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  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 mockRTM = vi.mocked(AgoraRTM.RTM)
  95. const mockClient = mockRTM.mock.results[0].value
  96. expect(mockClient.addEventListener).toHaveBeenCalledWith('status', expect.any(Function))
  97. expect(mockClient.addEventListener).toHaveBeenCalledWith('presence', expect.any(Function))
  98. expect(mockClient.addEventListener).toHaveBeenCalledWith('storage', expect.any(Function))
  99. })
  100. })
  101. describe('updateSttData', () => {
  102. beforeEach(async () => {
  103. await manager.join({
  104. channel: 'test-channel',
  105. userId: 'test-user',
  106. userName: 'Test User',
  107. })
  108. })
  109. it('should update STT data successfully', async () => {
  110. const updatingHandler = vi.fn()
  111. const updatedHandler = vi.fn()
  112. manager.on('metadataUpdating', updatingHandler)
  113. manager.on('metadataUpdated', updatedHandler)
  114. const data = {
  115. status: 'start',
  116. taskId: 'test-task-id',
  117. token: 'test-token',
  118. startTime: Date.now(),
  119. duration: 3600000,
  120. }
  121. await manager.updateSttData(data)
  122. expect(updatingHandler).toHaveBeenCalledTimes(1)
  123. expect(updatingHandler).toHaveBeenCalledWith({ data })
  124. expect(updatedHandler).toHaveBeenCalledTimes(1)
  125. expect(updatedHandler).toHaveBeenCalledWith({ data })
  126. // 验证存储方法被正确调用
  127. const mockRTM = vi.mocked(AgoraRTM.RTM)
  128. const mockClient = mockRTM.mock.results[0].value
  129. expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith(
  130. 'test-channel',
  131. 'MESSAGE',
  132. expect.arrayContaining([
  133. { key: 'status', value: expect.any(String) },
  134. { key: 'taskId', value: expect.any(String) },
  135. { key: 'token', value: expect.any(String) },
  136. { key: 'startTime', value: expect.any(String) },
  137. { key: 'duration', value: expect.any(String) },
  138. ])
  139. )
  140. })
  141. it('should throw error when not joined', async () => {
  142. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  143. const data = {
  144. status: 'start',
  145. taskId: 'test-task-id',
  146. }
  147. await expect(unjoinedManager.updateSttData(data)).rejects.toThrow(SttError)
  148. await expect(unjoinedManager.updateSttData(data)).rejects.toThrow(
  149. 'RTM manager must be joined to a channel before updating STT data'
  150. )
  151. })
  152. it('should handle empty data gracefully', async () => {
  153. // 重置存储方法的调用记录
  154. const mockRTM = vi.mocked(AgoraRTM.RTM)
  155. const mockClient = mockRTM.mock.results[0].value
  156. mockClient.storage.setChannelMetadata.mockClear()
  157. await manager.updateSttData({})
  158. // 验证存储方法没有被调用(因为数据为空)
  159. expect(mockClient.storage.setChannelMetadata).not.toHaveBeenCalled()
  160. })
  161. })
  162. describe('updateLanguages', () => {
  163. beforeEach(async () => {
  164. await manager.join({
  165. channel: 'test-channel',
  166. userId: 'test-user',
  167. userName: 'Test User',
  168. })
  169. })
  170. it('should update languages successfully', async () => {
  171. const updatingHandler = vi.fn()
  172. const updatedHandler = vi.fn()
  173. manager.on('languagesUpdating', updatingHandler)
  174. manager.on('languagesUpdated', updatedHandler)
  175. const languages = [
  176. { source: 'en-US', target: ['zh-CN', 'ja-JP'] },
  177. { source: 'zh-CN', target: ['en-US'] },
  178. ]
  179. await manager.updateLanguages(languages)
  180. expect(updatingHandler).toHaveBeenCalledTimes(1)
  181. expect(updatingHandler).toHaveBeenCalledWith({ languages })
  182. expect(updatedHandler).toHaveBeenCalledTimes(1)
  183. expect(updatedHandler).toHaveBeenCalledWith({ languages })
  184. // 验证 updateSttData 被正确调用
  185. const mockRTM = vi.mocked(AgoraRTM.RTM)
  186. const mockClient = mockRTM.mock.results[0].value
  187. expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith(
  188. 'test-channel',
  189. 'MESSAGE',
  190. expect.arrayContaining([
  191. { key: 'transcribe1', value: expect.any(String) },
  192. { key: 'translate1List', value: expect.any(String) },
  193. { key: 'transcribe2', value: expect.any(String) },
  194. { key: 'translate2List', value: expect.any(String) },
  195. ])
  196. )
  197. })
  198. it('should throw error when not joined', async () => {
  199. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  200. const languages = [{ source: 'en-US' }]
  201. await expect(unjoinedManager.updateLanguages(languages)).rejects.toThrow(SttError)
  202. await expect(unjoinedManager.updateLanguages(languages)).rejects.toThrow(
  203. 'RTM manager must be joined to a channel before updating languages'
  204. )
  205. })
  206. it('should handle empty languages array', async () => {
  207. await manager.updateLanguages([])
  208. // 验证 updateSttData 被调用但数据为空
  209. const mockRTM = vi.mocked(AgoraRTM.RTM)
  210. const mockClient = mockRTM.mock.results[0].value
  211. expect(mockClient.storage.setChannelMetadata).toHaveBeenCalledWith(
  212. 'test-channel',
  213. 'MESSAGE',
  214. expect.arrayContaining([
  215. { key: 'transcribe1', value: '""' },
  216. { key: 'translate1List', value: '[]' },
  217. { key: 'transcribe2', value: '""' },
  218. { key: 'translate2List', value: '[]' },
  219. ])
  220. )
  221. })
  222. })
  223. describe('acquireLock', () => {
  224. beforeEach(async () => {
  225. await manager.join({
  226. channel: 'test-channel',
  227. userId: 'test-user',
  228. userName: 'Test User',
  229. })
  230. })
  231. it('should acquire lock successfully', async () => {
  232. const acquiringHandler = vi.fn()
  233. const acquiredHandler = vi.fn()
  234. manager.on('lockAcquiring', acquiringHandler)
  235. manager.on('lockAcquired', acquiredHandler)
  236. await manager.acquireLock()
  237. expect(acquiringHandler).toHaveBeenCalledTimes(1)
  238. expect(acquiredHandler).toHaveBeenCalledTimes(1)
  239. // 验证锁方法被正确调用
  240. const mockRTM = vi.mocked(AgoraRTM.RTM)
  241. const mockClient = mockRTM.mock.results[0].value
  242. expect(mockClient.lock.acquireLock).toHaveBeenCalledWith(
  243. 'test-channel',
  244. 'MESSAGE',
  245. 'lock_stt'
  246. )
  247. })
  248. it('should throw error when not joined', async () => {
  249. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  250. await expect(unjoinedManager.acquireLock()).rejects.toThrow(SttError)
  251. await expect(unjoinedManager.acquireLock()).rejects.toThrow(
  252. 'RTM manager must be joined to a channel before acquiring lock'
  253. )
  254. })
  255. })
  256. describe('releaseLock', () => {
  257. beforeEach(async () => {
  258. await manager.join({
  259. channel: 'test-channel',
  260. userId: 'test-user',
  261. userName: 'Test User',
  262. })
  263. })
  264. it('should release lock successfully', async () => {
  265. const releasingHandler = vi.fn()
  266. const releasedHandler = vi.fn()
  267. manager.on('lockReleasing', releasingHandler)
  268. manager.on('lockReleased', releasedHandler)
  269. await manager.releaseLock()
  270. expect(releasingHandler).toHaveBeenCalledTimes(1)
  271. expect(releasedHandler).toHaveBeenCalledTimes(1)
  272. // 验证锁方法被正确调用
  273. const mockRTM = vi.mocked(AgoraRTM.RTM)
  274. const mockClient = mockRTM.mock.results[0].value
  275. expect(mockClient.lock.releaseLock).toHaveBeenCalledWith(
  276. 'test-channel',
  277. 'MESSAGE',
  278. 'lock_stt'
  279. )
  280. })
  281. it('should throw error when not joined', async () => {
  282. const unjoinedManager = new RtmManagerAdapter(mockAppId)
  283. await expect(unjoinedManager.releaseLock()).rejects.toThrow(SttError)
  284. await expect(unjoinedManager.releaseLock()).rejects.toThrow(
  285. 'RTM manager must be joined to a channel before releasing lock'
  286. )
  287. })
  288. })
  289. describe('destroy', () => {
  290. it('should destroy manager successfully', async () => {
  291. // 重置模拟调用次数
  292. vi.clearAllMocks()
  293. await manager.join({
  294. channel: 'test-channel',
  295. userId: 'test-user',
  296. userName: 'Test User',
  297. })
  298. const destroyingHandler = vi.fn()
  299. const destroyedHandler = vi.fn()
  300. manager.on('destroying', destroyingHandler)
  301. manager.on('destroyed', destroyedHandler)
  302. await manager.destroy()
  303. expect(manager.isJoined).toBe(false)
  304. expect(manager.config).toBeUndefined()
  305. expect(manager.userList).toHaveLength(0)
  306. expect(destroyingHandler).toHaveBeenCalledTimes(1)
  307. expect(destroyedHandler).toHaveBeenCalledTimes(1)
  308. // 验证 RTM 客户端被正确销毁
  309. const mockRTM = vi.mocked(AgoraRTM.RTM)
  310. const mockClient = mockRTM.mock.results[0].value
  311. expect(mockClient.logout).toHaveBeenCalledTimes(1)
  312. })
  313. it('should handle destroy when not joined', async () => {
  314. await expect(manager.destroy()).resolves.not.toThrow()
  315. })
  316. it('should remove all event listeners', async () => {
  317. await manager.join({
  318. channel: 'test-channel',
  319. userId: 'test-user',
  320. userName: 'Test User',
  321. })
  322. const testHandler = vi.fn()
  323. manager.on('connected', testHandler)
  324. await manager.destroy()
  325. // 验证事件监听器被移除
  326. manager.emit('connected', { channel: 'test', userId: 'test' })
  327. expect(testHandler).not.toHaveBeenCalled()
  328. })
  329. })
  330. describe('event handling', () => {
  331. beforeEach(async () => {
  332. await manager.join({
  333. channel: 'test-channel',
  334. userId: 'test-user',
  335. userName: 'Test User',
  336. })
  337. })
  338. it('should handle presence events correctly', async () => {
  339. const userListChangedHandler = vi.fn()
  340. manager.on('userListChanged', userListChangedHandler)
  341. // 模拟 presence 事件
  342. const mockRTM = vi.mocked(AgoraRTM.RTM)
  343. const mockClient = mockRTM.mock.results[0].value
  344. const presenceHandler = mockClient.eventHandlers?.presence
  345. if (presenceHandler) {
  346. // 模拟 SNAPSHOT 事件
  347. presenceHandler({
  348. channelName: 'test-channel',
  349. eventType: 'SNAPSHOT',
  350. snapshot: [
  351. {
  352. states: {
  353. type: 'UserInfo',
  354. userId: 'other-user',
  355. userName: 'Other User',
  356. },
  357. },
  358. ],
  359. })
  360. expect(userListChangedHandler).toHaveBeenCalledWith(
  361. expect.arrayContaining([
  362. { userId: 'test-user', userName: 'Test User' },
  363. { userId: 'other-user', userName: 'Other User' },
  364. ])
  365. )
  366. }
  367. })
  368. it('should handle storage events correctly', async () => {
  369. const languagesChangedHandler = vi.fn()
  370. const sttDataChangedHandler = vi.fn()
  371. manager.on('languagesChanged', languagesChangedHandler)
  372. manager.on('sttDataChanged', sttDataChangedHandler)
  373. // 模拟 storage 事件
  374. const mockRTM = vi.mocked(AgoraRTM.RTM)
  375. const mockClient = mockRTM.mock.results[0].value
  376. const storageHandler = mockClient.eventHandlers?.storage
  377. if (storageHandler) {
  378. // 模拟 UPDATE 事件
  379. storageHandler({
  380. channelName: 'test-channel',
  381. eventType: 'UPDATE',
  382. data: {
  383. metadata: {
  384. transcribe1: { value: '"en-US"' },
  385. translate1List: { value: '["zh-CN","ja-JP"]' },
  386. transcribe2: { value: '"zh-CN"' },
  387. translate2List: { value: '["en-US"]' },
  388. status: { value: '"start"' },
  389. taskId: { value: '"test-task-id"' },
  390. },
  391. },
  392. })
  393. expect(languagesChangedHandler).toHaveBeenCalledWith({
  394. transcribe1: 'en-US',
  395. translate1List: ['zh-CN', 'ja-JP'],
  396. transcribe2: 'zh-CN',
  397. translate2List: ['en-US'],
  398. })
  399. expect(sttDataChangedHandler).toHaveBeenCalledWith({
  400. status: 'start',
  401. taskId: 'test-task-id',
  402. })
  403. }
  404. })
  405. })
  406. })