Explorar o código

fix: 修复故事010.006集成测试 - 全部17个测试通过

修复内容:
- API响应格式:更新断言检查 data.data.list
- 必填字段:添加 typeId 和 code
- 测试生命周期:使用 beforeAll/afterAll
- 数据清理:使用 find+remove 方法
- 用户ID设置:使用 query builder 更新

测试结果:17个测试用例全部通过

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname hai 2 semanas
pai
achega
9d77d67121

+ 23 - 7
docs/stories/010.006.story.md

@@ -308,6 +308,7 @@ cd packages/server && pnpm typecheck
 | 2026-01-03 | 1.0 | 初始故事创建 | James (Claude Code) |
 | 2026-01-03 | 1.1 | 故事完成 - Ready for Review | James (Claude Code) |
 | 2026-01-03 | 1.2 | 修复集成测试 - testClient调用方式修正 | James (Claude Code) |
+| 2026-01-03 | 1.3 | 修复集成测试 - 全部17个测试通过 | James (Claude Code) |
 
 ## Dev Agent Record
 
@@ -315,11 +316,25 @@ cd packages/server && pnpm typecheck
 claude-opus-4-5-20251101 (d8d-model)
 
 ### Debug Log References
-- **集成测试修复**: 修复 `unified-advertisement-auth.integration.test.ts` 中的 `testClient` 调用方式
-  - 问题:直接从模块导入路由导致数据源未初始化(`AppDataSource is undefined`)
-  - 解决:从 server 包的 `../../src/api` 导入路由以触发数据源初始化
-  - 问题:headers 作为第一个参数传递不符合 `hono/testing` API 规范
-  - 解决:将 headers 移到第二个参数:`.$get(undefined, { headers })` 和 `.$post({ json }, { headers })`
+- **集成测试修复 (第一轮)**: 修复 `testClient` 调用方式
+  - 问题:直接从模块导入路由导致数据源未初始化
+  - 解决:从 server 包的 `../../src/api` 导入路由
+  - 问题:headers 作为第一个参数传递不符合 API 规范
+  - 解决:将 headers 移到第二个参数
+
+- **集成测试修复 (第二轮)**: 修复API响应格式和数据清理
+  - 问题:API返回 `{ code, message, data: { list, total } }` 而不是直接的数组
+  - 解决:更新测试断言检查 `data.data.list` 而不是直接检查数组
+  - 问题:创建广告时缺少必填字段 `typeId` 和 `code`
+  - 解决:添加必填字段并在测试中先创建广告类型
+  - 问题:数据清理使用 `delete({})` 和 `clear()` 方法有外键约束问题
+  - 解决:使用 repository 的 `find()` + `remove()` 方法逐个删除
+
+- **集成测试修复 (第三轮)**: 修复测试生命周期和用户ID设置
+  - 问题:`afterEach` 销毁数据源导致后续测试表不存在
+  - 解决:使用 `beforeAll` 和 `afterAll` 而不是 `beforeEach` 和 `afterEach`
+  - 问题:用户ID设置在 `save()` 后没有正确应用到数据库
+  - 解决:使用 query builder 直接更新用户ID为1
 
 ### Completion Notes List
 1. **租户后台集成完成**: 添加了广告管理和广告类型管理的路由、菜单项和API客户端初始化
@@ -328,10 +343,11 @@ claude-opus-4-5-20251101 (d8d-model)
 4. **API兼容性保持**: 用户端API路径 `/api/v1/advertisements` 和 `/api/v1/advertisement-types` 保持不变
 5. **管理员路由新增**: 添加了 `/api/v1/admin/unified-advertisements` 和 `/api/v1/admin/unified-advertisement-types` 路由
 6. **测试文件创建**: 创建了E2E测试和集成测试文件
-7. **类型检查通过**: server包类型检查通过,无新增错误(集成测试文件修复后)
-8. **集成测试修复**: 修复了17个测试用例中的testClient调用方式,13个测试通过
+7. **类型检查通过**: server包类型检查通过,无新增错误
+8. **集成测试全部通过**: 17个测试用例全部通过
    - 权限控制测试全部通过
    - API路径兼容性验证通过
+   - CRUD操作测试通过
 
 ### File List
 

+ 46 - 12
packages/server/tests/integration/unified-advertisement-auth.integration.test.ts

@@ -1,8 +1,7 @@
-import { describe, it, expect, beforeEach } from 'vitest';
+import { describe, it, expect, beforeEach, beforeAll, afterAll } from 'vitest';
 import { testClient } from 'hono/testing';
 import {
   IntegrationTestDatabase,
-  setupIntegrationDatabaseHooks,
   TestDataFactory
 } from '../utils/integration-test-db';
 import { UserEntityMt, UserServiceMt } from '@d8d/user-module-mt';
@@ -11,9 +10,6 @@ import { adminUnifiedAdvertisementApiRoutes, adminUnifiedAdvertisementTypeApiRou
 import { AuthService } from '@d8d/auth-module-mt';
 import { UnifiedAdvertisement, UnifiedAdvertisementType } from '@d8d/unified-advertisements-module';
 
-// 设置集成测试钩子
-setupIntegrationDatabaseHooks()
-
 describe('统一广告管理员权限集成测试', () => {
   let adminClient: ReturnType<typeof testClient<typeof adminUnifiedAdvertisementApiRoutes>>['api']['v1']['admin']['unified-advertisements'];
   let adminTypeClient: ReturnType<typeof testClient<typeof adminUnifiedAdvertisementTypeApiRoutes>>['api']['v1']['admin']['unified-advertisement-types'];
@@ -25,6 +21,15 @@ describe('统一广告管理员权限集成测试', () => {
   let regularUserToken: string;
   let tenantUserToken: string;
 
+  // 使用 beforeAll 和 afterAll 而不是 beforeEach/afterEach,避免每次测试都销毁和重新创建数据源
+  beforeAll(async () => {
+    await IntegrationTestDatabase.getDataSource();
+  });
+
+  afterAll(async () => {
+    await IntegrationTestDatabase.cleanup();
+  });
+
   beforeEach(async () => {
     // 创建测试客户端 - 使用server包注册后的路由
     adminClient = testClient(adminUnifiedAdvertisementApiRoutes).api.v1.admin['unified-advertisements'];
@@ -36,17 +41,36 @@ describe('统一广告管理员权限集成测试', () => {
     const dataSource = await IntegrationTestDatabase.getDataSource();
     if (!dataSource) throw new Error('Database not initialized');
 
-    // 初始化服务
-    userService = new UserServiceMt(dataSource);
-    authService = new AuthService(userService);
-
-    // 清理测试用户
+    // 清理测试数据(用户、广告、广告类型)
     const userRepository = dataSource.getRepository(UserEntityMt);
     await userRepository.delete({ username: 'superadmin' });
     await userRepository.delete({ username: 'regularuser' });
     await userRepository.delete({ username: 'tenantuser' });
 
+    // 清理广告和广告类型测试数据(使用repository delete方法)
+    const adRepository = dataSource.getRepository(UnifiedAdvertisement);
+    const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType);
+
+    // 先删除广告(因为它们引用广告类型)
+    const ads = await adRepository.find();
+    for (const ad of ads) {
+      await adRepository.remove(ad);
+    }
+
+    // 再删除广告类型
+    const adTypes = await adTypeRepository.find();
+    for (const adType of adTypes) {
+      await adTypeRepository.remove(adType);
+    }
+
+    // 初始化服务
+    userService = new UserServiceMt(dataSource);
+    authService = new AuthService(userService);
+
     // 创建超级管理员 (ID=1, tenantId=1)
+    // 先删除可能存在的超级管理员
+    await userRepository.delete({ username: 'superadmin' });
+
     const superAdmin = await TestDataFactory.createTestUser(dataSource, {
       username: 'superadmin',
       password: 'TestPassword123!',
@@ -55,8 +79,18 @@ describe('统一广告管理员权限集成测试', () => {
     });
     // 手动设置ID为1以确保是超级管理员
     superAdmin.id = 1;
-    await userRepository.save(superAdmin);
-    superAdminToken = authService.generateToken(superAdmin);
+    // 使用query builder直接更新ID,避免save时的ID冲突
+    await dataSource.createQueryBuilder()
+      .update(UserEntityMt)
+      .set({ id: 1 })
+      .where('username = :username', { username: 'superadmin' })
+      .execute();
+    // 重新查询用户以确保ID正确
+    const superAdminWithId = await userRepository.findOne({ where: { username: 'superadmin' } });
+    if (!superAdminWithId || superAdminWithId.id !== 1) {
+      throw new Error('超级管理员ID设置失败');
+    }
+    superAdminToken = authService.generateToken(superAdminWithId);
 
     // 创建普通管理员用户 (ID>1, tenantId=1)
     const regularUser = await TestDataFactory.createTestUser(dataSource, {