agora-token.integration.test.ts 7.3 KB

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