# 商品详情内页 — 测试用例（C 端）

> **依据：** 《商品详情内页功能需求.md》v1.1、《商品详情内页技术方案.md》v1.4  
> **关联：** C 端《商城首页测试用例.md》MHP-UI-012（HM12 进详情）、《商品分类测试用例.md》CGC-UI-012（GC11）、《搜索页测试用例.md》CSR-UI-020、《关联需求分析.md》§11（GD15 四条件）  
> **范围：** C 端 `/api/goods/{goodsId}`、`/can-purchase`、`/reviews`；`GoodsAppServiceImpl` / `GoodsAppController` / `GoodsPicSupport`；详情页 UI 各区块与底栏交互  
> **代码落位：** `baqing-shop/src/main/java/com/ruoyi/web/modules/home/`（**已实现**）  
> **排除：** 购物车写入 POST、确认订单/支付、评价发表、店铺主页完整能力、多规格 SKU 价库矩阵（v1 统一规格）、`biz_goods_review` 表未建时的评价内容校验  
> **环境：** RuoYi v3.9.2-springboot2；MySQL 5.7.39；C 端 **无需 Token**（读接口 `@Anonymous`）；UI 使用 Playwright **`channel: 'chrome'`**

---

## 用例编号说明

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

> **命名：** **CGD** = Consumer Goods Detail（C 端商品详情）；与商家 **SGL-** 区分。

---

## 测试数据约定

| 实体 | 说明 |
|------|------|
| 店铺 | **S101** 开业 `shop_phone=13800000001`；**S102** **停业**；**S103** 已删 |
| 出售中商品 | **G-OK** @S101 主图 `/m.jpg` 副图 2 张 价88 库存100 销量50 属性/服务快照齐全；**G-SUB0** 仅主图无副图；**G-ZERO** stock=0；**G-HIDE** 分类隐藏 |
| 非出售中 | **G-OFF** 已下架 status=4；**G-WAIT** 待审核 status=1；**G-DRAFT** 未上架 status=0 |
| 异常 | **G-DEL** 商品已删；**G-S103** 所属店 S103 已删（JOIN 失败） |
| 服务快照 | **SV-SHIP** 名称含「24小时发货」；**SV-REFUND**「7天无理由」；**SV-DEL** 对应平台服务已删（**不应**出现在 `services`） |
| 属性快照 | **G-OK** 含 attr_type=1「产地」、attr_type=2「规格」展示项 |
| 会员 | **M1** 正常；**M2** **禁用**；**访客** 未登录 |
| 评价 | `biz_goods_review` **未建表** 时接口返空；UI 测「暂无评价」 |

---

## 一、单元测试

### CGD-UT-001 主图+副图合集（GD4）

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-001 |
| **测试模块** | 商品详情内页 |
| **测试项** | `GoodsPicSupport.buildGalleryPics` |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 GD4、§4.1 轮播合集 |
| **前置条件** | mainPic=`/m.jpg`；subPics=`[/s1.jpg,/s2.jpg]` |
| **测试步骤** | 调用 `buildGalleryPics(mainPic, subPics)` |
| **预期结果** | 返回 `[/m.jpg,/s1.jpg,/s2.jpg]`；**主图首张** |

### CGD-UT-002 仅主图无副图

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-002 |
| **测试模块** | 商品详情内页 |
| **测试项** | `GoodsPicSupport` 单图 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 §4.1 仅主图 |
| **前置条件** | mainPic=`/m.jpg`；subPics 空 |
| **测试步骤** | `buildGalleryPics` |
| **预期结果** | 返回 `[/m.jpg]`；长度 1 |

### CGD-UT-003 副图空白 URL 过滤

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-003 |
| **测试模块** | 商品详情内页 |
| **测试项** | `GoodsPicSupport` 清洗 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证副图入库容错 |
| **前置条件** | subPics 含 `""`、`"  "` |
| **测试步骤** | `buildGalleryPics("/m.jpg", subPics)` |
| **预期结果** | 仅保留非空 URL；主图仍首张 |

### CGD-UT-004 getDetail 合并 pics

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-004 |
| **测试模块** | 商品详情内页 |
| **测试项** | `GoodsAppServiceImpl.getDetail` |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §2.3 Service 聚合 |
| **前置条件** | Mock base 含 mainPic；`selectPicUrlsByGoodsId` 返 2 条 |
| **测试步骤** | `getDetail(goodsId)` |
| **预期结果** | `vo.pics` = 主图 + 2 副图；调 PicMapper 1 次 |

### CGD-UT-005 商品不存在拒绝

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-005 |
| **测试模块** | 商品详情内页 |
| **测试项** | getDetail 404 业务 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §12 商品不存在 |
| **前置条件** | `selectDetailBase` 返 null |
| **测试步骤** | `getDetail(99L)` |
| **预期结果** | 抛 `ServiceException`；msg=「商品不存在」 |

### CGD-UT-006 分类路径 Facade

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-006 |
| **测试模块** | 商品详情内页 |
| **测试项** | categoryPath |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §2.4、GD6 |
| **前置条件** | base.categoryId=10；Facade 返 `兽药 > 抗生素` |
| **测试步骤** | `getDetail` |
| **预期结果** | `categoryPath` 与 Facade 一致 |

### CGD-UT-007 属性/规格快照分组

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-007 |
| **测试模块** | 商品详情内页 |
| **测试项** | attributes / specDisplay |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GD6、§4.3 |
| **前置条件** | Mock `selectByGoodsId` 含 attr_type=1、2 行 |
| **测试步骤** | `getDetail` |
| **预期结果** | `attributes`、`specDisplay` 非空且分组正确 |

### CGD-UT-008 服务快照可见过滤

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-008 |
| **测试模块** | 商品详情内页 |
| **测试项** | services |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GD17、§2.3 P20 |
| **前置条件** | Mock `selectVisibleByGoodsId` 仅返 SV-REFUND |
| **测试步骤** | `getDetail` |
| **预期结果** | `services` 与 Mapper 一致；**不含** 已删目录项 |

### CGD-UT-009 承诺发货时间解析

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-009 |
| **测试模块** | 商品详情内页 |
| **测试项** | logistics.shipPromise |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §4.4、§2.2 v1 |
| **前置条件** | services 含名称「24小时发货」 |
| **测试步骤** | `getDetail` |
| **预期结果** | `logistics.shipPromise=24小时发货`；`freightDesc`、`shipCity` 为 null |

### CGD-UT-010 purchase 块透传 Facade

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-010 |
| **测试模块** | 商品详情内页 |
| **测试项** | purchase |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GD15、§3.2 |
| **前置条件** | Mock `canPurchase` 返 allowed=false reason=库存不足 |
| **测试步骤** | `getDetail` |
| **预期结果** | `purchase.allowed=false`；reason 一致 |

### CGD-UT-011 非出售中仍可返回详情（浏览层）

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-011 |
| **测试模块** | 商品详情内页 |
| **测试项** | 浏览 vs 可购 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GD15、§6.1 |
| **前置条件** | base.goodsStatus=4（已下架）；Facade purchase=false |
| **测试步骤** | `getDetail` |
| **预期结果** | **不** 抛异常；返回 VO；`purchase.allowed=false` |

### CGD-UT-012 店铺与售后电话

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-012 |
| **测试模块** | 商品详情内页 |
| **测试项** | shop / afterSalePhone |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 GD8、GD13、§4.6 |
| **前置条件** | base.shopPhone=`13800000001` |
| **测试步骤** | `getDetail` |
| **预期结果** | `shop.shopPhone`、`afterSalePhone` 均为该号码 |

### CGD-UT-013 店铺电话为空返 null

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-013 |
| **测试模块** | 商品详情内页 |
| **测试项** | afterSalePhone 空 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §2.5 选填 |
| **前置条件** | base.shopPhone 空或 null |
| **测试步骤** | `getDetail` |
| **预期结果** | `afterSalePhone=null` |

### CGD-UT-014 canPurchase 委托 Facade

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-014 |
| **测试模块** | 商品详情内页 |
| **测试项** | canPurchase |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §3.3 |
| **前置条件** | Mock Facade 返 allowed=true |
| **测试步骤** | `canPurchase(goodsId)` |
| **预期结果** | 仅调 `IGoodsPurchaseFacade.canPurchase` 1 次；结果一致 |

### CGD-UT-015 评价列表未建表返空

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-015 |
| **测试模块** | 商品详情内页 |
| **测试项** | listReviews |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 |
| **测试目的** | 验证 §2.4 未建表 |
| **前置条件** | 无 |
| **测试步骤** | `listReviews(goodsId)` |
| **预期结果** | 空 List；**不** 抛异常 |

### CGD-UT-016 goodsBrief v1 恒 null

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UT-016 |
| **测试模块** | 商品详情内页 |
| **测试项** | goodsBrief |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §2.2 v1 无字段 |
| **前置条件** | G-OK 正常 base |
| **测试步骤** | `getDetail` |
| **预期结果** | `goodsBrief=null` |

---

## 二、接口测试

### CGD-API-001 详情匿名无需 Token

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-001 |
| **测试模块** | 商品详情内页 |
| **测试项** | GET /api/goods/{id} 鉴权 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc / Apifox |
| **测试目的** | 验证 §3 读接口 @Anonymous |
| **前置条件** | 不传 Authorization；G-OK 存在 |
| **测试步骤** | GET `/api/goods/{G-OK.id}` |
| **预期结果** | HTTP 200；`code=200` |

### CGD-API-002 详情响应契约完整

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-002 |
| **测试模块** | 商品详情内页 |
| **测试项** | GoodsDetailAppVO 字段 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §3.2、GD5 |
| **前置条件** | G-OK 数据齐全 |
| **测试步骤** | GET 详情 |
| **预期结果** | `data` 含 goodsId、goodsName、pics、salePrice、stock、salesCount、goodsStatus、goodsStatusLabel、categoryPath、detailContent、attributes、specDisplay、services、logistics、shop、purchase、afterSalePhone |

### CGD-API-003 pics 主图+副图顺序

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-003 |
| **测试模块** | 商品详情内页 |
| **测试项** | pics[] 合集 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 GD4 |
| **前置条件** | G-OK 主图+2 副图 |
| **测试步骤** | GET 详情 |
| **预期结果** | `pics[0]`=主图 URL；后续为副图；长度=3 |

### CGD-API-004 pics 仅主图

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-004 |
| **测试模块** | 商品详情内页 |
| **测试项** | pics 单元素 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §4.1 G-SUB0 |
| **前置条件** | G-SUB0 无 biz_goods_pic 行 |
| **测试步骤** | GET 详情 |
| **预期结果** | `pics.length=1`；等于 main_pic |

### CGD-API-005 商品不存在失败

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-005 |
| **测试模块** | 商品详情内页 |
| **测试项** | 404 业务 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §12 |
| **前置条件** | G-DEL 或不存在 id |
| **测试步骤** | GET `/api/goods/999999` |
| **预期结果** | 业务失败；msg 含「商品不存在」 |

### CGD-API-006 所属店铺已删视为不存在

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-006 |
| **测试模块** | 商品详情内页 |
| **测试项** | JOIN 条件 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §2.2 SQL |
| **前置条件** | G-S103 商品 del=0 但店 S103 del=2 |
| **测试步骤** | GET 详情 |
| **预期结果** | 业务失败「商品不存在」 |

### CGD-API-007 可购校验 allowed=true

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-007 |
| **测试模块** | 商品详情内页 |
| **测试项** | GET /can-purchase 成功 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 GD15 四条件全满足 |
| **前置条件** | G-OK 出售中、有库存、S101 开业、分类可见 |
| **测试步骤** | GET `/api/goods/{id}/can-purchase` |
| **预期结果** | `data.allowed=true` |

### CGD-API-008 非出售中不可购

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-008 |
| **测试模块** | 商品详情内页 |
| **测试项** | 条件① |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §11 #1、G-OFF |
| **前置条件** | G-OFF status=4 |
| **测试步骤** | GET can-purchase |
| **预期结果** | `allowed=false`；reason 含「非出售中」或「下架」 |

### CGD-API-009 库存为零不可购

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-009 |
| **测试模块** | 商品详情内页 |
| **测试项** | 条件④ |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §11 #4、G-ZERO |
| **前置条件** | G-ZERO stock=0 但 status=2 |
| **测试步骤** | GET can-purchase |
| **预期结果** | `allowed=false`；reason=库存不足 |

### CGD-API-010 店铺停业不可购

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-010 |
| **测试模块** | 商品详情内页 |
| **测试项** | 条件② GD16 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §11 #2 |
| **前置条件** | 商品 @S102 停业 |
| **测试步骤** | GET can-purchase |
| **预期结果** | `allowed=false`；reason 含「停业」 |

### CGD-API-011 分类不可见不可购

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-011 |
| **测试模块** | 商品详情内页 |
| **测试项** | 条件③ |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §11 #3、G-HIDE |
| **前置条件** | G-HIDE 挂载隐藏分类 |
| **测试步骤** | GET can-purchase |
| **预期结果** | `allowed=false`；reason 含「分类不可见」 |

### CGD-API-012 下架商品详情仍可浏览

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-012 |
| **测试模块** | 商品详情内页 |
| **测试项** | 浏览层 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GD15 浏览≠可购 |
| **前置条件** | G-OFF del=0 |
| **测试步骤** | GET 详情 |
| **预期结果** | HTTP 200；`purchase.allowed=false`；`goodsStatusLabel=已下架` |

### CGD-API-013 详情 purchase 块与 can-purchase 一致

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-013 |
| **测试模块** | 商品详情内页 |
| **测试项** | purchase 一致性 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §3.2 实现要点 |
| **前置条件** | G-ZERO |
| **测试步骤** | ① GET 详情 ② GET can-purchase |
| **预期结果** | 两次 `allowed`、`reason` **一致** |

### CGD-API-014 不含 B 端字段

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-014 |
| **测试模块** | 商品详情内页 |
| **测试项** | VO 边界 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §3.2 GD1 |
| **前置条件** | G-OK |
| **测试步骤** | GET 详情 |
| **预期结果** | **无** rejectReason、canAudit、merchantName 等商家/平台字段 |

### CGD-API-015 服务快照字段

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-015 |
| **测试模块** | 商品详情内页 |
| **测试项** | services[] |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GD7、§4.5 |
| **前置条件** | G-OK 勾选 SV-REFUND、SV-SHIP |
| **测试步骤** | GET 详情 |
| **预期结果** | `services` 含 serviceName、serviceIcon、serviceIntro；**不含** SV-DEL |

### CGD-API-016 属性快照字段

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-016 |
| **测试模块** | 商品详情内页 |
| **测试项** | attributes / specDisplay |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GD6 |
| **前置条件** | G-OK 有 biz_goods_attr |
| **测试步骤** | GET 详情 |
| **预期结果** | `attributes`、`specDisplay` 为数组；项含名称与值列表 |

### CGD-API-017 shop 子对象

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-017 |
| **测试模块** | 商品详情内页 |
| **测试项** | shop |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GD13 |
| **前置条件** | G-OK @S101 |
| **测试步骤** | GET 详情 |
| **预期结果** | `shop.shopId/shopName/shopAvatar/shopStatus` 有值；v1 `rating`、`fansCount` 可为 null |

### CGD-API-018 评价分页空列表

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-018 |
| **测试模块** | 商品详情内页 |
| **测试项** | GET /reviews |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §3.4 未建表 |
| **前置条件** | biz_goods_review 未建 |
| **测试步骤** | GET `/api/goods/{id}/reviews` |
| **预期结果** | `code=200`；`rows=[]`；`total=0`；**不** 404 |

### CGD-API-019 评价摘要 pageSize=2

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-019 |
| **测试模块** | 商品详情内页 |
| **测试项** | reviews 分页参数 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GD14、§10.1 |
| **前置条件** | 评价表落地后 ≥3 条；未落地则 **跳过** 并注明 |
| **测试步骤** | GET `.../reviews?pageNum=1&pageSize=2` |
| **预期结果** | `rows.length≤2`；按 create_time 降序 |

### CGD-API-020 零库存仍可 GET 详情

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-020 |
| **测试模块** | 商品详情内页 |
| **测试项** | 浏览层零库存 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §1.3 浏览≠拦截 |
| **前置条件** | G-ZERO status=2 stock=0 |
| **测试步骤** | GET 详情 |
| **预期结果** | HTTP 200；展示 stock=0；`purchase.allowed=false` |

### CGD-API-021 待审核直链详情

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-021 |
| **测试模块** | 商品详情内页 |
| **测试项** | G-WAIT 直链 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §2.2、§6.1 |
| **前置条件** | G-WAIT status=1 |
| **测试步骤** | GET 详情 + can-purchase |
| **预期结果** | 详情 200；`purchase.allowed=false` |

### CGD-API-022 logistics v1 可空字段

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-022 |
| **测试模块** | 商品详情内页 |
| **测试项** | logistics |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §2.2 v1 |
| **前置条件** | 无含「发货」服务快照 |
| **测试步骤** | GET 详情 |
| **预期结果** | `logistics.shipPromise=null`；freightDesc、shipCity 为 null |

### CGD-API-023 can-purchase 匿名

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-023 |
| **测试模块** | 商品详情内页 |
| **测试项** | can-purchase 鉴权 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §3.3 @Anonymous |
| **前置条件** | 无 Token |
| **测试步骤** | GET can-purchase |
| **预期结果** | HTTP 200 |

### CGD-API-024 detailContent 原样返回

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-API-024 |
| **测试模块** | 商品详情内页 |
| **测试项** | 图文详情 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 GD9 |
| **前置条件** | G-OK detail_content 含 HTML |
| **测试步骤** | GET 详情 |
| **预期结果** | `detailContent` 与库表一致 |

---

## 三、界面测试

### CGD-UI-001 首页热销进详情

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-001 |
| **测试模块** | 商品详情内页 |
| **测试项** | 入口 HM12 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §3.2 首页入口 |
| **前置条件** | 首页热销区有 G-OK |
| **测试步骤** | 打开首页 → 点击热销商品卡片 |
| **预期结果** | 进入详情页；URL 含 `goodsId`；顶栏可见商品名或返回 |

### CGD-UI-002 分类列表进详情

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-002 |
| **测试模块** | 商品详情内页 |
| **测试项** | 入口 GC11 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §3.2 分类入口 |
| **前置条件** | 分类 B 页有 G-OK |
| **测试步骤** | 分类列表 → 点击商品 |
| **预期结果** | 进入同一详情页结构 |

### CGD-UI-003 轮播主图+副图滑动

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-003 |
| **测试模块** | 商品详情内页 |
| **测试项** | 图片轮播 GD4 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.1 多图轮播 |
| **前置条件** | G-OK pics≥3 |
| **测试步骤** | 打开详情 → 左滑轮播 2 次 |
| **预期结果** | 首张为主图；可切换至副图；指示点/计数与 pics 长度一致 |

### CGD-UI-004 仅主图不展示多余指示

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-004 |
| **测试模块** | 商品详情内页 |
| **测试项** | 单图轮播 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.1 G-SUB0 |
| **前置条件** | G-SUB0 |
| **测试步骤** | 打开详情 |
| **预期结果** | 仅 1 张图；无滑动或指示点隐藏 |

### CGD-UI-005 基本信息区展示

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-005 |
| **测试模块** | 商品详情内页 |
| **测试项** | 售价/名称/库存/销量 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 GD5、§4.2 |
| **前置条件** | G-OK |
| **测试步骤** | 打开详情 |
| **预期结果** | 展示名称、售价 ¥88、库存 100、销量 50（与接口一致） |

### CGD-UI-006 简述无数据隐藏

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-006 |
| **测试模块** | 商品详情内页 |
| **测试项** | goodsBrief |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.2 v1 |
| **前置条件** | 接口 `goodsBrief=null` |
| **测试步骤** | 打开详情 |
| **预期结果** | **不展示** 简述行 |

### CGD-UI-007 商品参数区展示

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-007 |
| **测试模块** | 商品详情内页 |
| **测试项** | 参数区 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 GD6、§4.3 |
| **前置条件** | G-OK 有 attributes |
| **测试步骤** | 滚动至参数区 |
| **预期结果** | 键值对展示「产地」等属性项 |

### CGD-UI-008 无参数隐藏或空态

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-008 |
| **测试模块** | 商品详情内页 |
| **测试项** | 参数空态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.3 |
| **前置条件** | 商品无 biz_goods_attr |
| **测试步骤** | 打开详情 |
| **预期结果** | 参数区 **隐藏** 或展示「暂无参数」 |

### CGD-UI-009 服务说明区展示

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-009 |
| **测试模块** | 商品详情内页 |
| **测试项** | 服务说明 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 GD7、§4.5 |
| **前置条件** | G-OK 有服务快照 |
| **测试步骤** | 查看服务说明区 |
| **预期结果** | 展示已勾选服务名称/图标；与物流区 **分区** |

### CGD-UI-010 物流承诺发货文案

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-010 |
| **测试模块** | 商品详情内页 |
| **测试项** | 物流说明 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.4 |
| **前置条件** | 含 SV-SHIP 快照 |
| **测试步骤** | 查看物流区 |
| **预期结果** | 展示「24小时发货」类文案 |

### CGD-UI-011 售后说明含客服电话

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-011 |
| **测试模块** | 商品详情内页 |
| **测试项** | 售后服务 GD8 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.6 |
| **前置条件** | S101 shop_phone 已配置 |
| **测试步骤** | 查看售后说明区 |
| **预期结果** | 模板文案中 **【客服电话】** 替换为店铺电话 |

### CGD-UI-012 图文详情渲染

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-012 |
| **测试模块** | 商品详情内页 |
| **测试项** | detailContent |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 GD9、§4.7 |
| **前置条件** | G-OK 含图文块 |
| **测试步骤** | 滚动至详情底部 |
| **预期结果** | 图片/文字块按发品顺序纵向展示 |

### CGD-UI-013 店铺入口区展示

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-013 |
| **测试模块** | 商品详情内页 |
| **测试项** | 进店区 GD13 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §9.1 |
| **前置条件** | G-OK @S101 |
| **测试步骤** | 查看店铺入口区 |
| **预期结果** | 展示店头像、店名；评分/粉丝 v1 可隐藏 |

### CGD-UI-014 点击进店跳转

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-014 |
| **测试模块** | 商品详情内页 |
| **测试项** | 进店交互 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §9.2 |
| **前置条件** | 店铺主页路由已配置 |
| **测试步骤** | 点击「进店」或店铺区域 |
| **预期结果** | 跳转店铺主页；URL 含 shopId；未建设则记 **跳过** |

### CGD-UI-015 评价区暂无评价

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-015 |
| **测试模块** | 商品详情内页 |
| **测试项** | 评价空态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 GD14、§10.1 |
| **前置条件** | reviews 接口 total=0 |
| **测试步骤** | 打开详情 |
| **预期结果** | 评价区展示「暂无评价」 |

### CGD-UI-016 评价摘要两条

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-016 |
| **测试模块** | 商品详情内页 |
| **测试项** | 评价摘要 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §10.1 |
| **前置条件** | 评价表落地且 ≥3 条；未落地 **跳过** |
| **测试步骤** | 打开详情 |
| **预期结果** | 评价区 **最多 2 条**；含头像、内容 |

### CGD-UI-017 全部评价页

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-017 |
| **测试模块** | 商品详情内页 |
| **测试项** | 全部评价 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §10.2 |
| **前置条件** | 有评价数据 |
| **测试步骤** | 点击「全部评价」 |
| **预期结果** | 进入全部评价页；列表 **最新倒序**；支持分页/load more |

### CGD-UI-018 可购时底栏可点

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-018 |
| **测试模块** | 商品详情内页 |
| **测试项** | 底栏可购态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §6.1 G-OK |
| **前置条件** | G-OK；M1 已登录；purchase.allowed=true |
| **测试步骤** | 打开详情 |
| **预期结果** | 「加入购物车」「立即购买」**可点击**（非置灰） |

### CGD-UI-019 下架商品置灰与提示

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-019 |
| **测试模块** | 商品详情内页 |
| **测试项** | 下架 GD10 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §6.2 |
| **前置条件** | G-OFF 直链进入 |
| **测试步骤** | 打开详情 |
| **预期结果** | 显著提示「商品已下架」；底栏 **置灰/隐藏**；点击购买有拦截提示 |

### CGD-UI-020 停业店可浏览禁购

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-020 |
| **测试模块** | 商品详情内页 |
| **测试项** | 停业 GD16 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §11、§6.1 |
| **前置条件** | 出售中商品 @S102 停业 |
| **测试步骤** | 打开详情 → 点击加购 |
| **预期结果** | 页面信息 **正常展示**；加购提示「店铺已停业」类文案 |

### CGD-UI-021 零库存可浏览禁购

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-021 |
| **测试模块** | 商品详情内页 |
| **测试项** | 缺货 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §6.3、§11 #4 |
| **前置条件** | G-ZERO |
| **测试步骤** | 打开详情 → 点击立即购买 |
| **预期结果** | 展示库存 0；按钮置灰或点击提示「库存不足」 |

### CGD-UI-022 访客加购引导登录

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-022 |
| **测试模块** | 商品详情内页 |
| **测试项** | 访客 GD3 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §7.1、§2.6 |
| **前置条件** | 访客；G-OK 可购 |
| **测试步骤** | 点击「加入购物车」 |
| **预期结果** | **不** 写购物车；弹出登录/注册引导 |

### CGD-UI-023 禁用会员禁购

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-023 |
| **测试模块** | 商品详情内页 |
| **测试项** | M2 禁用 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §2.6、§11 |
| **前置条件** | M2 登录；G-OK |
| **测试步骤** | 点击加购/购买 |
| **预期结果** | 拦截并提示账号不可用；**仍可浏览** 详情 |

### CGD-UI-024 购买前重调 can-purchase

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-024 |
| **测试模块** | 商品详情内页 |
| **测试项** | 二次校验 GD15 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §11 校验时机 |
| **前置条件** | 详情 purchase.allowed=true；Mock 点击时 can-purchase 改 false |
| **测试步骤** | 点击「立即购买」 |
| **预期结果** | 前端再调 `/can-purchase`；失败则展示 reason **不** 进确认订单 |

### CGD-UI-025 商品不存在空态

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-025 |
| **测试模块** | 商品详情内页 |
| **测试项** | 不存在 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §12 |
| **前置条件** | 访问无效 goodsId |
| **测试步骤** | 直链打开详情 |
| **预期结果** | 提示「商品不存在」；提供返回；**不** 白屏 |

### CGD-UI-026 详情加载失败可返回

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-026 |
| **测试模块** | 商品详情内页 |
| **测试项** | 接口失败 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §12 |
| **前置条件** | Mock `/api/goods/{id}` 500 |
| **测试步骤** | 打开详情 |
| **预期结果** | 错误/空态 + 返回按钮；**不** 整页白屏 |

### CGD-UI-027 评价区失败不影响其它区块

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-027 |
| **测试模块** | 商品详情内页 |
| **测试项** | 评价独立空态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §12 |
| **前置条件** | 详情 OK；Mock `/reviews` 失败 |
| **测试步骤** | 打开详情 |
| **预期结果** | 轮播/基本信息 **正常**；评价区单独错误/空态 |

### CGD-UI-028 图片加载失败占位

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-028 |
| **测试模块** | 商品详情内页 |
| **测试项** | 图片异常 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §12 |
| **前置条件** | pics URL 404 |
| **测试步骤** | 打开详情 |
| **预期结果** | 轮播/详情图展示 **占位图**；页面不崩溃 |

### CGD-UI-029 访客与会员均可浏览

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-029 |
| **测试模块** | 商品详情内页 |
| **测试项** | GD2 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §1.4 |
| **前置条件** | ① 访客 ② M1 登录 |
| **测试步骤** | 分别打开 G-OK 详情 |
| **预期结果** | 均可看到轮播、基本信息、参数等只读区 |

### CGD-UI-030 返回保留来源页

| 要素 | 内容 |
|------|------|
| **用例编号** | CGD-UI-030 |
| **测试模块** | 商品详情内页 |
| **测试项** | 返回导航 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §3.3 |
| **前置条件** | 从分类 B 页滚动至中部进详情 |
| **测试步骤** | 点击返回 |
| **预期结果** | 回到分类 B 页；滚动位置 **大致保留**（以前台 UX 为准） |

---

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

| 规则 | UT | API | UI |
|------|:--:|:---:|:--:|
| GD1 只读无编辑 | — | API-014 | — |
| GD2 访客/会员可浏览 | — | API-001 | UI-029 |
| GD3 加购须登录 | — | — | UI-022/023 |
| GD4 主图+副图轮播 | UT-001～004 | API-003/004 | UI-003/004 |
| GD5 基本信息 | UT-016 | API-002 | UI-005/006 |
| GD6 参数快照 | UT-007 | API-016 | UI-007/008 |
| GD7 物流/服务分区 | UT-008/009 | API-015/022 | UI-009/010 |
| GD8 售后电话 | UT-012/013 | API-002 | UI-011 |
| GD9 图文详情 | — | API-024 | UI-012 |
| GD10 下架不可购 | UT-011 | API-008/012 | UI-019 |
| GD11 规格/缺货（v1 单规格） | — | API-009 | UI-021 |
| GD12 立即购买跳转 | — | — | UI-018/024 |
| GD13 进店 | UT-012 | API-017 | UI-013/014 |
| GD14 评价摘要 | UT-015 | API-018/019 | UI-015～017 |
| GD15 四条件/浏览≠可购 | UT-010/011/014 | API-007～013/020 | UI-018～021/024 |
| GD16 停业可浏览禁购 | — | API-010 | UI-020 |
| GD17 服务快照不追溯 | UT-008 | API-015 | UI-009 |
| §12 异常空态 | UT-005 | API-005/006 | UI-025～028 |

---

## 五、自动化映射

| 用例 | 建议代码位置 |
|------|--------------|
| CGD-UT-001～003 | `GoodsPicSupportTest.java` |
| CGD-UT-004～016 | `GoodsAppServiceImplTest.java` |
| CGD-API-001～024 | `GoodsAppControllerTest.java` + Apifox 集合 |
| CGD-UI-001～030 | `e2e/consumer/goods-detail.spec.ts`（Playwright `channel: 'chrome'`） |

**Playwright 示例：**

```typescript
// e2e/consumer/goods-detail.spec.ts 片段
import { test, expect } from '@playwright/test';

test('CGD-UI-003 轮播主图+副图', async ({ page }) => {
  await page.goto(`/goods/1`);
  const swiper = page.locator('[data-testid="goods-swiper"]');
  await expect(swiper.locator('img').first()).toHaveAttribute('src', /\/m\.jpg/);
  await swiper.swipe({ direction: 'left' });
});
```

**配置：**

```typescript
// playwright.config.ts 片段
use: { channel: 'chrome', baseURL: process.env.C_APP_URL }
```

---

## 六、用例统计

| 类型 | 数量 | 已实现自动化 |
|------|------|--------------|
| 单元测试 CGD-UT | 16 | UT-001～004（`GoodsPicSupportTest`）；UT-004～015 部分（`GoodsAppServiceImplTest`） |
| 接口测试 CGD-API | 24 | API-001/003/005/007/018/023（`GoodsAppControllerTest`） |
| 界面测试 CGD-UI | 30 | 待前端落地后 Playwright |
| **合计** | **70** | 后端 **16** 例已覆盖 |

---

## 七、修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版；70 用例；覆盖 GD1～GD17、三读接口、详情页各区块与四条件；对齐 v1.4 后端实现 |

---

*文档版本：v1.0 · 关联《商品详情内页功能需求.md》v1.1、《商品详情内页技术方案.md》v1.4*
