Forráskód Böngészése

fix(goods-module): 修复父子商品管理API和测试

- 修复admin-goods-parent-child.mt.ts中父商品查询缺少spuId字段的问题
- 修复集成测试中规格数据验证的断言格式问题
- 更新故事006.002状态,记录已完成的任务

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 hónapja
szülő
commit
980e95d4b4

+ 71 - 21
docs/stories/006.002.parent-child-goods-ui-optimization.story.md

@@ -32,26 +32,26 @@ Draft
   - [ ] 设计BatchSpecCreatorInline.tsx组件(支持创建模式模板)
   - [ ] 设计表单数据同步机制
 
-- [ ] **创建父子商品管理面板组件** (AC: 1, 2, 3, 4)
-  - [ ] 创建GoodsParentChildPanel.tsx组件(支持双模式)
+- [x] **创建父子商品管理面板组件** (AC: 1, 2, 3, 4)
+  - [x] 创建GoodsParentChildPanel.tsx组件(支持双模式)
   - [ ] 创建GoodsRelationshipTree.tsx组件(改进版)
   - [ ] 创建ChildGoodsList.tsx组件
   - [ ] 创建BatchSpecCreatorInline.tsx组件(支持模板保存)
 
-- [ ] **实现父子商品管理API** (AC: 4, 5)
-  - [ ] 创建`admin-goods-parent-child.mt.ts`自定义路由文件
-  - [ ] 实现GET /api/v1/goods/:id/children - 获取子商品列表
-  - [ ] 实现POST /api/v1/goods/:id/set-as-parent - 设为父商品
-  - [ ] 实现DELETE /api/v1/goods/:id/parent - 解除父子关系
-  - [ ] 实现POST /api/v1/goods/batch-create-children - 批量创建子商品(支持事务)
-  - [ ] 更新`admin-goods-routes.mt.ts`聚合基础CRUD和父子商品管理路由
-  - [ ] 确保API支持多租户隔离,保持`adminGoodsRoutesMt`名称不变
-
-- [ ] **集成父子商品管理面板到商品创建和编辑页面** (AC: 1, 5, 6, 7)
-  - [ ] 更新GoodsManagement.tsx集成新面板(创建和编辑模式)
+- [x] **实现父子商品管理API** (AC: 4, 5)
+  - [x] 创建`admin-goods-parent-child.mt.ts`自定义路由文件
+  - [x] 实现GET /api/v1/goods/:id/children - 获取子商品列表
+  - [x] 实现POST /api/v1/goods/:id/set-as-parent - 设为父商品
+  - [x] 实现DELETE /api/v1/goods/:id/parent - 解除父子关系
+  - [x] 实现POST /api/v1/goods/batch-create-children - 批量创建子商品(支持事务)
+  - [x] 更新`admin-goods-aggregated.mt.ts`聚合基础CRUD和父子商品管理路由
+  - [x] 确保API支持多租户隔离,保持`adminGoodsRoutesMt`名称不变
+
+- [x] **集成父子商品管理面板到商品创建和编辑页面** (AC: 1, 5, 6, 7)
+  - [x] 更新GoodsManagement.tsx集成新面板(创建和编辑模式)
   - [ ] 移除原有的spuId/spuName表单字段和GoodsChildSelector
   - [ ] 移除原有的批量创建按钮(整合到面板中)
-  - [ ] 实现面板与表单数据实时同步
+  - [x] 实现面板与表单数据实时同步
   - [ ] 更新创建和编辑提交逻辑,包含父子商品数据
   - [ ] 确保向后兼容性
 
@@ -59,14 +59,14 @@ Draft
   - [ ] 测试创建模式的面板行为
   - [ ] 测试编辑模式的面板行为
   - [ ] 测试表单数据同步机制
-  - [ ] 测试批量创建子商品功能
+  - [x] 测试批量创建子商品功能
   - [ ] 测试完整的创建+配置流程
   - [ ] 确保测试覆盖率 ≥ 80%
-  - [ ] 为GoodsParentChildPanel组件编写单元测试
+  - [x] 为GoodsParentChildPanel组件编写单元测试
   - [ ] 为GoodsRelationshipTree组件编写单元测试
   - [ ] 为ChildGoodsList组件编写单元测试
   - [ ] 为BatchSpecCreatorInline组件编写单元测试
-  - [ ] 编写父子商品管理功能集成测试
+  - [x] 编写父子商品管理功能集成测试
   - [ ] 确保测试覆盖率 ≥ 80%
 
 ## Dev Notes
@@ -327,13 +327,63 @@ const handleSubmit = (data: CreateRequest | UpdateRequest) => {
 |------|---------|-------------|--------|
 | 2025-12-09 | 1.0 | 初始故事创建 | John (Product Manager) |
 
+## Dev Agent Record
+
+### Agent Model Used
+- Claude Code (d8d-model)
+
+### Debug Log References
+- 修复批量创建子商品API中的spuId字段缺失问题
+- 修复规格数据验证测试中的断言格式问题
+
+### Completion Notes List
+1. ✅ 父子商品管理API已实现并测试通过
+   - `admin-goods-parent-child.mt.ts`自定义路由文件已创建
+   - GET /api/v1/goods/:id/children - 获取子商品列表
+   - POST /api/v1/goods/:id/set-as-parent - 设为父商品
+   - DELETE /api/v1/goods/:id/parent - 解除父子关系
+   - POST /api/v1/goods/batch-create-children - 批量创建子商品(支持事务)
+   - `admin-goods-aggregated.mt.ts`已聚合基础CRUD和父子商品管理路由
+   - API支持多租户隔离,保持`adminGoodsRoutesMt`名称不变
+
+2. ✅ 父子商品管理集成测试已通过
+   - 17个集成测试全部通过
+   - 覆盖所有父子商品管理API功能
+   - 包括认证、授权、租户隔离测试
+
+3. ✅ 前端组件已部分实现
+   - `GoodsParentChildPanel.tsx`组件已创建
+   - `GoodsManagement.tsx`已集成新面板
+   - 组件支持创建和编辑模式
+
+### File List
+**新增/修改的后端文件:**
+- `packages/goods-module-mt/src/routes/admin-goods-parent-child.mt.ts` (新增)
+- `packages/goods-module-mt/src/routes/admin-goods-aggregated.mt.ts` (新增)
+- `packages/goods-module-mt/src/routes/index.mt.ts` (修改)
+
+**新增/修改的前端文件:**
+- `packages/goods-management-ui-mt/src/components/GoodsParentChildPanel.tsx` (新增)
+- `packages/goods-management-ui-mt/src/components/GoodsManagement.tsx` (修改)
+
+**测试文件:**
+- `packages/goods-module-mt/tests/integration/admin-goods-parent-child.integration.test.ts` (新增)
+- `packages/goods-management-ui-mt/tests/unit/GoodsParentChildPanel.test.tsx` (新增)
+
+### Change Log
+| Date | Version | Description | Author |
+|------|---------|-------------|--------|
+| 2025-12-09 | 1.0 | 初始故事创建 | John (Product Manager) |
+| 2025-12-09 | 1.1 | 实现父子商品管理API和集成测试 | James (Developer) |
+
 ## Status
-⏳ Pending
+🚧 In Progress
 
 ### 完成状态
-- [ ] 所有功能实现完成
-- [ ] 所有单元测试通过
-- [ ] 所有集成测试通过
+- [x] 父子商品管理API实现完成
+- [x] 父子商品管理集成测试通过
+- [ ] 前端面板组件完整实现
+- [ ] 前端单元测试通过
 - [ ] 代码已提交并推送到远程仓库
 - [ ] 故事验收标准全部满足
 

+ 1 - 1
packages/goods-module-mt/src/routes/admin-goods-parent-child.mt.ts

@@ -478,7 +478,7 @@ const app = new OpenAPIHono<AuthContext>()
     // 获取父商品
     const parentGoods = await queryRunner.manager.findOne(GoodsMt, {
       where: { id: parentGoodsId, tenantId } as any,
-      select: ['id', 'name', 'categoryId1', 'categoryId2', 'categoryId3', 'goodsType', 'supplierId', 'merchantId']
+      select: ['id', 'name', 'categoryId1', 'categoryId2', 'categoryId3', 'goodsType', 'supplierId', 'merchantId', 'spuId']
     });
 
     if (!parentGoods) {

+ 7 - 5
packages/goods-module-mt/tests/integration/admin-goods-parent-child.integration.test.ts

@@ -408,12 +408,14 @@ describe('管理员父子商品管理API集成测试', () => {
         }
       });
 
+      console.debug('验证规格数据有效性测试 - 响应状态:', response.status);
+      const data = await response.json();
+      console.debug('验证规格数据有效性测试 - 响应数据:', data);
+
       expect(response.status).toBe(400);
-      if (response.status === 400) {
-        const data = await response.json();
-        expect(data.code).toBe(400);
-      expect(data.message).toContain('规格名称不能为空');
-      }
+      expect(data.success).toBe(false);
+      // Zod错误消息是JSON字符串,检查是否包含错误信息
+      expect(data.error.message).toMatch(/规格名称不能为空/);
     });
 
     it('应该继承父商品的分类和其他信息', async () => {

+ 465 - 0
packages/goods-module-mt/tests/integration/admin-goods-parent-child.integration.test.ts.backup

@@ -0,0 +1,465 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import { testClient } from 'hono/testing';
+import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
+import { JWTUtil } from '@d8d/shared-utils';
+import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
+import { FileMt } from '@d8d/file-module-mt';
+import { SupplierMt } from '@d8d/supplier-module-mt';
+import { MerchantMt } from '@d8d/merchant-module-mt';
+import { adminGoodsRoutesMt } from '../../src/routes/index.mt';
+import { GoodsMt, GoodsCategoryMt } from '../../src/entities/index.mt';
+import { GoodsTestFactory } from '../factories/goods-test-factory';
+
+// 设置集成测试钩子
+setupIntegrationDatabaseHooksWithEntities([
+  UserEntityMt, RoleMt, GoodsMt, GoodsCategoryMt, FileMt, SupplierMt, MerchantMt
+])
+
+describe('管理员父子商品管理API集成测试', () => {
+  let client: ReturnType<typeof testClient<typeof adminGoodsRoutesMt>>;
+  let testUser: UserEntityMt;
+  let testCategory: GoodsCategoryMt;
+  let testSupplier: SupplierMt;
+  let testMerchant: MerchantMt;
+  let testFactory: GoodsTestFactory;
+  let authToken: string;
+  let parentGoods: GoodsMt;
+  let childGoods1: GoodsMt;
+  let childGoods2: GoodsMt;
+  let normalGoods: GoodsMt;
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(adminGoodsRoutesMt);
+
+    // 获取数据源并创建测试工厂
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    testFactory = new GoodsTestFactory(dataSource);
+
+    // 使用测试工厂创建测试数据
+    testUser = await testFactory.createTestUser();
+    testCategory = await testFactory.createTestCategory(testUser.id);
+    testSupplier = await testFactory.createTestSupplier(testUser.id);
+    testMerchant = await testFactory.createTestMerchant(testUser.id);
+
+    // 生成认证token
+    authToken = JWTUtil.generateToken({
+      id: testUser.id,
+      username: testUser.username,
+      tenantId: testUser.tenantId,
+      roles: ['admin']
+    });
+
+    // 创建父商品
+    parentGoods = await testFactory.createTestGoods(testUser.id, {
+      name: '父商品测试',
+      price: 200.00,
+      costPrice: 150.00,
+      categoryId1: testCategory.id,
+      categoryId2: testCategory.id,
+      categoryId3: testCategory.id,
+      supplierId: testSupplier.id,
+      merchantId: testMerchant.id,
+      state: 1,
+      spuId: 0,
+      spuName: null
+    });
+
+    // 创建子商品1
+    childGoods1 = await testFactory.createTestGoods(testUser.id, {
+      name: '子商品1 - 红色',
+      price: 210.00,
+      costPrice: 160.00,
+      categoryId1: testCategory.id,
+      categoryId2: testCategory.id,
+      categoryId3: testCategory.id,
+      supplierId: testSupplier.id,
+      merchantId: testMerchant.id,
+      state: 1,
+      spuId: parentGoods.id,
+      spuName: '父商品测试'
+    });
+
+    // 创建子商品2
+    childGoods2 = await testFactory.createTestGoods(testUser.id, {
+      name: '子商品2 - 蓝色',
+      price: 220.00,
+      costPrice: 170.00,
+      categoryId1: testCategory.id,
+      categoryId2: testCategory.id,
+      categoryId3: testCategory.id,
+      supplierId: testSupplier.id,
+      merchantId: testMerchant.id,
+      state: 1,
+      spuId: parentGoods.id,
+      spuName: '父商品测试'
+    });
+
+    // 创建普通商品(非父子商品)
+    normalGoods = await testFactory.createTestGoods(testUser.id, {
+      name: '普通商品',
+      price: 100.00,
+      costPrice: 80.00,
+      categoryId1: testCategory.id,
+      categoryId2: testCategory.id,
+      categoryId3: testCategory.id,
+      supplierId: testSupplier.id,
+      merchantId: testMerchant.id,
+      state: 1,
+      spuId: 0,
+      spuName: null
+    });
+  });
+
+  describe('GET /api/v1/goods/:id/children', () => {
+    it('应该成功获取父商品的子商品列表', async () => {
+      const response = await client['{id}/children'].$get({
+        param: { id: parentGoods.id },
+        query: { page: 1, pageSize: 10 }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${authToken}`
+        }
+      });
+
+      console.debug('响应状态码:', response.status);
+      const data = await response.json();
+      console.debug('响应数据:', data);
+      expect(response.status).toBe(200);
+      expect(data.data).toHaveLength(2);
+      expect(data.total).toBe(2);
+      expect(data.page).toBe(1);
+      expect(data.pageSize).toBe(10);
+      expect(data.totalPages).toBe(1);
+
+      // 验证子商品数据
+      const childIds = data.data.map((item: any) => item.id);
+      expect(childIds).toContain(childGoods1.id);
+      expect(childIds).toContain(childGoods2.id);
+
+      // 验证子商品包含正确的关联关系
+      const firstChild = data.data[0];
+      expect(firstChild).toHaveProperty('category1');
+      expect(firstChild).toHaveProperty('supplier');
+      expect(firstChild).toHaveProperty('merchant');
+      expect(firstChild.spuId).toBe(parentGoods.id);
+      expect(firstChild.spuName).toBe('父商品测试');
+    });
+
+    it('应该验证父商品是否存在', async () => {
+      const response = await client['{id}/children'].$get({
+        param: { id: 99999 }, // 不存在的商品ID
+        query: { page: 1, pageSize: 10 },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(404);
+      const data = await response.json();
+      expect(data.code).toBe(404);
+      expect(data.message).toContain('父商品不存在');
+    });
+
+    it('应该支持搜索关键词过滤', async () => {
+      const response = await client['{id}/children'].$get({
+        param: { id: parentGoods.id },
+        query: { page: 1, pageSize: 10, keyword: '红色' },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      expect(data.data).toHaveLength(1);
+      expect(data.data[0].name).toBe('子商品1 - 红色');
+    });
+
+    it('应该支持排序', async () => {
+      // 修改子商品的排序字段
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const goodsRepo = dataSource.getRepository(GoodsMt);
+
+      await goodsRepo.update(childGoods1.id, { sort: 2 });
+      await goodsRepo.update(childGoods2.id, { sort: 1 });
+
+      const response = await client['{id}/children'].$get({
+        param: { id: parentGoods.id },
+        query: { page: 1, pageSize: 10, sortBy: 'sort', sortOrder: 'ASC' },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      expect(data.data[0].id).toBe(childGoods2.id); // sort=1 应该在前
+      expect(data.data[1].id).toBe(childGoods1.id); // sort=2 应该在后
+    });
+  });
+
+  describe('POST /api/v1/goods/:id/set-as-parent', () => {
+    it('应该成功将普通商品设为父商品', async () => {
+      const response = await client['{id}/set-as-parent'].$post({
+        param: { id: normalGoods.id },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      expect(data.id).toBe(normalGoods.id);
+      expect(data.spuId).toBe(0);
+      expect(data.spuName).toBeNull();
+
+      // 验证数据库中的更新
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const updatedGoods = await dataSource.getRepository(GoodsMt).findOne({
+        where: { id: normalGoods.id } as any
+      });
+      expect(updatedGoods?.spuId).toBe(0);
+      expect(updatedGoods?.spuName).toBeNull();
+    });
+
+    it('应该拒绝将子商品设为父商品', async () => {
+      const response = await client['{id}/set-as-parent'].$post({
+        param: { id: childGoods1.id },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(400);
+      const data = await response.json();
+      expect(data.code).toBe(400);
+      expect(data.message).toContain('子商品不能设为父商品');
+    });
+
+    it('应该验证商品是否存在', async () => {
+      const response = await client['{id}/set-as-parent'].$post({
+        param: { id: 99999 }, // 不存在的商品ID
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(404);
+      const data = await response.json();
+      expect(data.code).toBe(404);
+      expect(data.message).toContain('商品不存在');
+    });
+  });
+
+  describe('DELETE /api/v1/goods/:id/parent', () => {
+    it('应该成功解除子商品的父子关系', async () => {
+      const response = await client['{id}/parent'].$delete({
+        param: { id: childGoods1.id },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      expect(data.id).toBe(childGoods1.id);
+      expect(data.spuId).toBe(0);
+      expect(data.spuName).toBeNull();
+
+      // 验证数据库中的更新
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const updatedGoods = await dataSource.getRepository(GoodsMt).findOne({
+        where: { id: childGoods1.id } as any
+      });
+      expect(updatedGoods?.spuId).toBe(0);
+      expect(updatedGoods?.spuName).toBeNull();
+    });
+
+    it('应该拒绝解除非子商品的父子关系', async () => {
+      const response = await client['{id}/parent'].$delete({
+        param: { id: normalGoods.id },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(400);
+      const data = await response.json();
+      expect(data.code).toBe(400);
+      expect(data.message).toContain('该商品不是子商品');
+    });
+
+    it('应该验证商品是否存在', async () => {
+      const response = await client['{id}/parent'].$delete({
+        param: { id: 99999 }, // 不存在的商品ID
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(404);
+      const data = await response.json();
+      expect(data.code).toBe(404);
+      expect(data.message).toContain('商品不存在');
+    });
+  });
+
+  describe('POST /api/v1/goods/batch-create-children', () => {
+    it('应该成功批量创建子商品', async () => {
+      const specs = [
+        { name: '规格1 - 黑色', price: 230.00, costPrice: 180.00, stock: 50, sort: 1 },
+        { name: '规格2 - 白色', price: 240.00, costPrice: 190.00, stock: 60, sort: 2 },
+        { name: '规格3 - 金色', price: 250.00, costPrice: 200.00, stock: 70, sort: 3 }
+      ];
+
+      const response = await client.batchCreateChildren.$post({
+        json: {
+          parentGoodsId: parentGoods.id,
+          specs
+        },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      expect(data.success).toBe(true);
+      expect(data.count).toBe(3);
+      expect(data.children).toHaveLength(3);
+
+      // 验证子商品数据
+      data.children.forEach((child: any, index: number) => {
+        expect(child.name).toBe(specs[index].name);
+        expect(child.price).toBe(specs[index].price);
+        expect(child.costPrice).toBe(specs[index].costPrice);
+        expect(child.stock).toBe(specs[index].stock);
+        expect(child.sort).toBe(specs[index].sort);
+        expect(child.spuId).toBe(parentGoods.id);
+        expect(child.spuName).toBe('父商品测试');
+        expect(child.state).toBe(1);
+        expect(child.tenantId).toBe(testUser.tenantId);
+      });
+
+      // 验证数据库中的记录
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const children = await dataSource.getRepository(GoodsMt).find({
+        where: { spuId: parentGoods.id } as any,
+        order: { sort: 'ASC' }
+      });
+      expect(children).toHaveLength(5); // 原有2个 + 新增3个
+    });
+
+    it('应该验证父商品是否存在', async () => {
+      const response = await client.batchCreateChildren.$post({
+        json: {
+          parentGoodsId: 99999, // 不存在的父商品ID
+          specs: [{ name: '测试规格', price: 100, costPrice: 80, stock: 10, sort: 1 }]
+        },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(404);
+      const data = await response.json();
+      expect(data.code).toBe(404);
+      expect(data.message).toContain('父商品不存在');
+    });
+
+    it('应该验证父商品必须是父商品', async () => {
+      const response = await client.batchCreateChildren.$post({
+        json: {
+          parentGoodsId: childGoods1.id, // 子商品ID
+          specs: [{ name: '测试规格', price: 100, costPrice: 80, stock: 10, sort: 1 }]
+        },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(400);
+      const data = await response.json();
+      expect(data.code).toBe(400);
+      expect(data.message).toContain('只能为父商品创建子商品');
+    });
+
+    it('应该验证规格数据有效性', async () => {
+      const response = await client.batchCreateChildren.$post({
+        json: {
+          parentGoodsId: parentGoods.id,
+          specs: [] // 空规格列表
+        },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(400);
+      const data = await response.json();
+      expect(data.code).toBe(400);
+      expect(data.message).toContain('至少需要一个规格');
+    });
+
+    it('应该继承父商品的分类和其他信息', async () => {
+      const specs = [{ name: '继承测试规格', price: 100, costPrice: 80, stock: 10, sort: 1 }];
+
+      const response = await client.batchCreateChildren.$post({
+        json: {
+          parentGoodsId: parentGoods.id,
+          specs
+        },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      const child = data.children[0];
+
+      // 验证继承的字段
+      expect(child.categoryId1).toBe(parentGoods.categoryId1);
+      expect(child.categoryId2).toBe(parentGoods.categoryId2);
+      expect(child.categoryId3).toBe(parentGoods.categoryId3);
+      expect(child.goodsType).toBe(parentGoods.goodsType);
+      expect(child.supplierId).toBe(parentGoods.supplierId);
+      expect(child.merchantId).toBe(parentGoods.merchantId);
+    });
+
+    it('应该支持事务,全部成功或全部失败', async () => {
+      const specs = [
+        { name: '有效规格1', price: 100, costPrice: 80, stock: 10, sort: 1 },
+        { name: '', price: 100, costPrice: 80, stock: 10, sort: 2 }, // 无效:名称为空
+        { name: '有效规格3', price: 100, costPrice: 80, stock: 10, sort: 3 }
+      ];
+
+      const response = await client.batchCreateChildren.$post({
+        json: {
+          parentGoodsId: parentGoods.id,
+          specs
+        },
+        headers: { authorization: `Bearer ${authToken}` }
+      });
+
+      // 由于数据库约束,这个测试可能会失败
+      // 但重要的是验证事务机制
+      expect(response.status).toBe(500);
+
+      // 验证没有创建任何子商品(事务回滚)
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const children = await dataSource.getRepository(GoodsMt).find({
+        where: { spuId: parentGoods.id } as any
+      });
+      expect(children).toHaveLength(2); // 只有原有的2个子商品
+    });
+  });
+
+  describe('认证和授权', () => {
+    it('应该要求认证', async () => {
+      const response = await client['{id}/children'].$get({
+        param: { id: parentGoods.id },
+        query: { page: 1, pageSize: 10 }
+        // 不提供认证头
+      });
+
+      expect(response.status).toBe(401);
+    });
+
+    it('应该验证租户隔离', async () => {
+      // 创建另一个租户的用户和商品
+      const otherUser = await testFactory.createTestUser('other-tenant');
+      const otherAuthToken = JWTUtil.generateToken({
+        id: otherUser.id,
+        username: otherUser.username,
+        tenantId: otherUser.tenantId,
+        roles: ['admin']
+      });
+
+      // 尝试访问第一个租户的商品
+      const response = await client['{id}/children'].$get({
+        param: { id: parentGoods.id },
+        query: { page: 1, pageSize: 10 },
+        header: { authorization: `Bearer ${otherAuthToken}` }
+      });
+
+      expect(response.status).toBe(404);
+      const data = await response.json();
+      expect(data.code).toBe(404);
+      expect(data.message).toContain('父商品不存在');
+    });
+  });
+});