浏览代码

♻️ refactor(test): optimize integration test database handling

- 修改IntegrationTestDatabase.initialize()为getDataSource()并设为异步方法
- 添加测试数据库初始化和断开的调试日志
- 移除clearAllData()方法实现,解决测试数据清理问题
- 重构setupIntegrationDatabaseHooks()统一管理测试生命周期
- 更新测试用例中获取数据源的方式,确保数据库已正确初始化

♻️ refactor(test): simplify integration test utilities

- 注释掉未使用的测试配置接口和工具函数
- 移除重复的测试环境设置代码
- 优化测试断言工具中获取数据源的方式

✅ test(auth): update auth integration tests

- 使用setupIntegrationDatabaseHooks()简化测试生命周期管理
- 修复获取数据源的异步调用方式
- 移除重复的数据库初始化和清理代码

✅ test(users): update user integration tests

- 使用setupIntegrationDatabaseHooks()统一测试生命周期
- 修复获取数据源的异步调用方式
- 移除冗余的数据库初始化和清理代码
yourname 2 月之前
父节点
当前提交
1e516cce2a

+ 71 - 50
src/server/__test_utils__/integration-test-db.ts

@@ -8,21 +8,37 @@ import { AppDataSource } from '../data-source';
  * 集成测试数据库工具类 - 使用真实PostgreSQL数据库
  */
 export class IntegrationTestDatabase {
-  /**
-   * 初始化集成测试数据库
-   */
-  static async initialize(): Promise<DataSource> {
-    if (!AppDataSource.isInitialized) {
-      await AppDataSource.initialize();
-    }
-    return AppDataSource;
-  }
+  // private static initializationPromise: Promise<DataSource> | null = null;
+  // /**
+  //  * 初始化集成测试数据库
+  //  */
+  // static async initialize(): Promise<DataSource> {
+  //   // if (!AppDataSource.isInitialized) {
+  //   //   if (!this.initializationPromise) {
+  //   //     this.initializationPromise = (async () => {
+  //   //       try {
+  //   //         await AppDataSource.initialize();
+  //   //         return AppDataSource;
+  //   //       } catch (error) {
+  //   //         this.initializationPromise = null;
+  //   //         throw error;
+  //   //       }
+  //   //     })();
+  //   //   }
+  //   //   return await this.initializationPromise;
+  //   // }
+  //   // if(!AppDataSource.isInitialized) {
+  //   //   await AppDataSource.initialize();
+  //   // }
+  //   return AppDataSource;
+  // }
 
   /**
    * 清理集成测试数据库
    */
   static async cleanup(): Promise<void> {
     if (AppDataSource.isInitialized) {
+      console.debug('测试数据库断开')
       await AppDataSource.destroy();
     }
   }
@@ -30,44 +46,48 @@ export class IntegrationTestDatabase {
   /**
    * 获取当前数据源
    */
-  static getDataSource(): DataSource | null {
-    return AppDataSource.isInitialized ? AppDataSource : null;
-  }
-
-  /**
-   * 清空所有表数据
-   */
-  static async clearAllData(): Promise<void> {
-    if (!AppDataSource.isInitialized) {
-      return;
-    }
-
-    const queryRunner = AppDataSource.createQueryRunner();
-    await queryRunner.connect();
-
-    try {
-      // 获取所有实体
-      const entities = AppDataSource.entityMetadatas;
-
-      // 按依赖关系排序(先删除子表,再删除父表)
-      const sortedEntities = entities.sort((a, b) => {
-        if (a.foreignKeys.some(fk => fk.referencedEntityMetadata.name === b.name)) {
-          return 1; // a 依赖于 b,a 应该排在后面
-        }
-        if (b.foreignKeys.some(fk => fk.referencedEntityMetadata.name === a.name)) {
-          return -1; // b 依赖于 a,a 应该排在前面
-        }
-        return 0;
-      });
-
-      // 使用TRUNCATE CASCADE来清空所有表数据(包括有外键约束的表)
-      for (const entity of sortedEntities) {
-        await queryRunner.query(`TRUNCATE TABLE "${entity.tableName}" CASCADE`);
-      }
-    } finally {
-      await queryRunner.release();
+  static async getDataSource(): Promise<DataSource> {
+    if(!AppDataSource.isInitialized) {
+      console.debug('测试数据库初始化')
+      await AppDataSource.initialize();
     }
+    return AppDataSource
   }
+
+  // /**
+  //  * 清空所有表数据
+  //  */
+  // static async clearAllData(): Promise<void> {
+  //   if (!AppDataSource.isInitialized) {
+  //     return;
+  //   }
+
+  //   const queryRunner = AppDataSource.createQueryRunner();
+  //   await queryRunner.connect();
+
+  //   try {
+  //     // 获取所有实体
+  //     const entities = AppDataSource.entityMetadatas;
+
+  //     // 按依赖关系排序(先删除子表,再删除父表)
+  //     const sortedEntities = entities.sort((a, b) => {
+  //       if (a.foreignKeys.some(fk => fk.referencedEntityMetadata.name === b.name)) {
+  //         return 1; // a 依赖于 b,a 应该排在后面
+  //       }
+  //       if (b.foreignKeys.some(fk => fk.referencedEntityMetadata.name === a.name)) {
+  //         return -1; // b 依赖于 a,a 应该排在前面
+  //       }
+  //       return 0;
+  //     });
+
+  //     // 使用TRUNCATE CASCADE来清空所有表数据(包括有外键约束的表)
+  //     for (const entity of sortedEntities) {
+  //       await queryRunner.query(`TRUNCATE TABLE "${entity.tableName}" CASCADE`);
+  //     }
+  //   } finally {
+  //     await queryRunner.release();
+  //   }
+  // }
 }
 
 /**
@@ -132,14 +152,15 @@ export class TestDataFactory {
  */
 export function setupIntegrationDatabaseHooks() {
   beforeEach(async () => {
-    await IntegrationTestDatabase.initialize();
+    await IntegrationTestDatabase.getDataSource();
   });
 
-  afterEach(async () => {
-    await IntegrationTestDatabase.clearAllData();
-  });
+  // afterEach(async () => {
+  //   await IntegrationTestDatabase.clearAllData();
+  // });
 
-  afterAll(async () => {
+  afterEach(async () => {
+    console.debug('测试数据库断开')
     await IntegrationTestDatabase.cleanup();
   });
 }

+ 109 - 109
src/server/__test_utils__/integration-test-utils.ts

@@ -6,97 +6,97 @@ import { UserEntity } from '../modules/users/user.entity';
 import { Role } from '../modules/users/role.entity';
 import apiApp from '../api';
 
-/**
- * 集成测试配置选项
- */
-export interface IntegrationTestOptions {
-  setupDatabase?: boolean;
-  setupAuth?: boolean;
-  setupMiddlewares?: boolean;
-}
-
-/**
- * 集成测试上下文
- */
-export interface IntegrationTestContext {
-  app: OpenAPIHono;
-  client: ReturnType<typeof testClient<typeof apiApp>>;
-  dataSource: DataSource | null;
-}
-
-/**
- * 创建集成测试应用实例
- */
-export async function createIntegrationTestApp(
-  routes: any[],
-  options: IntegrationTestOptions = {}
-): Promise<OpenAPIHono> {
-  // 使用主API应用,确保所有路由已注册
-  return apiApp;
-}
-
-/**
- * 创建集成测试客户端(使用hono/testing的testClient)
- */
-export function createIntegrationTestClient(
-  app: OpenAPIHono,
-  options: IntegrationTestOptions = {}
-): ReturnType<typeof testClient<typeof apiApp>> {
-  const client = testClient(app);
-
-  // 设置默认认证头(如果需要)
-  if (options.setupAuth !== false) {
-    // testClient会自动处理header,这里不需要额外设置
-  }
-
-  return client;
-}
-
-/**
- * 设置集成测试环境
- */
-export async function setupIntegrationTestEnvironment(
-  routes: any[],
-  options: IntegrationTestOptions = {}
-): Promise<IntegrationTestContext> {
-  const {
-    setupDatabase = true,
-    setupAuth = true,
-    setupMiddlewares = true
-  } = options;
-
-  // 创建测试应用
-  const app = await createIntegrationTestApp(routes, options);
-
-  // 初始化数据库(如果需要)
-  let dataSource: DataSource | null = null;
-  if (setupDatabase) {
-    dataSource = await IntegrationTestDatabase.initialize();
-  }
-
-  // 创建API客户端
-  const client = createIntegrationTestClient(app, { setupAuth });
-
-  return {
-    app,
-    client,
-    dataSource
-  };
-}
-
-/**
- * 清理集成测试环境
- */
-export async function cleanupIntegrationTestEnvironment(): Promise<void> {
-  await IntegrationTestDatabase.clearAllData();
-  await IntegrationTestDatabase.cleanup();
-}
-
-/**
- * 测试数据工厂函数
- * 使用integration-test-db.ts中的TestDataFactory
- */
-export { TestDataFactory } from './integration-test-db';
+// /**
+//  * 集成测试配置选项
+//  */
+// export interface IntegrationTestOptions {
+//   setupDatabase?: boolean;
+//   setupAuth?: boolean;
+//   setupMiddlewares?: boolean;
+// }
+
+// /**
+//  * 集成测试上下文
+//  */
+// export interface IntegrationTestContext {
+//   app: OpenAPIHono;
+//   client: ReturnType<typeof testClient<typeof apiApp>>;
+//   dataSource: DataSource | null;
+// }
+
+// // /**
+// //  * 创建集成测试应用实例
+// //  */
+// // export async function createIntegrationTestApp(
+// //   routes: any[],
+// //   options: IntegrationTestOptions = {}
+// // ): Promise<OpenAPIHono> {
+// //   // 使用主API应用,确保所有路由已注册
+// //   return apiApp;
+// // }
+
+// /**
+//  * 创建集成测试客户端(使用hono/testing的testClient)
+//  */
+// export function createIntegrationTestClient(
+//   app: OpenAPIHono,
+//   options: IntegrationTestOptions = {}
+// ): ReturnType<typeof testClient<typeof apiApp>> {
+//   const client = testClient(app);
+
+//   // 设置默认认证头(如果需要)
+//   if (options.setupAuth !== false) {
+//     // testClient会自动处理header,这里不需要额外设置
+//   }
+
+//   return client;
+// }
+
+// /**
+//  * 设置集成测试环境
+//  */
+// export async function setupIntegrationTestEnvironment(
+//   routes: any[],
+//   options: IntegrationTestOptions = {}
+// ): Promise<IntegrationTestContext> {
+//   const {
+//     setupDatabase = true,
+//     setupAuth = true,
+//     setupMiddlewares = true
+//   } = options;
+
+//   // 创建测试应用
+//   // const app = await createIntegrationTestApp(routes, options);
+
+//   // 初始化数据库(如果需要)
+//   let dataSource: DataSource | null = null;
+//   if (setupDatabase) {
+//     dataSource = await IntegrationTestDatabase.initialize();
+//   }
+
+//   // 创建API客户端
+//   const client = createIntegrationTestClient(app, { setupAuth });
+
+//   return {
+//     app,
+//     client,
+//     dataSource
+//   };
+// }
+
+// /**
+//  * 清理集成测试环境
+//  */
+// export async function cleanupIntegrationTestEnvironment(): Promise<void> {
+//   await IntegrationTestDatabase.clearAllData();
+//   await IntegrationTestDatabase.cleanup();
+// }
+
+// /**
+//  * 测试数据工厂函数
+//  * 使用integration-test-db.ts中的TestDataFactory
+//  */
+// export { TestDataFactory } from './integration-test-db';
 
 /**
  * 集成测试断言工具
@@ -137,7 +137,7 @@ export class IntegrationTestAssertions {
    * 断言用户存在于数据库中
    */
   static async expectUserToExist(username: string): Promise<void> {
-    const dataSource = IntegrationTestDatabase.getDataSource();
+    const dataSource = await IntegrationTestDatabase.getDataSource();
     if (!dataSource) {
       throw new Error('Database not initialized');
     }
@@ -154,7 +154,7 @@ export class IntegrationTestAssertions {
    * 断言用户不存在于数据库中
    */
   static async expectUserNotToExist(username: string): Promise<void> {
-    const dataSource = IntegrationTestDatabase.getDataSource();
+    const dataSource = await IntegrationTestDatabase.getDataSource();
     if (!dataSource) {
       throw new Error('Database not initialized');
     }
@@ -168,19 +168,19 @@ export class IntegrationTestAssertions {
   }
 }
 
-/**
- * 集成测试生命周期钩子
- */
-export function setupIntegrationTestHooks() {
-  beforeEach(async () => {
-    await IntegrationTestDatabase.initialize();
-  });
-
-  afterEach(async () => {
-    await IntegrationTestDatabase.clearAllData();
-  });
-
-  afterAll(async () => {
-    await IntegrationTestDatabase.cleanup();
-  });
-}
+// /**
+//  * 集成测试生命周期钩子
+//  */
+// export function setupIntegrationTestHooks() {
+//   beforeEach(async () => {
+//     await IntegrationTestDatabase.initialize();
+//   });
+
+//   afterEach(async () => {
+//     await IntegrationTestDatabase.clearAllData();
+//   });
+
+//   afterAll(async () => {
+//     await IntegrationTestDatabase.cleanup();
+//   });
+// }

+ 6 - 16
src/server/api/auth/__tests__/auth.integration.test.ts

@@ -1,7 +1,8 @@
-import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
 import { testClient } from 'hono/testing';
 import {
   IntegrationTestDatabase,
+  setupIntegrationDatabaseHooks,
   TestDataFactory
 } from '../../../__test_utils__/integration-test-db';
 import { UserEntity } from '../../../modules/users/user.entity';
@@ -10,17 +11,7 @@ import { AuthService } from '../../../modules/auth/auth.service';
 import { UserService } from '../../../modules/users/user.service';
 
 // 设置集成测试钩子
-beforeAll(async () => {
-  await IntegrationTestDatabase.initialize();
-});
-
-afterEach(async () => {
-  await IntegrationTestDatabase.clearAllData();
-});
-
-afterAll(async () => {
-  await IntegrationTestDatabase.cleanup();
-});
+setupIntegrationDatabaseHooks()
 
 describe('认证API集成测试 (使用hono/testing)', () => {
   let client: ReturnType<typeof testClient<typeof authRoutes>>['api']['v1'];
@@ -34,8 +25,7 @@ describe('认证API集成测试 (使用hono/testing)', () => {
     client = testClient(authRoutes).api.v1;
 
     // 获取数据源
-    const dataSource = IntegrationTestDatabase.getDataSource();
-    if (!dataSource) throw new Error('Database not initialized');
+    const dataSource = await IntegrationTestDatabase.getDataSource();
 
     // 初始化服务
     userService = new UserService(dataSource);
@@ -116,7 +106,7 @@ describe('认证API集成测试 (使用hono/testing)', () => {
 
     it('应该拒绝禁用账户的登录', async () => {
       // 创建禁用账户
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       // 先删除可能存在的重复用户
@@ -302,7 +292,7 @@ describe('认证API集成测试 (使用hono/testing)', () => {
 
     it('应该正确处理权限不足错误', async () => {
       // 创建普通用户(无管理员权限)
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       // 先删除可能存在的重复用户

+ 13 - 22
src/server/api/users/__tests__/users.integration.test.ts

@@ -1,7 +1,8 @@
-import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
 import { testClient } from 'hono/testing';
 import {
   IntegrationTestDatabase,
+  setupIntegrationDatabaseHooks,
   TestDataFactory
 } from '../../../__test_utils__/integration-test-db';
 import { IntegrationTestAssertions } from '../../../__test_utils__/integration-test-utils';
@@ -9,18 +10,9 @@ import { userRoutes } from '../../../api';
 import { AuthService } from '../../../modules/auth/auth.service';
 import { UserService } from '../../../modules/users/user.service';
 
-// 设置集成测试钩子
-beforeAll(async () => {
-  await IntegrationTestDatabase.initialize();
-});
-
-afterEach(async () => {
-  await IntegrationTestDatabase.clearAllData();
-});
 
-afterAll(async () => {
-  await IntegrationTestDatabase.cleanup();
-});
+// 设置集成测试钩子
+setupIntegrationDatabaseHooks()
 
 describe('用户API集成测试 (使用hono/testing)', () => {
   let client: ReturnType<typeof testClient<typeof userRoutes>>['api']['v1'];
@@ -31,8 +23,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
     client = testClient(userRoutes).api.v1;
 
     // 创建测试用户并生成token
-    const dataSource = IntegrationTestDatabase.getDataSource();
-    if (!dataSource) throw new Error('Database not initialized');
+    const dataSource = await IntegrationTestDatabase.getDataSource();
 
     const userService = new UserService(dataSource);
     const authService = new AuthService(userService);
@@ -80,7 +71,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
     });
 
     it('应该拒绝创建重复用户名的用户', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       // 先创建一个用户
@@ -152,7 +143,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
 
   describe('用户读取测试', () => {
     it('应该成功获取用户列表', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       // 创建几个测试用户
@@ -177,7 +168,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
     });
 
     it('应该成功获取单个用户详情', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       const testUser = await TestDataFactory.createTestUser(dataSource, {
@@ -222,7 +213,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
 
   describe('用户更新测试', () => {
     it('应该成功更新用户信息', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       const testUser = await TestDataFactory.createTestUser(dataSource, {
@@ -296,7 +287,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
 
   describe('用户删除测试', () => {
     it('应该成功删除用户', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       const testUser = await TestDataFactory.createTestUser(dataSource, {
@@ -349,7 +340,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
 
   describe('用户搜索测试', () => {
     it('应该能够按用户名搜索用户', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       await TestDataFactory.createTestUser(dataSource, { username: 'search_user_1', email: 'search1@example.com' });
@@ -380,7 +371,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
     });
 
     it('应该能够按邮箱搜索用户', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       await TestDataFactory.createTestUser(dataSource, { username: 'user_email_1', email: 'test.email1@example.com' });
@@ -409,7 +400,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
 
   describe('性能测试', () => {
     it('用户列表查询响应时间应小于200ms', async () => {
-      const dataSource = IntegrationTestDatabase.getDataSource();
+      const dataSource = await IntegrationTestDatabase.getDataSource();
       if (!dataSource) throw new Error('Database not initialized');
 
       // 创建一些测试数据