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

♻️ refactor(test): optimize database configuration for integration tests

- 修改IntegrationTestDatabase类,使用AppDataSource替代自定义数据源
- 移除重复的数据源配置,统一使用AppDataSource管理数据库连接
- 优化clearAllData方法,使用TRUNCATE CASCADE清理所有表数据

🔧 chore(security): add psql command to allowed bash commands

- 允许psql命令执行,支持数据库操作相关的测试和脚本

✨ feat(test): enhance test database configuration

- 增加测试环境数据库URL配置支持
- 实现测试环境自动使用测试数据库的逻辑
- 测试环境启用dropSchema确保每次测试都是全新环境
yourname 2 месяцев назад
Родитель
Сommit
62b071facd

+ 2 - 1
.claude/settings.local.json

@@ -20,7 +20,8 @@
       "Bash(npx playwright test:*)",
       "Bash(npm view:*)",
       "Bash(npm run)",
-      "Bash(node debug-page.js:*)"
+      "Bash(node debug-page.js:*)",
+      "Bash(psql:*)"
     ],
     "deny": [],
     "ask": []

+ 12 - 31
src/server/__test_utils__/integration-test-db.ts

@@ -2,46 +2,28 @@ import { DataSource } from 'typeorm';
 import { beforeEach, afterEach, afterAll } from 'vitest';
 import { UserEntity } from '../modules/users/user.entity';
 import { Role } from '../modules/users/role.entity';
+import { AppDataSource } from '../data-source';
 
 /**
  * 集成测试数据库工具类 - 使用真实PostgreSQL数据库
  */
 export class IntegrationTestDatabase {
-  private static dataSource: DataSource | null = null;
-
   /**
    * 初始化集成测试数据库
    */
   static async initialize(): Promise<DataSource> {
-    if (this.dataSource?.isInitialized) {
-      return this.dataSource;
+    if (!AppDataSource.isInitialized) {
+      await AppDataSource.initialize();
     }
-
-    // 使用环境变量中的测试数据库配置
-    const databaseUrl = process.env.TEST_DATABASE_URL ||
-                       process.env.DATABASE_URL?.replace('d8dai', 'test_d8dai') ||
-                       'postgresql://postgres:test_password@localhost:5432/test_d8dai';
-
-    this.dataSource = new DataSource({
-      type: 'postgres',
-      url: databaseUrl,
-      synchronize: true,
-      dropSchema: true, // 每次测试都重新创建schema
-      logging: false,
-      entities: [UserEntity, Role]
-    });
-
-    await this.dataSource.initialize();
-    return this.dataSource;
+    return AppDataSource;
   }
 
   /**
    * 清理集成测试数据库
    */
   static async cleanup(): Promise<void> {
-    if (this.dataSource?.isInitialized) {
-      await this.dataSource.destroy();
-      this.dataSource = null;
+    if (AppDataSource.isInitialized) {
+      await AppDataSource.destroy();
     }
   }
 
@@ -49,23 +31,23 @@ export class IntegrationTestDatabase {
    * 获取当前数据源
    */
   static getDataSource(): DataSource | null {
-    return this.dataSource;
+    return AppDataSource.isInitialized ? AppDataSource : null;
   }
 
   /**
    * 清空所有表数据
    */
   static async clearAllData(): Promise<void> {
-    if (!this.dataSource?.isInitialized) {
+    if (!AppDataSource.isInitialized) {
       return;
     }
 
-    const queryRunner = this.dataSource.createQueryRunner();
+    const queryRunner = AppDataSource.createQueryRunner();
     await queryRunner.connect();
 
     try {
       // 获取所有实体
-      const entities = this.dataSource.entityMetadatas;
+      const entities = AppDataSource.entityMetadatas;
 
       // 按依赖关系排序(先删除子表,再删除父表)
       const sortedEntities = entities.sort((a, b) => {
@@ -78,10 +60,9 @@ export class IntegrationTestDatabase {
         return 0;
       });
 
-      // 清空所有表数据
+      // 使用TRUNCATE CASCADE来清空所有表数据(包括有外键约束的表)
       for (const entity of sortedEntities) {
-        const repository = this.dataSource.getRepository(entity.name);
-        await repository.clear();
+        await queryRunner.query(`TRUNCATE TABLE "${entity.tableName}" CASCADE`);
       }
     } finally {
       await queryRunner.release();

+ 29 - 14
src/server/data-source.ts

@@ -6,17 +6,32 @@ import process from 'node:process'
 import { UserEntity as User } from "./modules/users/user.entity"
 import { Role } from "./modules/users/role.entity"
 
-export const AppDataSource = new DataSource({
-  type: "postgres",
-  host: process.env.DB_HOST || "localhost",
-  port: parseInt(process.env.DB_PORT || "5432"),
-  username: process.env.DB_USERNAME || "postgres",
-  password: process.env.DB_PASSWORD || "",
-  database: process.env.DB_DATABASE || "postgres",
-  entities: [
-    User, Role, 
-  ],
-  migrations: [],
-  synchronize: process.env.DB_SYNCHRONIZE !== "false",
-  logging: process.env.DB_LOGGING === "true",
-});
+// 在测试环境下使用测试数据库配置
+const isTestEnv = process.env.NODE_ENV === 'test';
+const testDatabaseUrl = process.env.TEST_DATABASE_URL || 'postgresql://postgres:test_password@localhost:5432/test_d8dai';
+;
+
+const dataSource = isTestEnv && testDatabaseUrl
+  ? new DataSource({
+      type: "postgres",
+      url: testDatabaseUrl,
+      entities: [User, Role],
+      migrations: [],
+      synchronize: true, // 测试环境总是同步schema
+      dropSchema: true,  // 测试环境每次重新创建schema
+      logging: false,    // 测试环境关闭日志
+    })
+  : new DataSource({
+      type: "postgres",
+      host: process.env.DB_HOST || "localhost",
+      port: parseInt(process.env.DB_PORT || "5432"),
+      username: process.env.DB_USERNAME || "postgres",
+      password: process.env.DB_PASSWORD || "",
+      database: process.env.DB_DATABASE || "postgres",
+      entities: [User, Role],
+      migrations: [],
+      synchronize: process.env.DB_SYNCHRONIZE !== "false",
+      logging: process.env.DB_LOGGING === "true",
+    });
+
+export const AppDataSource = dataSource;