agora-token.integration.test.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest'
  2. import { testClient } from 'hono/testing'
  3. import { agoraApiRoutes } from '@/server/api'
  4. import { AgoraTokenService } from '@/server/modules/agora/agora-token.service'
  5. import { authMiddleware } from '@/server/middleware/auth.middleware'
  6. // 模拟模块
  7. vi.mock('@/server/modules/agora/agora-token.service', () => ({
  8. AgoraTokenService: vi.fn().mockImplementation(() => ({
  9. generateRtcToken: vi.fn().mockReturnValue('mock-rtc-token'),
  10. generateRtmToken: vi.fn().mockReturnValue('mock-rtm-token'),
  11. validateTokenParams: vi.fn().mockImplementation((type: 'rtc' | 'rtm', channel?: string, userId?: string) => {
  12. if (type === 'rtc' && !channel) {
  13. throw new Error('RTC Token需要提供channel参数')
  14. }
  15. if (type === 'rtm' && !userId) {
  16. throw new Error('RTM Token需要提供userId参数')
  17. }
  18. if (channel && channel.length > 64) {
  19. throw new Error('频道名称长度不能超过64个字符')
  20. }
  21. if (userId && userId.length > 64) {
  22. throw new Error('用户ID长度不能超过64个字符')
  23. }
  24. }),
  25. getTokenInfo: vi.fn().mockImplementation((token, type) => ({
  26. token,
  27. type,
  28. expiresAt: Math.floor(Date.now() / 1000) + 3600,
  29. expiresIn: 3600,
  30. generatedAt: Math.floor(Date.now() / 1000)
  31. }))
  32. }))
  33. }));
  34. vi.mock('@/server/middleware/auth.middleware');
  35. // Mock用户数据
  36. const mockUser = {
  37. id: 1,
  38. username: 'testuser',
  39. password: 'password123',
  40. phone: null,
  41. email: 'test@example.com',
  42. nickname: null,
  43. name: null,
  44. avatarFileId: null,
  45. avatarFile: null,
  46. isDisabled: 0,
  47. isDeleted: 0,
  48. roles: [],
  49. createdAt: new Date(),
  50. updatedAt: new Date()
  51. }
  52. describe('Agora Token API 集成测试', () => {
  53. let client: ReturnType<typeof testClient<typeof agoraApiRoutes>>['api']['v1']
  54. beforeAll(() => {
  55. // 设置测试环境变量
  56. process.env.AGORA_APP_ID = 'test-app-id'
  57. process.env.AGORA_APP_SECRET = 'test-app-secret'
  58. process.env.AGORA_TOKEN_EXPIRY = '3600'
  59. // Mock auth middleware
  60. vi.mocked(authMiddleware).mockImplementation(async (c, next) => {
  61. const authHeader = c.req.header('Authorization')
  62. if (!authHeader) {
  63. return c.json({ message: 'Authorization header missing' }, 401)
  64. }
  65. c.set('user', mockUser)
  66. await next()
  67. })
  68. // 创建测试客户端
  69. client = testClient(agoraApiRoutes).api.v1
  70. })
  71. afterAll(() => {
  72. vi.clearAllMocks()
  73. })
  74. test('AgoraTokenService参数验证功能', () => {
  75. const agoraTokenService = new AgoraTokenService()
  76. // 测试RTC Token参数验证
  77. expect(() => {
  78. agoraTokenService.validateTokenParams('rtc', 'test-channel')
  79. }).not.toThrow()
  80. // 测试RTC Token缺少channel参数
  81. expect(() => {
  82. agoraTokenService.validateTokenParams('rtc')
  83. }).toThrow('RTC Token需要提供channel参数')
  84. // 测试RTM Token参数验证
  85. expect(() => {
  86. agoraTokenService.validateTokenParams('rtm', undefined, 'test-user')
  87. }).not.toThrow()
  88. // 测试RTM Token缺少userId参数
  89. expect(() => {
  90. agoraTokenService.validateTokenParams('rtm')
  91. }).toThrow('RTM Token需要提供userId参数')
  92. // 测试频道名称长度限制
  93. expect(() => {
  94. agoraTokenService.validateTokenParams('rtc', 'a'.repeat(65))
  95. }).toThrow('频道名称长度不能超过64个字符')
  96. // 测试用户ID长度限制
  97. expect(() => {
  98. agoraTokenService.validateTokenParams('rtm', undefined, 'a'.repeat(65))
  99. }).toThrow('用户ID长度不能超过64个字符')
  100. })
  101. test('AgoraTokenService Token生成功能', () => {
  102. const agoraTokenService = new AgoraTokenService()
  103. // 测试RTC Token生成
  104. const rtcToken = agoraTokenService.generateRtcToken('test-channel', '123')
  105. expect(rtcToken).toBe('mock-rtc-token')
  106. // 测试RTM Token生成
  107. const rtmToken = agoraTokenService.generateRtmToken('test-user')
  108. expect(rtmToken).toBe('mock-rtm-token')
  109. // 测试Token信息获取
  110. const tokenInfo = agoraTokenService.getTokenInfo('test-token', 'rtc')
  111. expect(tokenInfo).toEqual({
  112. token: 'test-token',
  113. type: 'rtc',
  114. expiresAt: expect.any(Number),
  115. expiresIn: 3600,
  116. generatedAt: expect.any(Number)
  117. })
  118. })
  119. test('未认证用户访问Token API应该返回401', async () => {
  120. // 临时修改mock以模拟未认证
  121. vi.mocked(authMiddleware).mockImplementation(async (c) => {
  122. return c.json({ message: 'Authorization header missing' }, 401)
  123. })
  124. const response = await client.agora.token.$get({
  125. query: { type: 'rtc', channel: 'test-channel' }
  126. })
  127. expect(response.status).toBe(401)
  128. // 恢复mock
  129. vi.mocked(authMiddleware).mockImplementation(async (c, next) => {
  130. const authHeader = c.req.header('Authorization')
  131. if (!authHeader) {
  132. return c.json({ message: 'Authorization header missing' }, 401)
  133. }
  134. c.set('user', mockUser)
  135. await next()
  136. })
  137. })
  138. test('认证用户生成RTC Token成功', async () => {
  139. const response = await client.agora.token.$get({
  140. query: { type: 'rtc', channel: 'test-channel' }
  141. },
  142. {
  143. headers: {
  144. 'Authorization': 'Bearer test-token'
  145. }
  146. })
  147. expect(response.status).toBe(200)
  148. const data = await response.json()
  149. if ('token' in data) {
  150. expect(data.token).toBe('mock-rtc-token')
  151. expect(data.type).toBe('rtc')
  152. expect(data.expiresIn).toBe(3600)
  153. expect(data.expiresAt).toBeGreaterThan(Math.floor(Date.now() / 1000))
  154. }
  155. })
  156. test('认证用户生成RTM Token成功', async () => {
  157. const response = await client.agora.token.$get({
  158. query: { type: 'rtm', userId: 'test-user-123' }
  159. },
  160. {
  161. headers: {
  162. 'Authorization': 'Bearer test-token'
  163. }
  164. })
  165. expect(response.status).toBe(200)
  166. const data = await response.json()
  167. if ('token' in data) {
  168. expect(data.token).toBe('mock-rtm-token')
  169. expect(data.type).toBe('rtm')
  170. expect(data.expiresIn).toBe(3600)
  171. }
  172. })
  173. test('路由参数验证 - 无效Token类型', async () => {
  174. // 使用无效的type参数
  175. const response = await client.agora.token.$get({
  176. query: { type: 'invalid-type' as any, channel: 'test-channel' }
  177. },
  178. {
  179. headers: {
  180. 'Authorization': 'Bearer test-token'
  181. }
  182. })
  183. // 由于Zod验证,应该返回400错误
  184. expect(response.status).toBe(400)
  185. })
  186. test('路由参数验证 - 缺少必需参数', async () => {
  187. // 测试缺少channel参数 - 业务逻辑验证应该返回400错误
  188. const response1 = await client.agora.token.$get({
  189. query: { type: 'rtc' } // 缺少channel参数
  190. },
  191. {
  192. headers: {
  193. 'Authorization': 'Bearer test-token'
  194. }
  195. })
  196. // 由于业务逻辑验证,应该返回400错误
  197. expect(response1.status).toBe(400)
  198. // 测试缺少userId参数
  199. const response2 = await client.agora.token.$get({
  200. query: { type: 'rtm' } // 缺少userId参数
  201. },
  202. {
  203. headers: {
  204. 'Authorization': 'Bearer test-token'
  205. }
  206. })
  207. // 由于业务逻辑验证,应该返回400错误
  208. expect(response2.status).toBe(400)
  209. })
  210. test('Token有效期和格式验证', async () => {
  211. const response = await client.agora.token.$get({
  212. query: { type: 'rtc', channel: 'test-channel' }
  213. },
  214. {
  215. headers: {
  216. 'Authorization': 'Bearer test-token'
  217. }
  218. })
  219. expect(response.status).toBe(200)
  220. const data = await response.json()
  221. if ('token' in data) {
  222. // 验证Token信息格式
  223. expect(data).toHaveProperty('token')
  224. expect(data).toHaveProperty('type')
  225. expect(data).toHaveProperty('expiresAt')
  226. expect(data).toHaveProperty('expiresIn')
  227. expect(data).toHaveProperty('generatedAt')
  228. // 验证时间戳格式
  229. expect(data.expiresAt).toBeGreaterThan(data.generatedAt)
  230. expect(data.expiresIn).toBe(3600)
  231. expect(data.expiresAt - data.generatedAt).toBe(data.expiresIn)
  232. }
  233. })
  234. })