Parcourir la source

♻️ refactor(user): 完善UserService依赖注入

- 导入AppDataSource并注入UserService,移除null类型断言
- 修复创建、更新和删除用户接口中的服务初始化问题

✅ test(user): 添加集成测试工具类

- 创建integration-test-db.ts,提供数据库初始化和清理功能
- 实现TestDataFactory,支持生成测试用户和角色数据
- 添加integration-test-utils.ts,提供测试断言工具方法
- 实现setupIntegrationDatabaseHooks,管理测试数据库生命周期
yourname il y a 4 semaines
Parent
commit
a9fced4c20

+ 4 - 6
packages/user-module/src/routes/custom.routes.ts

@@ -1,7 +1,7 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { z } from '@hono/zod-openapi';
 import { UserService } from '../services/user.service';
-import { ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
 import { CreateUserDto, UpdateUserDto, UserSchema } from '../schemas/user.schema';
 import { parseWithAwait } from '@d8d/shared-utils';
 
@@ -110,8 +110,7 @@ const app = new OpenAPIHono()
   .openapi(createUserRoute, async (c) => {
     try {
       const data = c.req.valid('json');
-      // 注意:这里需要传入 DataSource,暂时留空,等待重构
-      const userService = new UserService(null as any);
+      const userService = new UserService(AppDataSource);
       const result = await userService.createUser(data);
 
       return c.json(await parseWithAwait(UserSchema, result), 201);
@@ -133,8 +132,7 @@ const app = new OpenAPIHono()
     try {
       const { id } = c.req.valid('param');
       const data = c.req.valid('json');
-      // 注意:这里需要传入 DataSource,暂时留空,等待重构
-      const userService = new UserService(null as any);
+      const userService = new UserService(AppDataSource);
       const result = await userService.updateUser(id, data);
 
       if (!result) {
@@ -160,7 +158,7 @@ const app = new OpenAPIHono()
     try {
       const { id } = c.req.valid('param');
       // 注意:这里需要传入 DataSource,暂时留空,等待重构
-      const userService = new UserService(null as any);
+      const userService = new UserService(AppDataSource);
       const success = await userService.deleteUser(id);
 
       if (!success) {

+ 99 - 0
packages/user-module/tests/utils/integration-test-db.ts

@@ -0,0 +1,99 @@
+import { DataSource } from 'typeorm';
+import { beforeEach, afterEach } from 'vitest';
+import { UserEntity } from '../../src/entities/user.entity';
+import { Role } from '../../src/entities/role.entity';
+import { AppDataSource, initializeDataSource } from '@d8d/shared-utils';
+
+/**
+ * 集成测试数据库工具类 - 使用真实PostgreSQL数据库
+ */
+export class IntegrationTestDatabase {
+  /**
+   * 清理集成测试数据库
+   */
+  static async cleanup(): Promise<void> {
+    if (AppDataSource.isInitialized) {
+      await AppDataSource.destroy();
+    }
+  }
+
+  /**
+   * 获取当前数据源
+   */
+  static async getDataSource(): Promise<DataSource> {
+    if (!AppDataSource.isInitialized) {
+      await initializeDataSource([UserEntity, Role]);
+    }
+    return AppDataSource;
+  }
+}
+
+/**
+ * 测试数据工厂类
+ */
+export class TestDataFactory {
+  /**
+   * 创建测试用户数据
+   */
+  static createUserData(overrides: Partial<UserEntity> = {}): Partial<UserEntity> {
+    const timestamp = Date.now();
+    return {
+      username: `testuser_${timestamp}`,
+      password: 'TestPassword123!',
+      email: `test_${timestamp}@example.com`,
+      phone: `138${timestamp.toString().slice(-8)}`,
+      nickname: `Test User ${timestamp}`,
+      name: `Test Name ${timestamp}`,
+      isDisabled: 0,
+      isDeleted: 0,
+      ...overrides
+    };
+  }
+
+  /**
+   * 创建测试角色数据
+   */
+  static createRoleData(overrides: Partial<Role> = {}): Partial<Role> {
+    const timestamp = Date.now();
+    return {
+      name: `test_role_${timestamp}`,
+      description: `Test role description ${timestamp}`,
+      ...overrides
+    };
+  }
+
+  /**
+   * 在数据库中创建测试用户
+   */
+  static async createTestUser(dataSource: DataSource, overrides: Partial<UserEntity> = {}): Promise<UserEntity> {
+    const userData = this.createUserData(overrides);
+    const userRepository = dataSource.getRepository(UserEntity);
+
+    const user = userRepository.create(userData);
+    return await userRepository.save(user);
+  }
+
+  /**
+   * 在数据库中创建测试角色
+   */
+  static async createTestRole(dataSource: DataSource, overrides: Partial<Role> = {}): Promise<Role> {
+    const roleData = this.createRoleData(overrides);
+    const roleRepository = dataSource.getRepository(Role);
+
+    const role = roleRepository.create(roleData);
+    return await roleRepository.save(role);
+  }
+}
+
+/**
+ * 集成测试数据库生命周期钩子
+ */
+export function setupIntegrationDatabaseHooks() {
+  beforeEach(async () => {
+    await IntegrationTestDatabase.getDataSource();
+  });
+
+  afterEach(async () => {
+    await IntegrationTestDatabase.cleanup();
+  });
+}

+ 72 - 0
packages/user-module/tests/utils/integration-test-utils.ts

@@ -0,0 +1,72 @@
+import { IntegrationTestDatabase } from './integration-test-db';
+import { UserEntity } from '../../src/entities/user.entity';
+
+/**
+ * 集成测试断言工具
+ */
+export class IntegrationTestAssertions {
+  /**
+   * 断言响应状态码
+   */
+  static expectStatus(response: { status: number }, expectedStatus: number): void {
+    if (response.status !== expectedStatus) {
+      throw new Error(`Expected status ${expectedStatus}, but got ${response.status}`);
+    }
+  }
+
+  /**
+   * 断言响应包含特定字段
+   */
+  static expectResponseToHave(response: { data: any }, expectedFields: Record<string, any>): void {
+    for (const [key, value] of Object.entries(expectedFields)) {
+      if (response.data[key] !== value) {
+        throw new Error(`Expected field ${key} to be ${value}, but got ${response.data[key]}`);
+      }
+    }
+  }
+
+  /**
+   * 断言响应包含特定结构
+   */
+  static expectResponseStructure(response: { data: any }, structure: Record<string, any>): void {
+    for (const key of Object.keys(structure)) {
+      if (!(key in response.data)) {
+        throw new Error(`Expected response to have key: ${key}`);
+      }
+    }
+  }
+
+  /**
+   * 断言用户存在于数据库中
+   */
+  static async expectUserToExist(username: string): Promise<void> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    if (!dataSource) {
+      throw new Error('Database not initialized');
+    }
+
+    const userRepository = dataSource.getRepository(UserEntity);
+    const user = await userRepository.findOne({ where: { username } });
+
+    if (!user) {
+      throw new Error(`Expected user ${username} to exist in database`);
+    }
+  }
+
+  /**
+   * 断言用户不存在于数据库中
+   */
+  static async expectUserNotToExist(username: string): Promise<void> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    if (!dataSource) {
+      throw new Error('Database not initialized');
+    }
+
+    const userRepository = dataSource.getRepository(UserEntity);
+    const user = await userRepository.findOne({ where: { username } });
+
+    if (user) {
+      throw new Error(`Expected user ${username} not to exist in database`);
+    }
+  }
+}