# 店铺商品分类 — 测试用例

> **依据：** 《店铺商品分类功能需求.md》v1.1、《店铺商品分类技术方案.md》v1.1  
> **关联：** 《商品分类测试用例.md》（二级分类边界）、《角色管理测试用例.md》（菜单权限）  
> **范围：** 商家端 `/agri/seller/shopCategory`；`CategoryServiceImpl`（店铺 scope）/ `SellerShopGoodsCategoryController`；`IShopGoodsCategoryFacade`  
> **排除：** 平台端 CRUD、商家「商品分类」`/agri/seller/category`、C 端完整导航 E2E  
> **环境：** RuoYi v3.9.2-springboot2；MySQL 5.7.39；请求头 **`X-Shop-Id`** 标识当前店铺

---

## 用例编号说明

| 前缀 | 类型 | 工具 |
|------|------|------|
| SGC-UT-xxx | 单元测试 | JUnit 5 + Mockito |
| SGC-API-xxx | 接口测试 | MockMvc / Apifox |
| SGC-UI-xxx | 界面测试 | Playwright（Chromium / Chrome） |

---

## 测试数据约定

| 实体 | 说明 |
|------|------|
| 商户 A | 店铺 **101**（张三农资店）、**102**（李四农资店）；经营账号 `13800001001` |
| 店铺 101 分类 | **一级** `1=店内专区`（sort=0）；**二级** `2=热销推荐`（parent=1, **有商品**）、`3=新品上市`（parent=1）、`4=隐藏区`（parent=1, show=0）、`5=待删`（parent=1, **无商品**） |
| 店铺 102 分类 | **一级** `501=他店专区`；**二级** `502=他店分类`（用于跨店越权） |
| 商品协作 | `goodsId=9001`，`shop_id=101`，`shop_category_id=2`（二级），`del_flag=0` |
| Token | 经营账号；`X-Shop-Id=101`；含 `agri:seller:shopCategory:*` |
| 员工 Token | 子管理员；默认 **无** 店铺商品分类权限 |
| 已删店 | 店铺 **103**（`biz_shop.del_flag=2`） |

---

## 一、单元测试

### SGC-UT-001 列表范围为当前店铺有效分类

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-001 |
| **测试模块** | 店铺商品分类 |
| **测试项** | selectList 数据范围 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SC1：仅当前店铺未删除分类 |
| **前置条件** | 店铺 101 有分类 1～4；店铺 102 有 501；分类 1 的 del_flag=0 |
| **测试步骤** | `SellerShopContext` 设 shopId=101，调用 `selectCategoryRowList` |
| **预期结果** | 返回 5 条（1 一级 + 4 二级）；**不含** 502 及 del_flag=2 记录 |

### SGC-UT-002 列表按 sort_no 升序再 create_time 升序

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-002 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 列表排序 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC5 |
| **前置条件** | 分类 sort_no 分别为 0、1、1（同 sort 时 create_time 2 早于 3） |
| **测试步骤** | 调用 selectList 无 keyword |
| **预期结果** | 首行 sort_no 最小；同 sort_no 时 create_time 较早者靠前 |

### SGC-UT-003 keyword 仅分类名称模糊匹配

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-003 |
| **测试模块** | 店铺商品分类 |
| **测试项** | keyword 检索 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 §5.4 可选检索 |
| **前置条件** | 存在「热销推荐」「新品上市」 |
| **测试步骤** | keyword=「热销」；再 keyword=「1」（按 ID） |
| **预期结果** | 前者命中「热销推荐」；后者 **不** 按 shopCategoryId 命中 |

### SGC-UT-004 requireShopId 未选店铺失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-004 |
| **测试模块** | 店铺商品分类 |
| **测试项** | requireShopId |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC9、§6 未选店铺 |
| **前置条件** | `SellerShopContext.getShopId()` 为 null |
| **测试步骤** | 任意 list/add 入口 |
| **预期结果** | 抛 `ServiceException`；msg 含「请先选择当前店铺」 |

### SGC-UT-005 店铺已逻辑删除 assertShopMaintable 失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-005 |
| **测试模块** | 店铺商品分类 |
| **测试项** | assertShopMaintable |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证删店后不可维护 |
| **前置条件** | shopId=103，`biz_shop.del_flag=2` |
| **测试步骤** | 调用 insertCategory / selectList |
| **预期结果** | 失败；msg 含「店铺已关闭，无法维护分类」 |

### SGC-UT-006 创建名称店铺内唯一-成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-006 |
| **测试模块** | 店铺商品分类 |
| **测试项** | assertCategoryNameUnique |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC3 正常路径 |
| **前置条件** | 店铺 101 无「促销专区」 |
| **测试步骤** | 创建 categoryName=促销专区 |
| **预期结果** | 校验通过 |

### SGC-UT-007 创建名称重复失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-007 |
| **测试模块** | 店铺商品分类 |
| **测试项** | assertCategoryNameUnique |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC3 同级唯一 |
| **前置条件** | 一级 1 下已有「热销推荐」 |
| **测试步骤** | 再在一级 1 下创建 categoryName=热销推荐 |
| **预期结果** | 失败；msg 含「同级分类名称已存在」 |

### SGC-UT-008 编辑名称排除自身可保留

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-008 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 编辑名称唯一 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 §8.1 排除自身 |
| **前置条件** | 编辑 shopCategoryId=1，名称仍为「热销推荐」 |
| **测试步骤** | updateCategory 不改 categoryName |
| **预期结果** | 校验通过 |

### SGC-UT-009 不同店铺同名允许

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-009 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 店间隔离 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC8 |
| **前置条件** | 店铺 101 已有「热销推荐」 |
| **测试步骤** | shopId=102 创建 categoryName=热销推荐 |
| **预期结果** | 校验通过；两店数据独立 |

### SGC-UT-010 名称为空失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-010 |
| **测试模块** | 店铺商品分类 |
| **测试项** | categoryName 校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 §7.1、§12 |
| **前置条件** | CreateDTO categoryName 为空或纯空格 |
| **测试步骤** | insertCategory |
| **预期结果** | 失败；msg 含「请输入分类名称」 |

### SGC-UT-011 sortNo 负数失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-011 |
| **测试模块** | 店铺商品分类 |
| **测试项** | sortNo 校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC4、§7.1 |
| **前置条件** | sortNo=-1 |
| **测试步骤** | insertCategory / updateCategory |
| **预期结果** | 校验失败 |

### SGC-UT-012 新增写入 shop_id 与默认值

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-012 |
| **测试模块** | 店铺商品分类 |
| **测试项** | insertCategory |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SC2、SC4 两级写入 |
| **前置条件** | shopId=101；POST 二级 body 含 categoryLevel=2、parentId=1、categoryPic |
| **测试步骤** | insertCategory |
| **预期结果** | `shop_id=101`；`parent_id=1`；`category_level=2`；`sort_no=0`；`show_flag=1`；`hot_flag=0` |

### SGC-UT-013 跨店铺 shopCategoryId 不可编辑

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-013 |
| **测试模块** | 店铺商品分类 |
| **测试项** | requireCategoryOfShop |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC9 跨店隔离 |
| **前置条件** | shopId=101；categoryId=502 属店铺 102 |
| **测试步骤** | updateCategory(502) |
| **预期结果** | 失败；msg 含「分类不存在」 |

### SGC-UT-014 删除无关联商品成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-014 |
| **测试模块** | 店铺商品分类 |
| **测试项** | deleteCategory |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §9 正常删除 |
| **前置条件** | categoryId=5；`existsByShopCategoryId(5)=false` |
| **测试步骤** | deleteCategoryByIds([5]) |
| **预期结果** | UPDATE del_flag=2；列表不再返回 |

### SGC-UT-015 删除有关联商品失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-015 |
| **测试模块** | 店铺商品分类 |
| **测试项** | existsByShopCategoryId |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SC6 |
| **前置条件** | categoryId=2；`IGoodsFacade.existsByShopCategoryId(2)=true` |
| **测试步骤** | deleteCategoryByIds([2]) |
| **预期结果** | 失败；msg 含「该分类下存在商品，请先修改商品分类或处理商品」；del_flag **不变** |

### SGC-UT-016 编辑 show_flag 不更新 biz_goods

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-016 |
| **测试模块** | 店铺商品分类 |
| **测试项** | SC7 联动 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SC7、§8.2 |
| **前置条件** | 分类 2 关联 goodsId=9001；show_flag 由 1 改 0 |
| **测试步骤** | updateCategory → 检查 goodsMapper 调用 |
| **预期结果** | **未** 调用 updateGoods；商品 goods_status 不变 |

### SGC-UT-017 options visibleOnly=true 过滤隐藏项

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-017 |
| **测试模块** | 店铺商品分类 |
| **测试项** | listOptionsByShopId |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC10、§3.4 |
| **前置条件** | 店铺 101 含 show_flag=0 的「隐藏区」 |
| **测试步骤** | `listOptionsByShopId(101, true)` |
| **预期结果** | **不含** categoryId=4（隐藏区）；含 2、3、5 等可见 **二级** |

### SGC-UT-018 options visibleOnly=false 含隐藏项

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-018 |
| **测试模块** | 店铺商品分类 |
| **测试项** | listOptionsByShopId |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 options 全量口径 |
| **前置条件** | 同 UT-017 |
| **测试步骤** | `listOptionsByShopId(101, false)` |
| **预期结果** | 含 2、3、4、5 全部有效 **二级** |

### SGC-UT-019 Facade existsByShopCategoryId 口径

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-019 |
| **测试模块** | 店铺商品分类 |
| **测试项** | existsByShopCategoryId SQL |
| **测试类型** | 单元测试 / SQL |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证技术方案 §4.2 |
| **前置条件** | 商品 9001 shop_category_id=2 del_flag=0；另有一条 del_flag=2 的历史商品 |
| **测试步骤** | 分别 exists(2)、exists(5) |
| **预期结果** | exists(2)=true（仅未删商品）；exists(5)=false |

### SGC-UT-020 切换店铺后列表 SQL 带新 shop_id

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-020 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 店铺切换 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 SC9 |
| **前置条件** | 101 有 5 条；102 有 2 条 |
| **测试步骤** | Context 设 101 selectList → 改 102 selectList |
| **预期结果** | 前者 5 条；后者 2 条（501、502） |

### SGC-UT-021 批量删除任一有商品则整批失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UT-021 |
| **测试模块** | 店铺商品分类 |
| **测试项** | deleteCategoryByIds 整批失败 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SC13 |
| **前置条件** | 勾选 id=2（有商品）与 id=5（无商品） |
| **测试步骤** | deleteCategoryByIds([2, 5]) |
| **预期结果** | 抛 `CategoryBatchDeleteException`；**两条均未** logicDelete；data.reasons 非空 |

---

## 二、接口测试

### SGC-API-001 列表分页成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-001 |
| **测试模块** | 店铺商品分类 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc / Apifox |
| **测试目的** | 验证 SC1、SC11 主流程 |
| **前置条件** | 经营账号 Token；Header `X-Shop-Id: 101`；`agri:seller:shopCategory:list` |
| **测试步骤** | `GET /agri/seller/shopCategory/list?pageNum=1&pageSize=10` |
| **预期结果** | `code=200`；rows 含 categoryName、showFlag；total≥4；**不含** 501 |

### SGC-API-002 列表 keyword 检索

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-002 |
| **测试模块** | 店铺商品分类 |
| **测试项** | GET /list keyword |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §5.4 |
| **前置条件** | 分类「热销推荐」「新品上市」 |
| **测试步骤** | `GET .../list?keyword=新品` → 清空 keyword 再请求 |
| **预期结果** | 前者仅「新品上市」；后者恢复全量 |

### SGC-API-003 scope 返回当前店铺信息

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-003 |
| **测试模块** | 店铺商品分类 |
| **测试项** | GET /scope |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §5.1 店铺上下文展示 |
| **前置条件** | `X-Shop-Id=101` |
| **测试步骤** | `GET /agri/seller/shopCategory/scope` |
| **预期结果** | `code=200`；data.shopId=101；data.shopName=张三农资店 |

### SGC-API-004 详情回显编辑字段

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-004 |
| **测试模块** | 店铺商品分类 |
| **测试项** | GET /{shopCategoryId} |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §8 编辑回显 |
| **前置条件** | `agri:seller:shopCategory:query` |
| **测试步骤** | `GET /agri/seller/shopCategory/2` |
| **预期结果** | `code=200`；含 categoryName、sortNo、showFlag |

### SGC-API-005 新增分类成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-005 |
| **测试模块** | 店铺商品分类 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc / Apifox |
| **测试目的** | 验证 §7 主流程 |
| **前置条件** | `agri:seller:shopCategory:add` |
| **测试步骤** | `POST /agri/seller/shopCategory` body：`{"categoryLevel":"2","parentId":1,"categoryName":"促销专区","categoryPic":"/profile/upload/x.jpg","sortNo":5,"showFlag":"1","hotFlag":"0"}` |
| **预期结果** | `code=200`；GET /list 可见新行；sortNo=5 |

### SGC-API-006 新增默认 sortNo 与 showFlag

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-006 |
| **测试模块** | 店铺商品分类 |
| **测试项** | POST / 默认值 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC5、§7.1 默认 |
| **前置条件** | body 仅 categoryLevel=1、categoryName、categoryPic |
| **测试步骤** | POST categoryName=默认测试类 |
| **预期结果** | 成功；sortNo=0；showFlag=1 |

### SGC-API-007 新增名称重复失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-007 |
| **测试模块** | 店铺商品分类 |
| **测试项** | POST / 名称 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC3 |
| **前置条件** | 已有「热销推荐」 |
| **测试步骤** | POST categoryName=热销推荐 |
| **预期结果** | 失败；msg 含「同级分类名称已存在」 |

### SGC-API-008 新增名称为空失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-008 |
| **测试模块** | 店铺商品分类 |
| **测试项** | POST / 必填 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §12 |
| **前置条件** | categoryName="" |
| **测试步骤** | POST 创建 |
| **预期结果** | 失败；msg 含「请输入分类名称」 |

### SGC-API-009 编辑分类成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-009 |
| **测试模块** | 店铺商品分类 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §8 主流程 |
| **前置条件** | `agri:seller:shopCategory:edit` |
| **测试步骤** | PUT body 改 categoryId=3 的 sortNo、showFlag、categoryName |
| **预期结果** | `code=200`；GET /2 与 /list 同步 |

### SGC-API-010 编辑隐藏分类不改动商品状态

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-010 |
| **测试模块** | 店铺商品分类 |
| **测试项** | PUT / SC7 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox + DB 断言 |
| **测试目的** | 验证 SC7 |
| **前置条件** | 分类 2 关联 goodsId=9001，goods_status=2（出售中） |
| **测试步骤** | PUT 分类 2 showFlag=0 |
| **预期结果** | 分类更新成功；goods 9001 goods_status **仍为** 2 |

### SGC-API-011 删除无商品分类成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-011 |
| **测试模块** | 店铺商品分类 |
| **测试项** | DELETE /{id} |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §9 正常删除 |
| **前置条件** | `agri:seller:shopCategory:remove`；categoryId=5 无商品 |
| **测试步骤** | `DELETE /agri/seller/shopCategory/5` |
| **预期结果** | `code=200`；/list **不含** 5；/level2Options **不含** 5 |

### SGC-API-012 删除有商品分类失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-012 |
| **测试模块** | 店铺商品分类 |
| **测试项** | DELETE 商品阻断 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC6 |
| **前置条件** | categoryId=2 关联 goodsId=9001 |
| **测试步骤** | DELETE /2 |
| **预期结果** | 失败；msg 含「该分类下存在商品」；分类仍在列表 |

### SGC-API-013 未携带 X-Shop-Id 写操作失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-013 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 店铺上下文 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §6、SC9 |
| **前置条件** | Token 有效；**无** X-Shop-Id |
| **测试步骤** | POST 新增分类 |
| **预期结果** | 失败；msg 含「请先选择当前店铺」 |

### SGC-API-014 跨店铺编辑失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-014 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 数据隔离 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC8、SC9 |
| **前置条件** | `X-Shop-Id=101`；shopCategoryId=501 属 102 |
| **测试步骤** | PUT 编辑 501 |
| **预期结果** | 失败；无跨店写库 |

### SGC-API-015 切换店铺后列表数据变化

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-015 |
| **测试模块** | 店铺商品分类 |
| **测试项** | X-Shop-Id 切换 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC9 |
| **前置条件** | 101、102 各有分类 |
| **测试步骤** | GET /list Header 101 → 改 Header 102 再 GET |
| **预期结果** | 101 返回本店 5 条；102 返回 501、502；**互不含** 对方数据 |

### SGC-API-016 无 list 权限拒绝

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-016 |
| **测试模块** | 店铺商品分类 |
| **测试项** | @PreAuthorize |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §2.5 菜单权限 |
| **前置条件** | Token **不含** `agri:seller:shopCategory:list` |
| **测试步骤** | GET /list |
| **预期结果** | HTTP 403 |

### SGC-API-017 level2Options 发品下拉

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-017 |
| **测试模块** | 店铺商品分类 |
| **测试项** | GET /level2Options |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC10、商品列表协作 |
| **前置条件** | 存在 show_flag=0 的「隐藏区」(id=4) |
| **测试步骤** | `GET /agri/seller/shopCategory/level2Options` |
| **预期结果** | `code=200`；data **仅含二级**；label 含「店内专区 > 热销推荐」格式 |

### SGC-API-018 已删店店铺拦截维护

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-018 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 删店拦截 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §2.2 店铺逻辑删除 |
| **前置条件** | `X-Shop-Id=103`（已逻辑删店） |
| **测试步骤** | GET /list |
| **预期结果** | 失败；msg 含「店铺已关闭，无法维护分类」 |

### SGC-API-019 响应结构含 code msg data

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-019 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 响应格式 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC11 |
| **前置条件** | 任意成功/失败接口 |
| **测试步骤** | 分别调用 GET /list、POST 重复名称 |
| **预期结果** | JSON 均含 `code`、`msg`；列表含 `rows`/`total`；详情/scope 含 `data` |

### SGC-API-020 与二级分类接口数据隔离

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-020 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 平台边界 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC2、与 `/agri/seller/category` 隔离 |
| **前置条件** | 本模块有「店内专区>热销推荐」；商品分类模块有「肥料>复合肥」 |
| **测试步骤** | GET /shopCategory/list 与 GET /seller/category/list |
| **预期结果** | 两接口 rows **shop_id 范围不同**；ID **不互通**；互不含对方记录 |

### SGC-API-021 删除后 options 不可选

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-021 |
| **测试模块** | 店铺商品分类（协作） |
| **测试项** | 删除后下游 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9.3、§10.1 |
| **前置条件** | 刚 DELETE 分类 5 |
| **测试步骤** | GET /level2Options |
| **预期结果** | data **不含** categoryId=5 |

### SGC-API-023 批量删除整批失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-023 |
| **测试模块** | 店铺商品分类 |
| **测试项** | DELETE /{ids} 批量 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SC13 |
| **前置条件** | id=2 有商品；id=5 无商品 |
| **测试步骤** | `DELETE /agri/seller/shopCategory/2,5` |
| **预期结果** | 失败；msg 含「整批操作已取消」；2、5 **均仍在** 列表 |

### SGC-API-022 员工无权限访问拒绝

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-API-022 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 子管理员权限 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §2.5、§1.4 |
| **前置条件** | 员工 Token；角色 **未** 分配 shopCategory 菜单 |
| **测试步骤** | GET /list |
| **预期结果** | HTTP 403 |

---

## 三、界面测试（Playwright）

> **通用前置：** Playwright 启动 **Chromium（Chrome 通道）**；商家端 URL 以环境为准（如 `http://localhost:80`）；经营账号 `13800001001` / 密码见测试环境；登录后 header 或页头携带当前店铺；菜单路径 **商品管理 → 店铺商品分类**（路由 `/agri/seller/shopCategory`）。

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

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-001 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 列表页 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright (Chrome) |
| **测试目的** | 验证 SC1、§5.1 |
| **前置条件** | 经营账号已登录；当前店铺=张三农资店；101 有 5 条分类（1 一级 + 4 二级） |
| **测试步骤** | 1. `page.goto` 商家端登录页并登录 2. 点击「店铺商品分类」 |
| **预期结果** | 页头展示「张三农资店」（只读）；表格含 **分类名称、分类图片、是否显示、是否热门、操作**；二级行有缩进；一级行有 **添加子分类** |

### SGC-UI-002 按分类名称检索

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-002 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 名称检索 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §5.4 |
| **前置条件** | 存在「热销推荐」「新品上市」 |
| **测试步骤** | 检索框输入「热销」→ 点「搜索」→ 点「重置」 |
| **预期结果** | 搜索后仅「热销推荐」；重置后恢复全量 |

### SGC-UI-003 添加分类成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-003 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 添加分类 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §7 主流程 |
| **前置条件** | 有 add 权限 |
| **测试步骤** | 1. 点「添加分类」2. 填名称=「限时特惠」、排序=10、是否显示=是 3. 确定 |
| **预期结果** | 成功提示；列表刷新出现「限时特惠」；是否显示=是 |

### SGC-UI-004 添加分类名称为空拦截

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-004 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 添加校验 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §12 |
| **前置条件** | 打开添加弹窗 |
| **测试步骤** | 名称留空 → 提交 |
| **预期结果** | 前端校验或后端提示「请输入分类名称」 |

### SGC-UI-005 添加分类名称重复提示

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-005 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 名称唯一 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 SC3 |
| **前置条件** | 已有「热销推荐」 |
| **测试步骤** | 添加名称=热销推荐 → 提交 |
| **预期结果** | 错误提示「该分类名称已存在」 |

### SGC-UI-006 编辑分类修改排序与显示

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-006 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 编辑分类 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8 |
| **前置条件** | 「新品上市」行 |
| **测试步骤** | 点「编辑」→ 改排序=0、是否显示=否 → 保存 |
| **预期结果** | 成功刷新；列表是否显示=否；排序变更后行位置前移（若展示排序） |

### SGC-UI-007 删除无商品分类二次确认成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-007 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 删除 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §9 二次确认与正常删除 |
| **前置条件** | 「待删」无关联商品 |
| **测试步骤** | 点「删除」→ 确认框点「确定」 |
| **预期结果** | 出现二次确认文案；确认后该行消失 |

### SGC-UI-008 删除有商品分类失败提示

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-008 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 删除阻断 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 SC6 |
| **前置条件** | 「热销推荐」有关联商品 |
| **测试步骤** | 点「删除」→ 确认 |
| **预期结果** | 提示「该分类下存在商品，请先修改商品分类或处理商品」；行仍在 |

### SGC-UI-009 切换店铺后列表整页刷新

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-009 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 多店切换 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 SC9、§6 |
| **前置条件** | 商户 A 有店铺 101、102；101 有 4 条分类；102 有 1 条 |
| **测试步骤** | 1. 当前 101 进入列表 2. 页头切换店铺为「李四农资店」 |
| **预期结果** | 页头店铺名变更；列表 **仅** 展示 102 的分类；**不含** 101 数据 |

### SGC-UI-010 员工无菜单不可进入

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-010 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 菜单权限 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §2.5、§1.4 |
| **前置条件** | 员工账号登录；角色无 shopCategory 权限 |
| **测试步骤** | 查看侧栏；`page.goto('/agri/seller/shopCategory')` |
| **预期结果** | 侧栏 **无**「店铺商品分类」；直链 403 或无权限页 |

### SGC-UI-011 隐藏分类在发品下拉不可见（协作）

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-011 |
| **测试模块** | 店铺商品分类（商品协作） |
| **测试项** | options 协作 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 SC10、§10.1 |
| **前置条件** | 存在 show=否 的「隐藏区」；商品列表已建设 |
| **测试步骤** | 进入「添加商品」→ 展开「店铺商品分类」下拉 |
| **预期结果** | 下拉 **不含**「隐藏区」；含「热销推荐」等可见项 |

### SGC-UI-012 删除确认框点取消不删除

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-012 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 删除取消 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §9 二次确认取消路径 |
| **前置条件** | 「待删」可删 |
| **测试步骤** | 点「删除」→ 确认框点「取消」 |
| **预期结果** | 弹窗关闭；列表 **仍含** 该行 |

### SGC-UI-013 列表默认排序与 SC5 一致

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-013 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 列表排序 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 SC5 |
| **前置条件** | 分类 sort 为 0、1、2、3 |
| **测试步骤** | 进入列表读取首列名称顺序 |
| **预期结果** | 首行「热销推荐」(sort=0)；末行 sort 最大项 |

### SGC-UI-014 与「商品分类」菜单并存互不混淆

| 要素 | 内容 |
|------|------|
| **用例编号** | SGC-UI-014 |
| **测试模块** | 店铺商品分类 |
| **测试项** | 菜单边界 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 SC2、§1.3 命名区分 |
| **前置条件** | 侧栏同时有「商品分类」「店铺商品分类」 |
| **测试步骤** | 分别进入两菜单页 |
| **预期结果** | 「商品分类」与「店铺商品分类」均为 **两级树/列表** 页；路由分别为 `/agri/seller/category` 与 `/agri/seller/shopCategory` |

---

## 四、业务规则覆盖矩阵

| 规则 | 单元 | 接口 | UI |
|------|:----:|:----:|:--:|
| SC1 当前店铺有效列表 | UT-001 | API-001 | UI-001 |
| SC2 两级且与商品分类隔离 | UT-012 | API-020 | UI-014 |
| SC3 同级名称唯一 | UT-006,007,008 | API-007 | UI-005 |
| SC4 名称/图片/排序/显示/热门 | UT-010,011,012 | API-005,006,008 | UI-003,004 |
| SC5 排序越小越靠前 | UT-002 | API-001 | UI-013 |
| SC6 有商品不可删/级联 | UT-015 | API-012 | UI-008 |
| SC7 改显示不改商品状态 | UT-016 | API-010 | — |
| SC8 店间独立/平台不维护 | UT-009 | API-014,020 | UI-009 |
| SC9 切换店铺/未选店铺 | UT-004,020 | API-013,015 | UI-009 |
| SC10 商品二级下拉 | UT-017,018 | API-017,021 | UI-011 |
| SC12 商品仅挂二级 | — | API-017 | UI-011 |
| SC13 批量删除整批失败 | UT-021 | API-023 | — |
| SC11 响应 code/msg/data | — | API-019 | — |
| 删店不可维护 | UT-005 | API-018 | — |
| 菜单权限 | — | API-016,022 | UI-010 |
| 删除二次确认 | UT-014 | API-011 | UI-007,012 |
| 名称检索 | UT-003 | API-002 | UI-002 |
| 编辑主流程 | UT-008 | API-009 | UI-006 |
| scope 店铺上下文 | — | API-003 | UI-001 |

---

## 五、修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版：20 单元 + 22 接口 + 14 UI；覆盖 SC1～SC11 及商品列表/二级分类边界 |
| **v1.1** | 对齐 v1.1 两级结构：测试数据改一级+二级；`/level2Options`；批量删除；级联删一级 |

---

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