# 店铺商品列表 — 测试用例

> **依据：** 《商品列表功能需求.md》v1.5、《店铺商品列表技术方案.md》v1.5  
> **关联：** 《商品审核功能需求.md》《商品分类功能需求.md》《店铺商品分类功能需求.md》v1.1、《属性模版功能需求.md》、《角色管理测试用例.md》  
> **范围：** 商家端 `/agri/seller/goods`；`GoodsServiceImpl`（商家 scope）/ `SellerGoodsController`；`IGoodsFacade` / 服务快照 / 属性模版与 **biz_goods_attr** 协作  
> **排除：** 平台端审核/监管 UI E2E、C 端下单、多规格 SKU/运费模版完整表单、属性模版 **维护** UI  
> **环境：** RuoYi v3.9.2-springboot2；MySQL 5.7.39；请求头 **`X-Shop-Id`** 标识当前店铺  
> **实现分期：** v1.0 补全 **已实现**（含 shopCategoryId、attrTemplateId、**biz_goods_attr**、删除、批量 submit/offShelf）；**商品副图 subPics**（`biz_goods_pic`）**已实现**（见技术方案 T13；`GoodsServiceImplTest` 4 例）；**O10 仍待实现**

---

## 用例编号说明

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

---

## 测试数据约定

| 实体 | 说明 |
|------|------|
| 商户 A | 店铺 **101**（张三农资店）、**102**（李四农资店）；经营账号 `13800001001` / 密码见环境 |
| 全局配置 | `default_audit_pass=0`（关，提交上架进 **待审核**）；测试免审时临时改为 `1` |
| 平台二级分类 | `categoryId=1002`（肥料>复合肥，`shop_id IS NULL`，level=2） |
| 店铺商品分类 | `shopCategoryId=2005`（店内专区>热销，`shop_id=101`，level=2） |
| 属性模版 | `attrTemplateId=4`（「手机」，`shop_id=101`）；跨店模版 `templateId=99`（`shop_id=102`） |
| 商品服务 | `serviceId=1`（24小时发货，默认显示）、`serviceId=3`（7天退换） |
| 店铺 101 商品 | `1001` 未上架(G001)、`1002` 待审核(G002)、`1003` 出售中(G003)、`1004` 审核失败(G004, reject=资料不全)、`1005` 已下架(G005) |
| 跨店商品 | `1006` 未上架，**shop_id=102** |
| Token | 经营账号；`X-Shop-Id=101`；含 `agri:seller:goods:*` |
| 员工 Token | 子管理员；默认 **无** 商品列表权限 |
| 未完成订单 | `orderId=8001` 关联 `goodsId=1003`，状态待发货（O10 下架阻断用，**接口待实现**） |

**状态码：** 0 未上架 / 1 待审核 / 2 出售中 / 3 审核失败 / 4 已下架

---

## 一、单元测试

### SGL-UT-001 商家列表仅当前店铺

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-001 |
| **测试模块** | 店铺商品列表 |
| **测试项** | selectSellerList 数据范围 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL1：列表范围=当前店铺未删除商品 |
| **前置条件** | 101 有 1001～1005；102 有 1006 |
| **测试步骤** | 调用 `selectSellerList(101L, query)` |
| **预期结果** | 返回 101 下 5 条；**不含** 1006 |

### SGL-UT-002 商家列表含未上架态

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-002 |
| **测试模块** | 店铺商品列表 |
| **测试项** | selectSellerList 含 status=0 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL2、与平台列表差异（商家含未上架） |
| **前置条件** | 101 含 status=0 的 1001 |
| **测试步骤** | `selectSellerList(101, null)` |
| **预期结果** | 结果集 **含** goods_status=0 记录；平台 `selectPlatformList` **不含** |

### SGL-UT-003 requireShopId 未选店铺失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-003 |
| **测试模块** | 店铺商品列表 |
| **测试项** | requireShopId |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 GL15 未选店铺 |
| **前置条件** | `SellerShopContext.getShopId()` 为 null；shopId 参数 null |
| **测试步骤** | 调用 `insertSellerGoods(null, dto, op)` |
| **预期结果** | 抛 `ServiceException`；msg 含「请先选择当前店铺」 |

### SGL-UT-004 新建保存为未上架

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-004 |
| **测试模块** | 店铺商品列表 |
| **测试项** | insertSellerGoods 初始状态 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL3：保存→未上架 |
| **前置条件** | 合法 GoodsSaveDTO；categoryId=1002 为二级 |
| **测试步骤** | `insertSellerGoods(101, dto, "seller")` |
| **预期结果** | insert 时 `goods_status='0'`；返回 goodsId |

### SGL-UT-005 新建生成全平台唯一编号

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-005 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GoodsSnGenerator |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证商品编号系统生成 |
| **前置条件** | `goodsSnGenerator.nextSn()` 返回 `G20260526000001` |
| **测试步骤** | insertSellerGoods |
| **预期结果** | 落库 `goods_sn=G20260526000001`；body **不可** 指定 sn |

### SGL-UT-006 保存禁止 body 携带 goodsStatus

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-006 |
| **测试模块** | 店铺商品列表 |
| **测试项** | rejectGoodsStatusInBody |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 GL13/P17：不可经保存改态 |
| **前置条件** | POST body 含 `goodsStatus=2` |
| **测试步骤** | insertSellerGoods / updateSellerGoods |
| **预期结果** | 失败；msg 含「请通过提交上架、审核或下架变更商品状态」 |

### SGL-UT-007 商品分类须平台二级

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-007 |
| **测试模块** | 店铺商品列表 |
| **测试项** | assertPlatformCategoryLevel2 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL11：仅挂平台二级分类 |
| **前置条件** | categoryId=1001 为一级或不存在 |
| **测试步骤** | insertSellerGoods |
| **预期结果** | 失败；msg 含「请选择平台二级分类」 |

### SGL-UT-008 新建必填项校验

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-008 |
| **测试模块** | 店铺商品列表 |
| **测试项** | validateSaveFields |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 §8.1 必填：名称、主图、详情、售价、库存、分类 |
| **前置条件** | 依次缺 categoryId、goodsName、mainPic、detailContent、salePrice、stock |
| **测试步骤** | insertSellerGoods |
| **预期结果** | 各场景分别失败并提示对应字段 |

### SGL-UT-009 编辑待审核状态不变

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-009 |
| **测试模块** | 店铺商品列表 |
| **测试项** | updateSellerGoods P14 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL6：待审核编辑后仍为待审核 |
| **前置条件** | 1002 status=1；修改 goodsName |
| **测试步骤** | updateSellerGoods → 检查 update SQL |
| **预期结果** | **未** UPDATE goods_status；仍为 1 |

### SGL-UT-010 编辑出售中状态不变

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-010 |
| **测试模块** | 店铺商品列表 |
| **测试项** | updateSellerGoods GL7 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL7：出售中可编辑资料，状态不变 |
| **前置条件** | 1003 status=2；修改 salePrice |
| **测试步骤** | updateSellerGoods |
| **预期结果** | 价格更新；goods_status **仍为** 2 |

### SGL-UT-011 提交上架免审关进待审核

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-011 |
| **测试模块** | 店铺商品列表 |
| **测试项** | submitGoods GL5 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 default_audit_pass=关 → 待审核 |
| **前置条件** | 1001 status=0；`getDefaultAuditPass()`=0 |
| **测试步骤** | `submitGoods(101, 1001, op)` |
| **预期结果** | 返回 goodsStatus=1；首次写入 submit_time |

### SGL-UT-012 提交上架免审开直接出售中

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-012 |
| **测试模块** | 店铺商品列表 |
| **测试项** | submitGoods 免审 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL5：default_audit_pass=开 → 出售中 |
| **前置条件** | 1001 status=0；`getDefaultAuditPass()`=1 |
| **测试步骤** | submitGoods |
| **预期结果** | goodsStatus=2 |

### SGL-UT-013 提交上架非法前置状态失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-013 |
| **测试模块** | 店铺商品列表 |
| **测试项** | submitGoods 状态校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL4：仅 0/3/4 可提交 |
| **前置条件** | 1002 status=1；1003 status=2 |
| **测试步骤** | 分别 submitGoods |
| **预期结果** | 均失败；msg 含「当前状态不可提交上架」 |

### SGL-UT-014 再次提交保留原 submit_time

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-014 |
| **测试模块** | 店铺商品列表 |
| **测试项** | submitGoods 重提 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §10.3 再次提交不覆盖首次时间 |
| **前置条件** | 1005 status=4 已有 submit_time=T1 |
| **测试步骤** | submitGoods |
| **预期结果** | updateSubmitStatus 传入 submitTime=T1；firstSubmit=false |

### SGL-UT-015 下架仅出售中成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-015 |
| **测试模块** | 店铺商品列表 |
| **测试项** | offShelfSeller |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL8 正常路径 |
| **前置条件** | 1003 status=2 |
| **测试步骤** | `offShelfSeller(101, 1003, op)` |
| **预期结果** | updateOffShelf 被调用；目标 status=4 |

### SGL-UT-016 下架非出售中失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-016 |
| **测试模块** | 店铺商品列表 |
| **测试项** | offShelfBatch 状态 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL8：待审核/未上架不可下架 |
| **前置条件** | 1001 status=0；1002 status=1 |
| **测试步骤** | offShelfSeller |
| **预期结果** | 抛 `GoodsBatchOperationException`；msg 含「仅出售中商品可下架」 |

### SGL-UT-017 跨店商品不可操作

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-017 |
| **测试模块** | 店铺商品列表 |
| **测试项** | requireGoodsOfShop |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL15/GL16：不可跨店、不可换店 |
| **前置条件** | shopId=101；goodsId=1006 属 102 |
| **测试步骤** | selectSellerDetail / submitGoods / offShelfSeller |
| **预期结果** | 失败；msg 含「无权操作该商品」 |

### SGL-UT-018 保存写入服务快照

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-018 |
| **测试模块** | 店铺商品列表 |
| **测试项** | saveServiceSnapshots |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL12：保存时写快照 |
| **前置条件** | serviceIds=[1,3] 均有效 |
| **测试步骤** | insertSellerGoods |
| **预期结果** | snapshotMapper 先删后插 2 条；含 name/intro/icon |

### SGL-UT-019 无效服务项拒绝保存

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-019 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 服务目录校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL12 无效 serviceId |
| **前置条件** | serviceIds 含已删或不存在 ID |
| **测试步骤** | insertSellerGoods |
| **预期结果** | 失败；msg 含「存在无效的商品服务项」 |

### SGL-UT-020 店铺商品分类校验

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-020 |
| **测试模块** | 店铺商品列表 |
| **测试项** | assertShopCategoryIfPresent |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL11：shopCategoryId 须本店二级 |
| **前置条件** | shopCategoryId=502 属 102；或传一级分类 ID |
| **测试步骤** | insertSellerGoods(101, dto含shopCategoryId) |
| **预期结果** | 失败；msg 提示分类无效 |

### SGL-UT-024 保存写入 attrTemplateId

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-024 |
| **测试模块** | 店铺商品列表 |
| **测试项** | assertAttrTemplateIfPresent + insert |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL18：合法 attrTemplateId 写入 biz_goods |
| **前置条件** | attrTemplateId=4 属 shop 101；categoryId=1002 有效 |
| **测试步骤** | insertSellerGoods(101, dto含attrTemplateId=4) |
| **预期结果** | insert 成功；BizGoods.attrTemplateId=4 |

### SGL-UT-025 无效 attrTemplateId 拒绝保存

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-025 |
| **测试模块** | 店铺商品列表 |
| **测试项** | assertAttrTemplateIfPresent |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL18：跨店或不存在模版拒绝 |
| **前置条件** | attrTemplateId=99 属 102 或不存在 |
| **测试步骤** | insertSellerGoods(101, dto含attrTemplateId) |
| **预期结果** | 失败；msg 含「属性模版」；不调用 insert |

### SGL-UT-026 保存写入 biz_goods_attr

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-026 |
| **测试模块** | 店铺商品列表 |
| **测试项** | saveGoodsAttrs |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL18：attributes/specs 先删后插 |
| **前置条件** | 合法 GoodsSaveDTO；attributes 含「品牌」→「华为」；specs 含「内存」→「8G」「16G」 |
| **测试步骤** | insertSellerGoods(101, dto) |
| **预期结果** | `goodsAttrMapper.deleteByGoodsId` + `batchInsert` 被调用 |

### SGL-UT-027 属性项校验失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-027 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GoodsAttrSupport 校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 属性与规格项名相同拒绝 |
| **前置条件** | attributes 与 specs 均含 itemName=「内存」 |
| **测试步骤** | insertSellerGoods |
| **预期结果** | 失败；msg 含「不可相同」 |

### SGL-UT-021 删除可删状态成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-021 |
| **测试模块** | 店铺商品列表 |
| **测试项** | deleteSellerGoodsBatch |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL9：0/3/4 可逻辑删除 |
| **前置条件** | 1001 status=0 |
| **测试步骤** | deleteSellerGoodsBatch(101, [1001]) |
| **预期结果** | del_flag=2 |

### SGL-UT-022 删除待审核/出售中失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-022 |
| **测试模块** | 店铺商品列表 |
| **测试项** | deleteSellerGoods 状态 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL9 阻断 |
| **前置条件** | 1002 status=1；1003 status=2 |
| **测试步骤** | deleteSellerGoodsBatch |
| **预期结果** | 失败；del_flag 不变 |

### SGL-UT-023 下架 O10 未完成订单阻断（待实现）

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UT-023 |
| **测试模块** | 店铺商品列表 |
| **测试项** | IOrderFacade O10 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GL8 订单约束 |
| **前置条件** | 1003 出售中；`hasUnfinishedOrderByGoodsId(1003)=true` |
| **测试步骤** | offShelfSeller |
| **预期结果** | **待实现**（`IGoodsOrderFacade` 接入后）失败；提示存在未完成订单 |

---

## 二、接口测试

### SGL-API-001 商品列表成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-001 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GET /list |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc / Apifox |
| **测试目的** | 验证 GL1 列表主流程 |
| **前置条件** | Token + `X-Shop-Id:101`；`agri:seller:goods:list` |
| **测试步骤** | `GET /agri/seller/goods/list?pageNum=1&pageSize=10` |
| **预期结果** | `code=200`；`rows` 含 goodsSn、goodsName、goodsStatus、categoryPath |

### SGL-API-002 列表按状态 Tab 筛选

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-002 |
| **测试模块** | 店铺商品列表 |
| **测试项** | goodsStatusQuery |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §5.2 五态筛选 |
| **前置条件** | 101 五态各至少 1 条 |
| **测试步骤** | 分别传 `goodsStatusQuery=0/1/2/3/4` |
| **预期结果** | 每次 rows **仅** 对应状态商品 |

### SGL-API-003 高级检索编号名称分类

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-003 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 检索参数 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6 高级检索 |
| **前置条件** | 存在 G001、名称含「复合肥」、categoryId=1002 |
| **测试步骤** | 分别 `goodsSn=G001`、`goodsName=复合`、`categoryId=1002` |
| **预期结果** | 各条件命中预期行；范围仍 **仅 101** |

### SGL-API-004 keyword 组合模糊

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-004 |
| **测试模块** | 店铺商品列表 |
| **测试项** | keyword |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证技术方案 keyword 口径 |
| **前置条件** | goodsSn=G001，goodsName=复合肥 |
| **测试步骤** | `keyword=G001`；`keyword=复合` |
| **预期结果** | 均命中 1001；keyword 不匹配他店商品 |

### SGL-API-005 切换店铺列表隔离

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-005 |
| **测试模块** | 店铺商品列表 |
| **测试项** | X-Shop-Id 切换 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL15 |
| **前置条件** | 101、102 各有商品 |
| **测试步骤** | Header 101 GET /list → 改 102 再 GET |
| **预期结果** | 两次 rows **互不含** 对方 shop 商品 |

### SGL-API-006 未传 X-Shop-Id 失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-006 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 店铺上下文 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 GL15 硬前置 |
| **前置条件** | 有效 Token；**无** X-Shop-Id |
| **测试步骤** | GET /list |
| **预期结果** | 业务失败；msg 含「请先选择当前店铺」 |

### SGL-API-007 商品详情全字段

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-007 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GET /{goodsId} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL17 详情可读 |
| **前置条件** | `agri:seller:goods:query`；1004 审核失败 |
| **测试步骤** | `GET /agri/seller/goods/1004` |
| **预期结果** | `code=200`；含 rejectReason、submitTime、services[]、stock |

### SGL-API-008 详情跨店 goodsId 拒绝

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-008 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 跨店详情 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 GL16 归属校验 |
| **前置条件** | X-Shop-Id=101；goodsId=1006 属 102 |
| **测试步骤** | GET /1006 |
| **预期结果** | 失败；msg 含「无权操作该商品」 |

### SGL-API-009 新增商品成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-009 |
| **测试模块** | 店铺商品列表 |
| **测试项** | POST / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §8 添加主流程 |
| **前置条件** | `agri:seller:goods:add` |
| **测试步骤** | POST body：categoryId=1002、goodsName、mainPic、detailContent、salePrice、stock、serviceIds |
| **预期结果** | `code=200`；data.goodsId 非空；DB goods_status=0 |

### SGL-API-010 新增携带 goodsStatus 拒收

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-010 |
| **测试模块** | 店铺商品列表 |
| **测试项** | POST 非法改态 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL13 |
| **前置条件** | body 含 `goodsStatus=2` |
| **测试步骤** | POST / |
| **预期结果** | 失败；未插入记录 |

### SGL-API-011 编辑商品成功且状态不变

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-011 |
| **测试模块** | 店铺商品列表 |
| **测试项** | PUT / |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §9 编辑 |
| **前置条件** | 1002 待审核；`agri:seller:goods:edit` |
| **测试步骤** | PUT 改 goodsName；再 GET 详情 |
| **预期结果** | 名称更新；goods_status **仍为** 1 |

### SGL-API-012 出售中编辑价格状态不变

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-012 |
| **测试模块** | 店铺商品列表 |
| **测试项** | PUT 出售中 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox + DB |
| **测试目的** | 验证 GL7 |
| **前置条件** | 1003 status=2 |
| **测试步骤** | PUT 改 salePrice |
| **预期结果** | 价格变更；goods_status **仍为** 2 |

### SGL-API-013 单独提交上架进待审核

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-013 |
| **测试模块** | 店铺商品列表 |
| **测试项** | PUT /{id}/submit |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL4/GL5 正常路径 |
| **前置条件** | 1001 status=0；default_audit_pass=0 |
| **测试步骤** | `PUT /agri/seller/goods/1001/submit` |
| **预期结果** | data.goodsStatus=1；submit_time 已写入 |

### SGL-API-014 免审店提交直接出售中

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-014 |
| **测试模块** | 店铺商品列表 |
| **测试项** | submit 免审 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL5 开路径 |
| **前置条件** | 临时 default_audit_pass=1；1001 status=0 |
| **测试步骤** | PUT /1001/submit |
| **预期结果** | data.goodsStatus=2 |

### SGL-API-015 待审核商品提交上架失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-015 |
| **测试模块** | 店铺商品列表 |
| **测试项** | submit 非法态 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL4 异常 |
| **前置条件** | 1002 status=1 |
| **测试步骤** | PUT /1002/submit |
| **预期结果** | 失败；status 仍为 1 |

### SGL-API-016 审核失败/已下架可重新提交

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-016 |
| **测试模块** | 店铺商品列表 |
| **测试项** | submit 3/4 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL4 可提交范围 |
| **前置条件** | 1004 status=3；1005 status=4 |
| **测试步骤** | 分别 PUT submit |
| **预期结果** | 均成功进入 1 或 2（视免审配置） |

### SGL-API-017 单独下架出售中成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-017 |
| **测试模块** | 店铺商品列表 |
| **测试项** | PUT /{id}/offShelf |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §11 下架主流程 |
| **前置条件** | 1003 status=2；`agri:seller:goods:offshelf` |
| **测试步骤** | `PUT /agri/seller/goods/1003/offShelf` |
| **预期结果** | `code=200`；goods_status=4；off_shelf_time 有值 |

### SGL-API-018 下架非出售中失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-018 |
| **测试模块** | 店铺商品列表 |
| **测试项** | offShelf 状态 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL8 异常 |
| **前置条件** | 1001 status=0 |
| **测试步骤** | PUT /1001/offShelf |
| **预期结果** | 失败；msg 含「仅出售中商品可下架」 |

### SGL-API-019 下架有未完成订单失败（待实现）

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-019 |
| **测试模块** | 店铺商品列表 |
| **测试项** | O10 订单约束 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL8 订单阻断 |
| **前置条件** | 1003 有 O10 未完成订单 |
| **测试步骤** | PUT /1003/offShelf |
| **预期结果** | **待实现**（`IGoodsOrderFacade` 接入后）失败；明确提示订单未完成 |

### SGL-API-020 删除未上架成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-020 |
| **测试模块** | 店铺商品列表 |
| **测试项** | DELETE /{id} |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL9 正常删除 |
| **前置条件** | 1001 status=0；`agri:seller:goods:remove` |
| **测试步骤** | `DELETE /agri/seller/goods/1001` |
| **预期结果** | code=200；列表不再返回 |

### SGL-API-021 删除出售中失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-021 |
| **测试模块** | 店铺商品列表 |
| **测试项** | DELETE 状态阻断 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL9 |
| **前置条件** | 1003 status=2 |
| **测试步骤** | DELETE /1003 |
| **预期结果** | 失败；商品仍在列表 |

### SGL-API-022 批量提交含非法态整批失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-022 |
| **测试模块** | 店铺商品列表 |
| **测试项** | PUT /submit 批量 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL10 |
| **前置条件** | 勾选 1001(0) 与 1003(2) |
| **测试步骤** | `PUT /agri/seller/goods/submit` body goodsIds |
| **预期结果** | 整批失败；data.reasons 非空；**均无** 状态变更 |

### SGL-API-023 批量下架整批失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-023 |
| **测试模块** | 店铺商品列表 |
| **测试项** | PUT /offShelf 批量 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL10 下架批量 |
| **前置条件** | goodsIds 含 1003(2) 与 1005(4) |
| **测试步骤** | `PUT /agri/seller/goods/offShelf` |
| **预期结果** | 整批失败；1003 **仍为** 出售中 |

### SGL-API-024 商品分类下拉

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-024 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GET /categoryOptions |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证发品「商品分类」数据源 |
| **前置条件** | 平台存在二级分类 |
| **测试步骤** | GET /agri/seller/goods/categoryOptions |
| **预期结果** | code=200；data **仅平台二级**（`shop_id IS NULL`）；label 含「一级 > 二级」 |

### SGL-API-025 商品服务下拉

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-025 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GET /serviceOptions |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL12 发品服务源 |
| **前置条件** | 平台服务目录有数据 |
| **测试步骤** | GET /serviceOptions |
| **预期结果** | data 含 all、defaultShow；defaultShow 为默认勾选项 |

### SGL-API-026 店铺商品分类下拉

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-026 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GET /shopCategoryOptions |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GL11 店铺分类下拉 |
| **前置条件** | 101 有店铺二级分类 2005 |
| **测试步骤** | GET /shopCategoryOptions |
| **预期结果** | 仅本店二级；label=`一级 > 二级` |

### SGL-API-027 无 list 权限拒绝

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

### SGL-API-028 平台审核后商家可见状态（协作）

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-028 |
| **测试模块** | 店铺商品列表（协作） |
| **测试项** | 平台 audit → 商家详情 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §13 状态机协作 |
| **前置条件** | 1002 待审核；平台 PUT /agri/goods/audit 驳回 |
| **测试步骤** | 商家 GET /1002 |
| **预期结果** | goods_status=3；rejectReason 非空 |

### SGL-API-029 属性模版预填

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-029 |
| **测试模块** | 店铺商品列表 |
| **测试项** | GET /attrTemplate/{templateId} |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证选用模版预填 attributes/specs |
| **前置条件** | templateId=4 属 shop 101 |
| **测试步骤** | GET /agri/seller/goods/attrTemplate/4 |
| **预期结果** | code=200；data.attributes / data.specs 结构与模版详情一致 |

### SGL-API-030 保存带 attributes 后详情回显

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-API-030 |
| **测试模块** | 店铺商品列表 |
| **测试项** | POST + GET 详情 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 biz_goods_attr 写入与聚合回显 |
| **前置条件** | 新建商品 body 含 attributes/specs |
| **测试步骤** | POST 保存 → GET 详情 |
| **预期结果** | 详情 attributes/specs 与保存体一致 |

---

## 三、界面测试（Playwright）

> **通用前置：** Playwright 启动 **Chromium（Chrome 通道）**；商家端 URL 以环境为准（如 `http://localhost:80`）；经营账号登录；菜单 **商品管理 → 商品列表**（路由 `/agri/seller/goods`）；页头携带当前店铺。

### SGL-UI-001 进入列表展示当前店铺

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-001 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 列表页加载 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright (Chrome) |
| **测试目的** | 验证 GL1、§17 页头 |
| **前置条件** | 已登录；当前店=张三农资店(101) |
| **测试步骤** | 1. 登录 2. 点击「商品列表」 |
| **预期结果** | 页头展示「张三农资店」；表格含编号、主图、名称、售价、销量、状态、操作 |

### SGL-UI-002 状态 Tab 筛选

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-002 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 状态 Tab |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §5.2 五态 Tab |
| **前置条件** | 101 五态均有数据 |
| **测试步骤** | 依次点「未上架」「待审核」「出售中」「审核失败」「已下架」Tab |
| **预期结果** | 每次表格 **仅** 展示对应状态行；「全部」恢复全量 |

### SGL-UI-003 高级检索与重置

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-003 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 检索表单 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §6 |
| **前置条件** | 存在 G001、名称「复合肥」 |
| **测试步骤** | 输入编号 G001 → 搜索 → 重置 → 再按名称搜索 |
| **预期结果** | 编号搜索命中单行；重置恢复；名称模糊命中 |

### SGL-UI-004 添加商品保存为未上架

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-004 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 添加商品 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8 主流程、GL3 |
| **前置条件** | 有 add 权限；已选商品分类 |
| **测试步骤** | 点「添加商品」→ 填必填项 → 点「保存」（**非**提交上架） |
| **预期结果** | 成功提示；列表「未上架」Tab 出现新商品 |

### SGL-UI-005 添加商品必填校验

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-005 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 表单校验 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §8.1 必填拦截 |
| **前置条件** | 打开添加表单 |
| **测试步骤** | 不填名称直接点保存 |
| **预期结果** | 前端/接口提示「请输入商品名称」等；未关闭表单 |

### SGL-UI-006 待审核商品可编辑

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-006 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 编辑待审核 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 GL6、P14 |
| **前置条件** | 1002 待审核 |
| **测试步骤** | 点「编辑」→ 改名称 → 保存 → 查看列表状态 |
| **预期结果** | 保存成功；状态列 **仍为**「待审核」 |

### SGL-UI-007 出售中商品可编辑

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-007 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 编辑出售中 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 GL7 |
| **前置条件** | 1003 出售中 |
| **测试步骤** | 编辑改库存 → 保存 |
| **预期结果** | 库存更新；状态 **仍为**「出售中」 |

### SGL-UI-008 未上架提交上架成功

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-008 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 提交上架 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §10、GL4 |
| **前置条件** | 1001 未上架；免审关 |
| **测试步骤** | 行操作点「提交上架」→ 确认 |
| **预期结果** | 成功提示目标态「待审核」；Tab 切换到待审核可见 |

### SGL-UI-009 出售中行无提交上架按钮

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-009 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 行操作按状态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §5.3 操作矩阵 |
| **前置条件** | 1003 出售中 |
| **测试步骤** | 查看该行操作按钮 |
| **预期结果** | **有**「下架」；**无**「提交上架」「删除」 |

### SGL-UI-010 下架二次确认

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-010 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 下架确认 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §11、§17 |
| **前置条件** | 1003 出售中 |
| **测试步骤** | 点「下架」→ 确认框点「取消」→ 再点「下架」→ 确认 |
| **预期结果** | 取消后仍为出售中；确认后变「已下架」 |

### SGL-UI-011 审核失败详情展示驳回原因

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-011 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 详情驳回原因 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 GL17 |
| **前置条件** | 1004 审核失败，reject=资料不全 |
| **测试步骤** | 点「查看详情」 |
| **预期结果** | 页面 **醒目** 展示「资料不全」；可操作「提交上架」 |

### SGL-UI-012 批量提交含非法态整批失败

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-012 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 批量提交上架 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 GL10 |
| **前置条件** | 勾选 1001(未上架) 与 1003(出售中) |
| **测试步骤** | 点「批量提交上架」 |
| **预期结果** | 提示含不可提交状态；**均不变**态 |

### SGL-UI-013 切换店铺列表刷新

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-013 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 店铺切换 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 GL15、§14 |
| **前置条件** | 101、102 各有商品 |
| **测试步骤** | 页头切换店铺 101→102 |
| **预期结果** | 页头店名变更；列表 **整页刷新** 为 102 商品 |

### SGL-UI-014 无权限不可见菜单

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-014 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 菜单权限 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §2.7 |
| **前置条件** | 员工 Token；角色无 goods 菜单 |
| **测试步骤** | 查看侧栏；`page.goto('/agri/seller/goods')` |
| **预期结果** | 侧栏 **无**「商品列表」；直达 URL 被拒绝或 403 |

### SGL-UI-015 空态引导添加商品

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-015 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 空列表 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §17 空态 |
| **前置条件** | 新建空店铺或无商品 |
| **测试步骤** | 进入商品列表 |
| **预期结果** | 展示空态文案；引导「添加商品」按钮可点 |

### SGL-UI-016 删除确认与状态限制

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-016 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 删除商品 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 GL9、§12 |
| **前置条件** | 1001 未上架可删；1002 待审核不可删 |
| **测试步骤** | 未上架点删除→确认；待审核行 **无** 删除或点击提示 |
| **预期结果** | 前者删除成功；后者不可删 |

### SGL-UI-017 发品默认勾选服务项

| 要素 | 内容 |
|------|------|
| **用例编号** | SGL-UI-017 |
| **测试模块** | 店铺商品列表 |
| **测试项** | 服务说明默认勾选 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright |
| **测试目的** | 验证 §2.5 默认显示=是 |
| **前置条件** | serviceOptions.defaultShow 含 serviceId=1 |
| **测试步骤** | 打开添加商品 → 查看「服务说明」区域 |
| **预期结果** | serviceId=1 **默认勾选**；可手动取消 |

---

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

| 规则 | 单元 | 接口 | UI |
|------|:----:|:----:|:--:|
| GL1 当前店铺列表 | UT-001 | API-001,005 | UI-001 |
| GL2 五态含未上架 | UT-002 | API-002 | UI-002 |
| GL3 保存→未上架 | UT-004 | API-009 | UI-004 |
| GL4 提交上架状态范围 | UT-013,014 | API-013,015,016 | UI-008,009 |
| GL5 免审策略 | UT-011,012 | API-013,014 | UI-008 |
| GL6 待审核可编辑 | UT-009 | API-011 | UI-006 |
| GL7 出售中可编辑 | UT-010 | API-012 | UI-007 |
| GL8 下架仅出售中/O10 | UT-015,016,023 | API-017,018,019 | UI-010 |
| GL9 删除状态限制 | UT-021,022 | API-020,021 | UI-016 |
| GL10 批量整批失败 | — | API-022,023 | UI-012 |
| GL11 分类二级/店铺分类 | UT-007,020 | API-024,026 | UI-004 |
| GL18 属性模版引用 | UT-024,025,026,027 | API-029,030 | — |
| GL12 服务快照 | UT-018,019 | API-025 | UI-017 |
| GL13 保存不可改态 | UT-006 | API-010 | — |
| GL14 外部变更不级联 | — | API-028 | — |
| GL15 店铺上下文 | UT-003,017 | API-005,006 | UI-013 |
| GL16 不可换店 | UT-017 | API-008 | — |
| GL17 详情与驳回原因 | — | API-007,028 | UI-011 |
| 高级检索 §6 | — | API-003,004 | UI-003 |
| 菜单权限 §2.7 | — | API-027 | UI-014 |
| 空态 §17 | — | — | UI-015 |

---

## 五、修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版：23 单元 + 28 接口 + 17 UI；覆盖 GL1～GL17 及状态机/检索/权限 |
| **v1.1** | 同步代码 v1.0 补全已实现；O10 仍标待实现；统一平台/店铺分类口径 |
| **v1.2** | 新增 GL18 / UT-024、025：attrTemplateId 保存与校验 |
| **v1.3** | 新增 UT-026/027、API-029/030：biz_goods_attr 保存与预填 |
| **v1.4** | 对齐 **subPics** 实现：技术方案 T13；单元覆盖 `insert_withSubPics` 等（API/UI 用例待补） |

---

*文档版本：v1.4 · 关联《商品列表功能需求.md》v1.5、《店铺商品列表技术方案.md》v1.5*
