# 商品分类 — 测试用例

> **依据：** 《商品分类功能需求.md》v1.4、《商品分类技术方案.md》v1.2  
> **范围：** **平台端 + 商家端** 商品分类 CRUD；`ICategoryFacade` / `IGoodsFacade.existsByCategoryId` 契约；平台只读 `GET /agri/goods/categoryOptions`（商品模块挂载，本文作协作验证）  
> **排除：** C 端完整类目页 E2E、三级分类、会员体系、商品发品/审核独立用例（删类仅验证 `existsByCategoryId` 阻断）  
> **环境：** RuoYi v3.9.2；平台 `/agri/category`、商家 `/agri/seller/category`（`X-Shop-Id`）；列表 `GET /list` 为分页 `TableDataInfo`

---

## 用例编号说明

| 前缀 | 类型 | 工具 |
|------|------|------|
| CAT-UT-xxx | 单元测试 | JUnit 5 + Mockito（Service/Validator/Facade） |
| CAT-API-xxx | 接口测试 | Apifox / REST Assured + 商家端 Token + `X-Shop-Id` |
| CAT-UI-xxx | 界面测试 | Playwright（Chromium/Chrome） |

---

## 一、单元测试

### CAT-UT-001 一级分类 parent_id=0 且 level=1

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 层级校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 C2 |
| **前置条件** | 合法一级 DTO |
| **测试步骤** | `validateLevel(dto)`，`categoryLevel=1`，`parentId=0` |
| **预期结果** | 通过；持久化 `parent_id=0`，`category_level=1` |

### CAT-UT-002 二级 parent 必须为本店一级

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 层级校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 C3 |
| **前置条件** | 本店存在一级 ID=1 |
| **测试步骤** | 二级 `parentId=1` |
| **预期结果** | 通过；`category_level=2` |

### CAT-UT-003 二级 parent 指向二级节点拒绝

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 层级校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证禁止三级（C1） |
| **前置条件** | 本店二级节点 ID=10 |
| **测试步骤** | 新增 `parentId=10` |
| **预期结果** | 失败；msg「二级分类的上级必须是一级分类」或「不支持三级分类」 |

### CAT-UT-004 同店同 parent 下名称唯一

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 名称唯一 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 G3 |
| **前置条件** | `shop_id=1, parent_id=0` 已有「肥料」 |
| **测试步骤** | 再增一级名「肥料」 |
| **预期结果** | 失败；「同级分类名称已存在」 |

### CAT-UT-005 编辑名称唯一排除自身

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 名称唯一 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §8.3 |
| **前置条件** | 同级另有「农药」；当前编辑「肥料」 |
| **测试步骤** | 改名为「农药」 |
| **预期结果** | 失败；改名为「肥料」自身则通过 |

### CAT-UT-006 不同店铺同名允许

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 店铺隔离 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 G1 |
| **前置条件** | 店铺 A 已有「肥料」 |
| **测试步骤** | 店铺 B 新增一级「肥料」 |
| **预期结果** | 通过 |

### CAT-UT-007 删二级-有关联商品阻断

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删除 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 G5 |
| **前置条件** | `IGoodsFacade.existsByCategoryId(10)=true` |
| **测试步骤** | `deleteCategory(10)` |
| **预期结果** | 失败；msg 含「存在商品」 |

### CAT-UT-008 删二级-无商品成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删除 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.3 正向 |
| **前置条件** | `existsByCategoryId=false` |
| **测试步骤** | 删除二级 |
| **预期结果** | `del_flag=2` |

### CAT-UT-009 删一级-任一下属二级有商品阻断

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删除 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §9.4 一级校验 |
| **前置条件** | 一级下二级 10 有商品、二级 11 无 |
| **测试步骤** | `deleteCategory(一级ID)` |
| **预期结果** | 失败；msg「该一级分类下存在商品」 |

### CAT-UT-010 删一级-级联逻辑删全部二级

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删除 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 G4 |
| **前置条件** | 一级下 2 个二级均无商品 |
| **测试步骤** | 删除一级 |
| **预期结果** | 一级及下属二级 `del_flag=2` |

### CAT-UT-011 批量删除任一条失败整批不删

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 批量删除 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 G9 |
| **前置条件** | ID 列表含可删与不可删 |
| **测试步骤** | `deleteCategories(ids)` |
| **预期结果** | 抛错/返回失败；所有 ID `del_flag` 不变 |

### CAT-UT-012 批量混选一级展开子级后校验

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 批量删除 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §3.5 展开顺序 |
| **前置条件** | 勾选一级（含二级 10、11）+ 二级 12 |
| **测试步骤** | 执行批量删除预展开 |
| **预期结果** | 最终校验集合含 10、11、12 去重；顺序先校验再删 |

### CAT-UT-013 `isCategoryVisible` 二级与父级均显示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | ICategoryFacade |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §4.2 可见性 |
| **前置条件** | 二级 `show_flag=1`；父一级 `show_flag=1`；均未删 |
| **测试步骤** | `isCategoryVisible(categoryId)` |
| **预期结果** | `true` |

### CAT-UT-014 `isCategoryVisible` 父级隐藏则 false

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | ICategoryFacade |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证父级 show 联动 |
| **前置条件** | 二级 show=1；父一级 show=0 |
| **测试步骤** | `isCategoryVisible` |
| **预期结果** | `false` |

### CAT-UT-015 `getCategoryPath` 格式

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | ICategoryFacade |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证路径拼接 |
| **前置条件** | 一级「肥料」、二级「复合肥」 |
| **测试步骤** | `getCategoryPath(二级ID)` |
| **预期结果** | 返回「肥料 > 复合肥」 |

### CAT-UT-016 `listLevel2ByShopId` 仅未删二级

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | ICategoryFacade |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 G7 发品下拉 |
| **前置条件** | 店内有二级与已删二级各一 |
| **测试步骤** | `listLevel2ByShopId(shopId)` |
| **预期结果** | 仅 `del_flag=0` 且 `category_level=2` |

### CAT-UT-017 编辑一级忽略 parentId 变更

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 编辑 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §3.4 一级 parent 不可改 |
| **前置条件** | 一级 `parent_id=0` |
| **测试步骤** | `PUT` 传 `parentId=99` |
| **预期结果** | 库中仍为 `0` |

### CAT-UT-018 编辑二级可换上级并校验新同级名唯一

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 编辑 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §8.2 换上级 |
| **前置条件** | 二级原属一级 A；一级 B 下无同名 |
| **测试步骤** | `parentId` 改为 B |
| **预期结果** | 保存成功；`parent_id` 更新 |

### CAT-UT-019 店铺已逻辑删除拦截维护

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 店铺上下文 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 §8.3 |
| **前置条件** | `IShopFacade` 返回店铺 `del_flag=2` |
| **测试步骤** | 任意分类写操作 |
| **预期结果** | 失败；「店铺已关闭，无法维护分类」 |

### CAT-UT-020 列表排序：一级优先再 sort_no

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 列表排序 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §5.2 |
| **前置条件** | 构造多一级、多二级不同 sort_no |
| **测试步骤** | `buildListRows()` |
| **预期结果** | 先按一级 sort 升序；其下二级同规则；同 sort 按 create_time |

### CAT-UT-021 写入同时设置 parent_id 与 category_level

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 新增 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §8.5 |
| **前置条件** | 二级 DTO |
| **测试步骤** | `insertCategory` |
| **预期结果** | 两字段与 DTO 一致，无漂移 |

### CAT-UT-022 `existsByCategoryId` 不统计已删商品

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | IGoodsFacade 口径 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 §4.1.2 |
| **前置条件** | 仅存在 `del_flag=2` 商品指向该分类 |
| **测试步骤** | `existsByCategoryId` |
| **预期结果** | `false` |

### CAT-UT-023 修改 show_flag 不调用商品更新

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 编辑 Service |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit + Mockito |
| **测试目的** | 验证 G8 |
| **前置条件** | Mock `IGoodsFacade` |
| **测试步骤** | 仅改 `showFlag=0` 保存 |
| **预期结果** | 分类更新成功；`IGoodsFacade` 无 UPDATE 商品调用 |

### CAT-UT-024 `sortNo` 须 ≥0

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 字段校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit |
| **测试目的** | 验证 §8.2 |
| **前置条件** | `sortNo=-1` |
| **测试步骤** | 校验 |
| **预期结果** | 不通过 |

---

## 二、接口测试

### CAT-API-001 分类列表分页与店铺 scope

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | GET /list、GET /scope |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §5.2 店铺上下文与分页 |
| **前置条件** | 商家 Token；`X-Shop-Id=1`；`agri:seller:category:list` |
| **测试步骤** | 1. `GET /agri/seller/category/list?pageNum=1&pageSize=10` 2. `GET /agri/seller/category/scope` |
| **预期结果** | 列表 `code=200`；顶层 `rows`、`total`；`scope` 返回 `shopId=1`、`shopName` 非空；`rows` 仅本店 `del_flag=0` |

### CAT-API-002 列表一级行含 canAddChild

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证列表字段 |
| **前置条件** | 存在一级分类 |
| **测试步骤** | `GET /list` |
| **预期结果** | 一级 `canAddChild=true`；二级含 `parentName` |

### CAT-API-003 列表按 keyword 模糊（若已实现）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | GET /list?keyword= |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §5.3 |
| **前置条件** | 存在「复合肥」二级 |
| **测试步骤** | `GET /list?keyword=复合` |
| **预期结果** | 命中二级及其层级关系展示 |

### CAT-API-004 树形接口 children 嵌套（若已实现）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | GET /tree |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §3.2.1 |
| **前置条件** | 一级下含二级 |
| **测试步骤** | `GET /agri/seller/category/tree` |
| **预期结果** | `tree[].children` 含二级节点 |

### CAT-API-005 一级下拉 level1Options

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | GET /level1Options |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证添加二级上级源 |
| **前置条件** | 本店≥1 个一级 |
| **测试步骤** | `GET /level1Options` |
| **预期结果** | 仅一级、未删除项 |

### CAT-API-006 二级下拉 level2Options 含全路径 label

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | GET /level2Options |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 G7 发品复用 |
| **前置条件** | 一级「肥料」下二级「复合肥」 |
| **测试步骤** | `GET /level2Options` |
| **预期结果** | `label` 为「肥料 > 复合肥」 |

### CAT-API-007 新增一级分类成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6 主流程 |
| **前置条件** | `agri:seller:category:add` |
| **测试步骤** | `POST /` body：`categoryLevel=1`，必填字段，默认 show=1 hot=0 sort=0 |
| **预期结果** | `code=200`；列表可见；`parentId=0` |

### CAT-API-008 新增二级分类成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §7 |
| **前置条件** | 本店一级 ID=1 |
| **测试步骤** | `POST /` `categoryLevel=2`，`parentId=1` |
| **预期结果** | `code=200`；`categoryLevel=2` |

### CAT-API-009 同级名称重复失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 G3 |
| **前置条件** | 一级已有「肥料」 |
| **测试步骤** | 再 POST 一级名「肥料」 |
| **预期结果** | 失败；「同级分类名称已存在」 |

### CAT-API-010 二级 parent 为二级节点失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 C1 |
| **前置条件** | 二级 ID=10 |
| **测试步骤** | `parentId=10` 新增 |
| **预期结果** | 失败；非法上级或禁止三级 |

### CAT-API-011 缺少分类图片失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §8.2 |
| **前置条件** | — |
| **测试步骤** | `categoryPic` 为空提交 |
| **预期结果** | 校验失败；未入库 |

### CAT-API-012 编辑分类名称与 show_flag

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §8 |
| **前置条件** | 存在二级；`agri:seller:category:edit` |
| **测试步骤** | `PUT /` 改 `categoryName`、`showFlag=0` |
| **预期结果** | `code=200`；详情已更新；关联商品状态不变 |

### CAT-API-013 编辑二级更换 parentId

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证换上级 |
| **前置条件** | 两个一级 A、B；二级原属 A |
| **测试步骤** | `PUT` `parentId=B` |
| **预期结果** | 成功；新 parent 下名称唯一校验生效 |

### CAT-API-014 删除二级无商品成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | DELETE /{ids} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9.3 |
| **前置条件** | 二级无关联商品 |
| **测试步骤** | `DELETE /agri/seller/category/{id}` |
| **预期结果** | `code=200`；列表不可见 |

### CAT-API-015 删除二级有关联商品失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | DELETE /{ids} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 G5 |
| **前置条件** | 商品 `category_id` 指向该二级 |
| **测试步骤** | `DELETE` |
| **预期结果** | 失败；msg 含「存在商品」 |

### CAT-API-016 删除一级级联删除二级

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | DELETE /{ids} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 G4 |
| **前置条件** | 一级下 2 二级均无商品 |
| **测试步骤** | `DELETE` 一级 ID |
| **预期结果** | 一级及子二级均 `del_flag=2` |

### CAT-API-017 删除一级-子级有商品失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | DELETE /{ids} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9.4 |
| **前置条件** | 一级下某二级有商品 |
| **测试步骤** | `DELETE` 一级 |
| **预期结果** | 失败；一级及子级均未删 |

### CAT-API-018 批量删除全部成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | DELETE 批量 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证批量正向 |
| **前置条件** | 多个无商品二级 ID |
| **测试步骤** | `DELETE /id1,id2` |
| **预期结果** | 全部删除成功 |

### CAT-API-019 批量删除任一条失败整批取消

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | DELETE 批量 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 G9 |
| **前置条件** | ID1 无商品；ID2 有商品 |
| **测试步骤** | `DELETE /ID1,ID2` |
| **预期结果** | `code=500` 或业务失败；`data.reasons` 非空；两条均未删 |

### CAT-API-020 批量混选一级自动展开子级校验

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | DELETE 批量 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §3.5 混选 |
| **前置条件** | 勾选一级（子级 10 有商品） |
| **测试步骤** | 仅传一级 ID 批量删 |
| **预期结果** | 整批失败；reasons 指明子分类有商品 |

### CAT-API-021 缺少店铺上下文拒绝

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 shop 上下文 |
| **前置条件** | Token 有效；不传 `X-Shop-Id` 且无 Session 店铺 |
| **测试步骤** | `GET /list` |
| **预期结果** | 403/400；提示选择或绑定当前店铺 |

### CAT-API-022 店铺已关闭维护分类失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §8.3 |
| **前置条件** | `shop_id` 对应店铺 `del_flag=2` |
| **测试步骤** | `POST` 新增分类 |
| **预期结果** | 失败；「店铺已关闭，无法维护分类」 |

### CAT-API-023 跨店操作他店分类 ID 失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 G1 隔离 |
| **前置条件** | 当前店 A；分类属店 B |
| **测试步骤** | `PUT` 店 B 的 `categoryId` |
| **预期结果** | 404 或无权；店 B 数据不变 |

### CAT-API-024 无 list 权限 403

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 权限 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §7 |
| **前置条件** | Token 无 `agri:seller:category:list` |
| **测试步骤** | `GET /list` |
| **预期结果** | HTTP 403 |

### CAT-API-025 无 remove 权限删除失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 权限 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证删除权限 |
| **前置条件** | 无 `agri:seller:category:remove` |
| **测试步骤** | `DELETE /{id}` |
| **预期结果** | 403 |

### CAT-API-026 平台 categoryOptions 按店铺只读

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类（协作） |
| **测试项** | GET /agri/goods/categoryOptions |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §4.1.1 平台筛类 |
| **前置条件** | 平台 Token `agri:goods:list`；店 1 有二级分类 |
| **测试步骤** | `GET /agri/goods/categoryOptions?shopId=1` |
| **预期结果** | `code=200`；`label` 为全路径；**无** 写接口 |

### CAT-API-027 平台 categoryOptions 缺 shopId 失败

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类（协作） |
| **测试项** | GET /agri/goods/categoryOptions |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证必填参数 |
| **前置条件** | 平台权限 |
| **测试步骤** | 不传 `shopId` |
| **预期结果** | 参数校验失败 |

### CAT-API-028 停业店铺仍可维护分类（接口层）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 G10 商家端不拦分类 |
| **前置条件** | 店铺 `shop_status=1`（停业）且未逻辑删除 |
| **测试步骤** | `POST` 新增一级 |
| **预期结果** | `code=200`（停业仅影响 C 端下单，不拦分类 CRUD） |

---

## 三、界面测试（Playwright）

> **通用前置：** 店铺经营账号登录商家后台；已选择 **当前店铺**；Chromium；菜单「商品分类」（路由以实际为准）。

### CAT-UI-001 进入分类列表展示当前店铺

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 列表页 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright (Chrome) |
| **测试目的** | 验证 §5.2、§12 多店铺 |
| **前置条件** | 已登录并选定店铺「张三农资店」 |
| **测试步骤** | 1. 登录商家端 2. 进入「商品分类」 |
| **预期结果** | 页头/顶部展示当前店铺名称；表格含分类名称、图片、是否显示、是否热门、排序、操作 |

### CAT-UI-002 一级分类行显示「添加子分类」

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 列表操作 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §5.1 |
| **前置条件** | 存在一级分类 |
| **测试步骤** | 查看一级行操作列 |
| **预期结果** | 有「添加子分类」；二级行 **无** 该按钮 |

### CAT-UI-003 二级分类层级缩进或 parentName

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 列表展示 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证层级清晰 |
| **前置条件** | 一级下存在二级 |
| **测试步骤** | 查看列表 |
| **预期结果** | 二级有缩进/树线或展示所属一级名称 |

### CAT-UI-004 添加一级分类成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 添加一级 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §6 主流程 |
| **前置条件** | 有 add 权限；图片可上传 |
| **测试步骤** | 添加分类→填名称/图/排序→提交 |
| **预期结果** | 成功刷新；新一级出现在列表；上级为顶级 |

### CAT-UI-005 从一级行添加子分类

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 添加二级 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §7.1 入口 |
| **前置条件** | 一级「肥料」存在 |
| **测试步骤** | 点击该行「添加子分类」→填写→提交 |
| **预期结果** | 二级挂在「肥料」下；上级已预选 |

### CAT-UI-006 添加二级时上级仅可选一级

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 添加表单 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 C3 UI |
| **前置条件** | 打开添加/子分类表单 |
| **测试步骤** | 展开「上级分类」下拉 |
| **预期结果** | 仅列本店一级；**无** 二级选项 |

### CAT-UI-007 编辑分类修改显示与热门

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 编辑 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8 |
| **前置条件** | 存在可编辑分类 |
| **测试步骤** | 编辑→改「是否显示」为否→保存 |
| **预期结果** | 列表更新；**不** 提示商品自动下架 |

### CAT-UI-008 删除二级二次确认成功

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删除 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §9.3 UI |
| **前置条件** | 二级下无商品 |
| **测试步骤** | 删除→确认弹窗确定 |
| **预期结果** | 行消失；提示成功 |

### CAT-UI-009 删除二级有关联商品阻断提示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删除 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 G5 提示 |
| **前置条件** | 二级下有关联商品 |
| **测试步骤** | 点击删除 |
| **预期结果** | Toast/弹窗：「存在商品，请先修改商品分类或处理商品」；行仍在 |

### CAT-UI-010 删除一级级联确认文案

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删除一级 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §9.2 |
| **前置条件** | 一级下无商品关联 |
| **测试步骤** | 删除一级→查看确认框 |
| **预期结果** | 文案含「同时删除其下全部二级分类」；确认后一级与下属二级均消失 |

### CAT-UI-011 批量删除整批失败汇总

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 批量删除 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 G9 UI |
| **前置条件** | 勾选 1 个可删 + 1 个有商品 |
| **测试步骤** | 批量删除→确定 |
| **预期结果** | 提示整批已取消及原因汇总；勾选行均未删 |

### CAT-UI-012 切换店铺后列表刷新

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 店铺上下文 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §12 多店铺 |
| **前置条件** | 商户下店铺 A、B 分类数据不同 |
| **测试步骤** | 当前店 A 打开分类→切换至店 B |
| **预期结果** | 页头店铺名变为 B；列表为 B 的分类，与 A 不一致 |

### CAT-UI-013 必填项为空提交校验

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 表单校验 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证必填 |
| **前置条件** | 打开新增 |
| **测试步骤** | 不填名称直接提交 |
| **预期结果** | 表单项错误提示；不提交成功 |

### CAT-UI-014 同级名称重复提示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 添加 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 G3 UI |
| **前置条件** | 已有「肥料」 |
| **测试步骤** | 再添加一级「肥料」 |
| **预期结果** | 「同级分类名称已存在」 |

### CAT-UI-015 分类图片上传预览

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 图片上传 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §12 图片预览 |
| **前置条件** | 准备 jpg 小于 5MB |
| **测试步骤** | 上传分类图 |
| **预期结果** | 预览显示；提交后列表缩略图可见 |

### CAT-UI-016 店铺已关闭无法进入分类维护

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 删店后 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §13.2 |
| **前置条件** | 平台已逻辑删除该店铺 |
| **测试步骤** | 商家尝试进入该店分类页 |
| **预期结果** | 提示店铺已关闭/不可维护；无法新增 |

### CAT-UI-017 无 remove 权限隐藏删除

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 权限 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证按钮权限 |
| **前置条件** | 子账号仅 list+edit |
| **测试步骤** | 打开分类列表 |
| **预期结果** | 无删除/批量删除按钮 |

### CAT-UI-018 列表按排序值升序展示

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 排序 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §5.2 sort |
| **前置条件** | 同级 sort 分别为 0、5、2 |
| **测试步骤** | 查看列表顺序 |
| **预期结果** | 显示顺序 0→2→5 |

### CAT-UI-019 名称 keyword 检索（若已实现）

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 高级检索 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §5.3 |
| **前置条件** | 存在「复合肥」 |
| **测试步骤** | 输入「复合」查询 |
| **预期结果** | 仅显示匹配项及层级 |

### CAT-UI-020 商家端无会员入口；平台/商家分类菜单分离

| 要素 | 内容 |
|------|------|
| **测试模块** | 商品分类 |
| **测试项** | 边界 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 G6、G11 |
| **前置条件** | 平台/商家后台已登录 |
| **测试步骤** | 分别浏览平台「平台商品→商品分类」与商家「商品管理→商品分类」菜单 |
| **预期结果** | 平台端维护平台分类；商家端维护本店分类；**无** 会员绑定项 |

---

## 四、用例统计

| 类型 | 数量 | 编号段 |
|------|------|--------|
| 单元测试 | 24 | CAT-UT-001～024 |
| 接口测试 | 28 | CAT-API-001～028 |
| 界面测试 | 20 | CAT-UI-001～020 |
| **合计** | **72** | — |

---

## 五、业务规则覆盖索引

| 规则 | 含义 | 对应用例（示例） |
|------|------|------------------|
| G1 | 平台/店铺数据隔离 | CAT-UT-006, CAT-API-023 |
| G2 | 仅两级 | CAT-UT-001～003, CAT-API-010 |
| G3 | 同级名唯一 | CAT-UT-004/005, CAT-API-009, CAT-UI-014 |
| G4 | 删一级级联 | CAT-UT-010, CAT-API-016, CAT-UI-010 |
| G5 | 有商品不可删 | CAT-UT-007/009, CAT-API-015/017, CAT-UI-009 |
| G6 | 平台 + 商家端 CRUD | CAT-API-026, CAT-UI-020 |
| G7 | 商品仅挂二级 | CAT-API-006, CAT-UT-016 |
| G8 | 改显示不自动改商品 | CAT-UT-023, CAT-API-012, CAT-UI-007 |
| G9 | 批量整批失败 | CAT-UT-011/012, CAT-API-019/020, CAT-UI-011 |
| G10 | 停业可维护分类 | CAT-API-028 |
| G11 | 经营账号、无会员 | CAT-UI-020 |

| 功能域 | 正常流程 | 异常/约束 |
|--------|----------|-----------|
| 列表 | CAT-API-001/002, CAT-UI-001～003 | CAT-API-021, CAT-UI-012 |
| 新增 | CAT-API-007/008, CAT-UI-004～006 | CAT-API-009～011, CAT-UI-013/014 |
| 编辑 | CAT-API-012/013, CAT-UI-007 | CAT-API-023 |
| 删除 | CAT-API-014/016/018, CAT-UI-008/010 | CAT-API-015/017/019/020, CAT-UI-009/011 |
| Facade/协作 | CAT-UT-013～016/022, CAT-API-006/026/027 | CAT-UT-014 |
| 权限/店铺 | CAT-API-024/025, CAT-UI-017 | CAT-API-022, CAT-UI-016 |

---

## 六、测试数据建议

| 数据 | 用途 |
|------|------|
| 店铺 A / B 各一套分类树 | G1 隔离、切换店铺 UI |
| 一级+多个二级（均无商品） | 级联删除、批量删正向 |
| 二级下挂未删除商品 | 删类阻断 |
| 一级下部分二级有商品 | 删一级阻断 |
| show/hot 不同组合 | 可见性 Facade |
| 停业未删店铺 | CAT-API-028 |
| 已逻辑删除店铺 | CAT-API-022、CAT-UI-016 |
| 平台商品 list 权限账号 | categoryOptions 协作 |

---

## 七、Playwright 执行说明（UI 用例）

**依赖：** Node.js 18+；`@playwright/test`；`npx playwright install chromium`。

**建议配置：**

```typescript
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
  testDir: './tests/seller/category',
  use: {
    baseURL: process.env.SELLER_BASE_URL || 'http://localhost:80/seller',
    ...devices['Desktop Chrome'],
    extraHTTPHeaders: {
      'X-Shop-Id': process.env.SHOP_ID || '1',
    },
  },
});
```

**登录示例（经营账号）：**

```typescript
import { test, expect } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  await page.goto('/login');
  await page.fill('input[name="username"]', process.env.SELLER_USER!);
  await page.fill('input[name="password"]', process.env.SELLER_PASS!);
  await page.click('button:has-text("登")');
  // 若需选店
  await page.getByText('张三农资店').click();
  await page.getByRole('menuitem', { name: '商品分类' }).click();
});
```

> UI 用例 **CAT-UI-001～020** 与上表一一对应编写 spec；选择器按商家端实际 DOM / `data-testid` 调整。

---

*文档版本：v1.0 · 关联《商品分类功能需求.md》v1.3.1、《商品分类技术方案.md》v1.1*
