Просмотр исходного кода

✅ test(agora): 完善Agora Token API测试套件

- 添加AgoraTokenService单元测试,覆盖参数验证和Token生成功能
- 重构集成测试,使用统一的auth middleware mock
- 增加路由参数验证测试,覆盖无效类型和缺少参数场景
- 优化测试结构,分离单元测试和集成测试关注点

♻️ refactor(agora): 简化Agora API路由配置

- 移除冗余的OpenAPI文档配置
- 简化路由注册方式,直接使用route方法挂载tokenRoutes
- 清理导出类型定义,减少不必要的类型导出
yourname 4 месяцев назад
Родитель
Сommit
3ab3eb3774
2 измененных файлов с 120 добавлено и 115 удалено
  1. 118 100
      src/server/api/agora/__tests__/agora-token.integration.test.ts
  2. 2 15
      src/server/api/agora/index.ts

+ 118 - 100
src/server/api/agora/__tests__/agora-token.integration.test.ts

@@ -2,6 +2,7 @@ import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest'
 import { testClient } from 'hono/testing'
 import { agoraApiRoutes } from '@/server/api'
 import { AgoraTokenService } from '@/server/modules/agora/agora-token.service'
+import { authMiddleware } from '@/server/middleware/auth.middleware'
 
 // 模拟AgoraTokenService
 vi.mock('@/server/modules/agora/agora-token.service', () => ({
@@ -19,6 +20,24 @@ vi.mock('@/server/modules/agora/agora-token.service', () => ({
   }))
 }))
 
+// Mock用户数据
+const mockUser = {
+  id: 1,
+  username: 'testuser',
+  password: 'password123',
+  phone: null,
+  email: 'test@example.com',
+  nickname: null,
+  name: null,
+  avatarFileId: null,
+  avatarFile: null,
+  isDisabled: 0,
+  isDeleted: 0,
+  roles: [],
+  createdAt: new Date(),
+  updatedAt: new Date()
+}
+
 describe('Agora Token API 集成测试', () => {
   let client: ReturnType<typeof testClient<typeof agoraApiRoutes>>['api']['v1']
 
@@ -28,6 +47,16 @@ describe('Agora Token API 集成测试', () => {
     process.env.AGORA_APP_SECRET = 'test-app-secret'
     process.env.AGORA_TOKEN_EXPIRY = '3600'
 
+    // Mock auth middleware
+    vi.mocked(authMiddleware).mockImplementation(async (c, next) => {
+      const authHeader = c.req.header('Authorization')
+      if (!authHeader) {
+        return c.json({ message: 'Authorization header missing' }, 401)
+      }
+      c.set('user', mockUser)
+      await next()
+    })
+
     // 创建测试客户端
     client = testClient(agoraApiRoutes).api.v1
   })
@@ -36,32 +65,90 @@ describe('Agora Token API 集成测试', () => {
     vi.clearAllMocks()
   })
 
+  test('AgoraTokenService参数验证功能', () => {
+    const agoraTokenService = new AgoraTokenService()
+
+    // 测试RTC Token参数验证
+    expect(() => {
+      agoraTokenService.validateTokenParams('rtc', 'test-channel')
+    }).not.toThrow()
+
+    // 测试RTC Token缺少channel参数
+    expect(() => {
+      agoraTokenService.validateTokenParams('rtc')
+    }).toThrow('RTC Token需要提供channel参数')
+
+    // 测试RTM Token参数验证
+    expect(() => {
+      agoraTokenService.validateTokenParams('rtm', undefined, 'test-user')
+    }).not.toThrow()
+
+    // 测试RTM Token缺少userId参数
+    expect(() => {
+      agoraTokenService.validateTokenParams('rtm')
+    }).toThrow('RTM Token需要提供userId参数')
+
+    // 测试频道名称长度限制
+    expect(() => {
+      agoraTokenService.validateTokenParams('rtc', 'a'.repeat(65))
+    }).toThrow('频道名称长度不能超过64个字符')
+
+    // 测试用户ID长度限制
+    expect(() => {
+      agoraTokenService.validateTokenParams('rtm', undefined, 'a'.repeat(65))
+    }).toThrow('用户ID长度不能超过64个字符')
+  })
+
+  test('AgoraTokenService Token生成功能', () => {
+    const agoraTokenService = new AgoraTokenService()
+
+    // 测试RTC Token生成
+    const rtcToken = agoraTokenService.generateRtcToken('test-channel', '123')
+    expect(rtcToken).toBe('mock-rtc-token')
+
+    // 测试RTM Token生成
+    const rtmToken = agoraTokenService.generateRtmToken('test-user')
+    expect(rtmToken).toBe('mock-rtm-token')
+
+    // 测试Token信息获取
+    const tokenInfo = agoraTokenService.getTokenInfo('test-token', 'rtc')
+    expect(tokenInfo).toEqual({
+      token: 'test-token',
+      type: 'rtc',
+      expiresAt: expect.any(Number),
+      expiresIn: 3600,
+      generatedAt: expect.any(Number)
+    })
+  })
+
   test('未认证用户访问Token API应该返回401', async () => {
-    const response = await client.api.v1.agora.token.$get({
+    // 临时修改mock以模拟未认证
+    vi.mocked(authMiddleware).mockImplementation(async (c) => {
+      return c.json({ message: 'Authorization header missing' }, 401)
+    })
+
+    const response = await client.agora.token.$get({
       query: { type: 'rtc', channel: 'test-channel' }
     })
 
     expect(response.status).toBe(401)
-  })
 
-  test('认证用户生成RTC Token成功', async () => {
-    // 模拟认证中间件
-    const mockAuthMiddleware = vi.fn((c, next) => {
-      c.set('user', { id: 1, username: 'testuser', role: 'admin' })
-      return next()
+    // 恢复mock
+    vi.mocked(authMiddleware).mockImplementation(async (c, next) => {
+      const authHeader = c.req.header('Authorization')
+      if (!authHeader) {
+        return c.json({ message: 'Authorization header missing' }, 401)
+      }
+      c.set('user', mockUser)
+      await next()
     })
+  })
 
-    // 临时替换认证中间件
-    const originalMiddleware = app.middleware
-    app.middleware = mockAuthMiddleware
-
-    const response = await client.api.v1.agora.token.$get({
+  test('认证用户生成RTC Token成功', async () => {
+    const response = await client.agora.token.$get({
       query: { type: 'rtc', channel: 'test-channel' }
     })
 
-    // 恢复原始中间件
-    app.middleware = originalMiddleware
-
     expect(response.status).toBe(200)
 
     const data = await response.json()
@@ -72,23 +159,10 @@ describe('Agora Token API 集成测试', () => {
   })
 
   test('认证用户生成RTM Token成功', async () => {
-    // 模拟认证中间件
-    const mockAuthMiddleware = vi.fn((c, next) => {
-      c.set('user', { id: 1, username: 'testuser', role: 'admin' })
-      return next()
-    })
-
-    // 临时替换认证中间件
-    const originalMiddleware = app.middleware
-    app.middleware = mockAuthMiddleware
-
-    const response = await client.api.v1.agora.token.$get({
+    const response = await client.agora.token.$get({
       query: { type: 'rtm', userId: 'test-user-123' }
     })
 
-    // 恢复原始中间件
-    app.middleware = originalMiddleware
-
     expect(response.status).toBe(200)
 
     const data = await response.json()
@@ -97,95 +171,39 @@ describe('Agora Token API 集成测试', () => {
     expect(data.expiresIn).toBe(3600)
   })
 
-  test('RTC Token缺少channel参数应该返回400', async () => {
-    // 模拟认证中间件
-    const mockAuthMiddleware = vi.fn((c, next) => {
-      c.set('user', { id: 1, username: 'testuser', role: 'admin' })
-      return next()
-    })
-
-    // 临时替换认证中间件
-    const originalMiddleware = app.middleware
-    app.middleware = mockAuthMiddleware
-
-    const response = await client.api.v1.agora.token.$get({
-      query: { type: 'rtc' } // 缺少channel参数
+  test('路由参数验证 - 无效Token类型', async () => {
+    // 使用无效的type参数
+    const response = await client.agora.token.$get({
+      query: { type: 'invalid-type', channel: 'test-channel' }
     })
 
-    // 恢复原始中间件
-    app.middleware = originalMiddleware
-
+    // 由于Zod验证,应该返回400错误
     expect(response.status).toBe(400)
-
-    const data = await response.json()
-    expect(data.message).toContain('RTC Token需要提供channel参数')
   })
 
-  test('RTM Token缺少userId参数应该返回400', async () => {
-    // 模拟认证中间件
-    const mockAuthMiddleware = vi.fn((c, next) => {
-      c.set('user', { id: 1, username: 'testuser', role: 'admin' })
-      return next()
+  test('路由参数验证 - 缺少必需参数', async () => {
+    // 测试缺少channel参数
+    const response1 = await client.agora.token.$get({
+      query: { type: 'rtc' } // 缺少channel参数
     })
 
-    // 临时替换认证中间件
-    const originalMiddleware = app.middleware
-    app.middleware = mockAuthMiddleware
+    // 由于Zod验证,应该返回400错误
+    expect(response1.status).toBe(400)
 
-    const response = await client.api.v1.agora.token.$get({
+    // 测试缺少userId参数
+    const response2 = await client.agora.token.$get({
       query: { type: 'rtm' } // 缺少userId参数
     })
 
-    // 恢复原始中间件
-    app.middleware = originalMiddleware
-
-    expect(response.status).toBe(400)
-
-    const data = await response.json()
-    expect(data.message).toContain('RTM Token需要提供userId参数')
-  })
-
-  test('无效的Token类型应该返回400', async () => {
-    // 模拟认证中间件
-    const mockAuthMiddleware = vi.fn((c, next) => {
-      c.set('user', { id: 1, username: 'testuser', role: 'admin' })
-      return next()
-    })
-
-    // 临时替换认证中间件
-    const originalMiddleware = app.middleware
-    app.middleware = mockAuthMiddleware
-
-    // 使用无效的type参数
-    const response = await client.api.v1.agora.token.$get({
-      query: { type: 'invalid-type', channel: 'test-channel' }
-    })
-
-    // 恢复原始中间件
-    app.middleware = originalMiddleware
-
     // 由于Zod验证,应该返回400错误
-    expect(response.status).toBe(400)
+    expect(response2.status).toBe(400)
   })
 
   test('Token有效期和格式验证', async () => {
-    // 模拟认证中间件
-    const mockAuthMiddleware = vi.fn((c, next) => {
-      c.set('user', { id: 1, username: 'testuser', role: 'admin' })
-      return next()
-    })
-
-    // 临时替换认证中间件
-    const originalMiddleware = app.middleware
-    app.middleware = mockAuthMiddleware
-
-    const response = await client.api.v1.agora.token.$get({
+    const response = await client.agora.token.$get({
       query: { type: 'rtc', channel: 'test-channel' }
     })
 
-    // 恢复原始中间件
-    app.middleware = originalMiddleware
-
     expect(response.status).toBe(200)
 
     const data = await response.json()

+ 2 - 15
src/server/api/agora/index.ts

@@ -2,20 +2,7 @@ import { OpenAPIHono } from '@hono/zod-openapi'
 import tokenRoutes from './token/get'
 
 const app = new OpenAPIHono()
+  .route('/', tokenRoutes)
 
 // 注册Agora路由
-app.route('/token', tokenRoutes)
-
-// OpenAPI文档配置
-app.doc('/doc', {
-  openapi: '3.0.0',
-  info: {
-    title: 'Agora API',
-    version: '1.0.0',
-    description: 'Agora实时语音转录Token管理API'
-  }
-})
-
-export default app
-
-export type AgoraRoutes = typeof app
+export default app