Files
patbond-doc/docs/development/iterations/iteration-3/30-release-e2e-regression.md
T
lixi efdfe59a41
CI / docs-build (push) Successful in 1m0s
docs: v0.3.0 发布记录与发布前 E2E 回归门禁报告
- 新建常设「发布记录」页(挂开发文档导航),首条 v0.3.0 M3 社区
- 30 号报告:M2 11/11 + M3 14/14 同环境各连跑 3 轮零 flake、契约偏差 0;
  附契约向后兼容结构化比对(removed/changed 均 NONE)与共享代码面回归分析
- 记录首次发布的一次性操作:api main 孤儿历史重建(方案 A)、flutter 快进推法改进

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 15:46:57 +08:00

314 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 30 首次发布门禁:M2+M3 双份 E2E 回归(checklist 第 2 步)
- 执行人:QAExplore / 回归执行)
- 日期:2026-09-10
- 依据:iteration-3/08 号《Git 工作流规划》**§3.3 发布 checklist 第 2 步**——
「compose 全栈起,跑 M2+M3 两份 E2E 烟囱脚本,全场景 PASS,证据入波次报告」
- 环境:`patbond-api`docker compose 六容器)+ `patbond-flutter`dart 脚本直连)
- 测试脚本:`patbond-flutter/test_e2e_m2_manual.dart`M2 收官版,11 场景)
`patbond-flutter/test_e2e_m3_manual.dart`M3 收官版,14 场景),均取 `flutter@0e87413`
- 冻结契约:`patbond-doc/docs/api/openapi.yaml` **v1.3.0**`doc@f848476`
- 参照模式:iteration-2/28 与 iteration-3/28 号收官报告(格式与取证标准沿用)
> **三仓代码零改动**:本次只起 compose、跑两份既有脚本、做只读 psql/git 取证。
> 未修改、未 commit、未 push 任何代码仓;未改 `mkdocs.yml`。
---
## 0. 执行概要
### 门禁结论
**PASS**——发布 checklist 第 2 步满足。
| 项目 | 结果 |
| --- | --- |
| M2 场景通过数 | **11 / 11**44 条断言全绿,`exit 0` |
| M3 场景通过数 | **14 / 14**88 条断言全绿,`exit 0` |
| 契约偏差数 | **0 个**(对照冻结契约 v1.3.0 |
| 失败项 | **0 项** |
| 稳定性 | 同一 compose 环境内 **两份脚本各连跑 3 轮**6 次全通过、零 flake |
| 两域共存 | M2→M3→M2→M3 交叉执行,pet_health 与 community 数据同库共存,互不干扰 |
| 容器异常 | 六容器 RestartCount 全 0;四个应用容器日志 `ERROR`/`Exception` 计数全 0 |
### 本单目标(与 M3 收官报告的区别)
M3 版脚本已在 M3 收官(iteration-3/28)跑过 14/14。**本单的增量价值在于复跑 M2 版**:
M3 期间 pets 域未做功能变更,但两域共享 `patbond-common`、契约快照文件、CI 流水线与同一
数据库实例——需要实测确认 M3 交付没有回归 M2 的宠物健康档案域。为把「共存」也一并证实,
两份脚本在**同一次** compose 生命周期内交叉执行。
### 脱敏声明
token 一律截断至前 20 字符 + `<REDACTED>`(脚本内置 `redact()`);MinIO 预签名 URL 的
查询串替换为 `<SIGNATURE_REDACTED>`;幂等键显示为 `<KEY-1>`;密码与 `.env` 内容不出现在
任何输出。
---
## 1. 环境记录
### 1.1 构建与启动(patbond-api 代码零改动)
```bash
cd <你的工作区>/patbond-api
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -DskipTests package
# BUILD SUCCESS —— Total time: 4.263 s(增量编译,五模块 reactor 全 SUCCESS
# 产出四个 -exec.jarauth 36MB / community 51MB / pet 25MB / user 41MB
docker compose up -d --build
# Container patbond-postgres-1 Healthy
# Container patbond-minio-1 Healthy
# Container patbond-user-1 / auth-1 / community-1 / pet-1 Started
```
### 1.2 六容器状态与镜像版本
```text
CONTAINER REPOSITORY TAG SIZE STATUS
patbond-postgres-1 postgres 18 162MB Up (healthy)
patbond-minio-1 minio/minio RELEASE.2025-04-22T22-12-26Z 64MB Up (healthy)
patbond-auth-1 patbond-auth latest(本次重建) 141MB Up :8081
patbond-user-1 patbond-user latest(本次重建) 147MB Up :8082
patbond-pet-1 patbond-pet latest(本次重建) 132MB Up :8083
patbond-community-1 patbond-community latest(本次重建) 155MB Up :8084
```
| 组件 | 版本 |
| --- | --- |
| PostgreSQL | 18.6 (Debian 18.6-1.pgdg13+2) |
| MinIO | RELEASE.2025-04-22T22-12-26Z |
| 容器内 JRE | Temurin OpenJDK 17.0.20+8 |
| 宿主 Docker | 29.7.2 / Docker Compose 5.5.1 |
| Dart SDK(跑脚本) | 3.12.2 (stable) |
### 1.3 启动耗时与就绪验证
依赖顺序符合编排:postgres/minio 先 Healthy,四应用容器随后 Started(容器创建到启动
约 3 秒),Spring Boot 自身启动耗时:
```text
Started AuthApplication in 9.752 seconds
Started UserApplication in 13.194 seconds
Started PetApplication in 9.255 seconds
Started CommunityApplication in 11.135 seconds
```
无 token 探活(预期 401 信封):
```bash
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8082/api/v1/me # 401
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8083/api/v1/pets # 401
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8084/api/v1/feed # 401
```
### 1.4 脚本执行耗时(第三轮,取权威计时)
| 脚本 | 场景数 | 耗时 | 退出码 | `✓` 断言数 | 失败标记 |
| --- | --- | --- | --- | --- | --- |
| `test_e2e_m2_manual.dart` | 11 | **1149 ms** | 0 | 44 | 0 |
| `test_e2e_m3_manual.dart` | 14 | **1662 ms** | 0 | 88 | 0 |
### 1.5 数据残留说明(非缺陷)
`pgdata` / `minio-data` 卷延续自 M3 收官那次运行(compose 只重建容器与镜像,未 `down -v`)。
两份脚本均以时间戳随机账号运行、且 M3 的全量翻页断言按「本轮新增 26 条 vs 全量 117 条」
的相对口径校验,因此**残留数据不影响判定,反而额外证明了跨轮数据共存无干扰**。
---
## 2. M2 版 E2E 逐场景结果(11/11 PASS
本轮取证账号 `e2e_m2_a_1789025850872`petId `01a08a40-1ad6-7a26-962c-a5f6cd3706a1`
| # | 场景 | 结果 | 关键断言实测 |
| --- | --- | --- | --- |
| 1 | 注册账号 A → 登录 | ✓ PASS | register 200 / login 200token `eyJhbGciOiJSUzI1NiJ9...<REDACTED>` |
| 2 | 建档(含品种)→ 列表/详情读回 | ✓ PASS | 品种目录 16 条;POST /pets **201**`myRole=owner``version=0``breedDisplayName=中华田园犬`;列表/详情四字段一致 |
| 3 | 记体重 ×2 → cursor 分页 | ✓ PASS | 8.20/8.45kg 各 201;第一页 8.45 在前 + `hasMore=true`;第二页 8.20 + `hasMore=false``nextCursor=null` |
| 4 | 疫苗登记(scheduled)→ 标记完成 | ✓ PASS | 疫苗目录 6 条;PATCH 200`version 0→1``administeredOn`/`nextDueOn` 回读一致 |
| 5 | 健康事件(整数分)→ 时间线 | ✓ PASS | `amountCents=12500` 原样回读;`createdByUserId` = token subject;时间线 1 条 |
| 6 | 提醒创建 → 标记完成 | ✓ PASS | 创建恒 `pending` + `completedAt=null`PATCH 后 `completedAt` = 客户端提交时刻 |
| 7 | `/summary?tz=Asia/Shanghai` 四项聚合 | ✓ PASS | 最新体重 8.45kg;疫苗进度 1/1;下次接种 2027-09-10`source=nextDue`);当月花费 12500 分、`month=2026-09`、tz 回显 |
| 8 | 账号 B 越权访问 A 的宠物四路(防枚举) | ✓ PASS | 详情/体重/疫苗/摘要四路全 **404 / 40401**,响应体逐字节一致 `{"code":40401,"message":"宠物不存在","data":null}`B 列表为空 |
| 9 | 账号 A 第二设备重新登录 → 全量读回 | ✓ PASS | 新会话 token 与设备 1 不同(独立 token family);宠物 1 / 体重 2 / 疫苗 1 / 事件 1 / 提醒 1 全量一致 |
| 10 | `/api/v1/events` v2 事件上报 | ✓ PASS | 4 条 → **202**`accepted=4, duplicated=0, rejected=0` |
| 11 | 乐观锁冲突明确性 | ✓ PASS | 第一次 PATCH 200`version 0→1`);同过期 version 第二次 **409 / 40902**;读回确认先写者数据保留 |
---
## 3. M3 版 E2E 逐场景结果(14/14 PASS
本轮取证账号 `e2e_m3_a_1789025861236`postId `01a08a40-422a-7d60-a915-f04d7b0061be`
(三轮运行的断言输出逐条一致,仅随机账号名与 UUID 不同;下表的字面值取自取证轮输出。)
| # | 场景 | 结果 | 关键断言实测 |
| --- | --- | --- | --- |
| 1 | 注册 A/B 两账号 → 登录 | ✓ PASS | 两账号注册 200 且为两个独立 userId(模拟两客户端) |
| 2 | 两步上传直传 MinIO`/media/uploads` → complete | ✓ PASS | 登记 201 返回 SigV4 预签名凭据、`requiredHeaders` 恒且仅 `{Content-Type}`344 字节直传 200complete 后 `uploading→ready``byteSize=344``readyAt` 已写);重复 complete 幂等 200 同一 asset |
| 3 | 草稿创建 → 发布(PATCH draft→published | ✓ PASS | 草稿 `status=draft`/`publishedAt=null`、media 挂接 `position=0` 且服务端置唯一 `isCover`;发布后 `status=published``publishedAt` 已写、`version 0→1` |
| 4 | **验收①** B 拉 `/feed` A 的帖首位可见 | ✓ PASS | `published_at DESC` 首位命中;FeedCard 必填齐备;`AuthorSummary` 不露 bio/username;裁剪生效(无 content 全文/media 整组/version |
| 5 | 预签名 GET 字节往返 + 桶私有 | ✓ PASS | 344 字节逐字节一致;去签名直访 **403** |
| 6 | **验收②** 连续 3 次 PUT like → count 恰 1 | ✓ PASS | 三次均 200 权威终态 `{liked:true,likeCount:1}`(非 409);DELETE → 0;重复 DELETE 幂等 |
| 7 | 收藏 → `/me/bookmarks` → 取消 | ✓ PASS | 列表项形态 = FeedCard,视角字段为 B;取消后不含 |
| 8 | 评论 ×2 / 作者软删 / 帖主越权删被拒 | ✓ PASS | B 删自己 200A(帖主)删 B 的 **403 / 40301**`commentCount` 同事务 +1/-1 准确 |
| 9 | 关注幂等 + follow-stats + 自关注拒绝 | ✓ PASS | 重复 PUT 幂等 200;自关注 **422 / 42204**;自取关 200 no-opA 查自己 `followedByMe` 恒 false |
| 10 | **验收③** 25 帖 → 双粒度全量翻页比对 | ✓ PASS | limit=7 → 17 页 117 条;limit=100 → 2 页 117 条;两种页大小**逐位一致**,零重复零遗漏 |
| 11 | **验收④** 软删一帖 → 出 Feed + 直接 GET 404 | ✓ PASS | 删后全量恰少 1;B 与作者 A 直接 GET 均 **404 / 40403** 且响应体逐字节一致 |
| 12 | 防枚举:草稿 vs 随机 UUID 响应体一致 | ✓ PASS | 四路全 **404 / 40403** 一致 `{"code":40403,"message":"帖子不存在","data":null}`;互动面恒为公开面;草稿对作者详情仍可见、不入公共 Feed |
| 13 | v3 社区事件上报 + 白名单/字典兜底 | ✓ PASS | 8 条 → **202** `accepted=8`;白名单外键剥离后仍 accepted;字典外 `post_impression` 整条 rejected`unknown_event_name`),批次仍 202 |
| 14 | 幂等重放:同键同 hash / 异 hash / 缺头 / 跨作者 | ✓ PASS | 同键同 hash 返回原帖不产生第二帖;异 hash **409 / 40905**;缺 `Idempotency-Key` **400 / 40000**;幂等键按作者隔离 |
---
## 4. 数据库证据(两域共存,只读 psql)
### 4.1 schema 与 Flyway 迁移
```sql
-- \dn
community | identity | media | pet_health | platform | public
-- select installed_rank, version, description, success from public.flyway_schema_history;
1 | 1 | identity media baseline | t
2 | 2 | create platform product events | t
3 | 3 | pet health baseline | t
4 | 4 | pet health dictionary seed | t
5 | 5 | community baseline | t
```
V1~V5 全 `success=t`M3 的 V5 未触碰 M2 的 V3/V4(不可变迁移纪律保持)。
### 4.2 两域数据行数(六次脚本运行累计,含既有残留)
```sql
pet_health.pets | 8 community.posts | 131
pet_weight_records | 9 community.comments | 10
pet_vaccinations | 7 community.post_likes | 3
health_events | 6 community.user_follows | 3
care_reminders | 6 media.assets | 17
pet_owners | 8 platform.product_events | 61
```
两域各 8 张表并存于同一实例,交叉执行无外键/唯一键冲突、无死锁。
### 4.3 埋点落库核对
M2 本轮会话(`session_id=1e1a8388-…`,共 4 条 = 上报数):
```sql
health_record_create_succeeded | android | 1.0.0+e2e | 2
page_viewed | android | 1.0.0+e2e | 1
pet_create_succeeded | android | 1.0.0+e2e | 1
```
M3 本轮会话(`session_id=a28da209-…`,8 白名单事件 + 1 条隐私兜底事件):
```sql
comment_create_succeeded | 1 post_liked | 2
feed_viewed | 1 post_media_upload_succeeded | 1
page_viewed | 1 post_publish_succeeded | 1
post_favorited | 1 user_followed | 1
```
隐私红线兜底与字典守门实测:
```sql
-- 字典外事件未落库
select count(*) from platform.product_events where event_name='post_impression'; -- 0
-- 混入白名单外 postId 的 post_liked:落库 props 已剥离
select props from platform.product_events where event_id='bc13cbbd-…';
{"source": "feed"} -- 无 postId
```
---
## 5. 契约偏差声明与共享面回归分析
### 5.1 契约偏差数:**0 个**
两份脚本共 132 条断言覆盖 HTTP 状态码、业务错误码、信封结构、字段形态、分页语义、
幂等语义、乐观锁语义与防枚举一致性,与冻结契约 **v1.3.0** 全部一致,无需修复项。
### 5.2 M2 契约面在 v1.3.0 中零漂移(结构化取证)
M3 期间四模块契约快照从 `openapi-v1.2.0.yaml` 换名到 `openapi-v1.3.0.yaml`
是本次回归最需要盯的共享面。对 `doc@511617b`v1.2.0 冻结)与 `doc@f848476`v1.3.0 冻结)
做结构化比对(YAML 解析后按键排序序列化对比,非文本 diff):
```text
info.version: 1.2.0 -> 1.3.0
paths18 -> 31+13,全部为 community/media 新增;removed: NONE
v1.2.0 的 18 条 path 定义 —— CHANGED/MISSING: NONE(全部完全一致)
schemas45 -> 72+27
removed schemas: NONE
changed schemas: NONE
```
**v1.3.0 相对 v1.2.0 严格增量**:M2 的 18 条路径与 45 个 schema 一字未改,
M2 版脚本对照 v1.3.0 运行等价于对照 v1.2.0 运行。
### 5.3 共享代码面回归分析(`64c9b72..8089c06`,即 M3 全区间)
```text
patbond-pet/src/main/ → 0 个文件变更(pets 域生产代码 M3 期间未被触碰)
patbond-pet/ 变更仅在测试侧:ContractConformanceTest / ContractValidator /
OpenApiContract + 快照文件改名(v1.2.0 → v1.3.0
patbond-common/ → 仅 ErrorCode.java +9 行,纯新增枚举常量:
POST_ACCESS_DENIED(40301) / POST_NOT_FOUND(40403) / IDEMPOTENCY_PAYLOAD_MISMATCH(40905)
MEDIA_NOT_FOUND(40405) / COMMENT_NOT_FOUND(40404) / TARGET_USER_NOT_FOUND(40406)
MEDIA_NOT_READY(42203) / FOLLOW_RULE_VIOLATION(42204) / MEDIA_UPLOAD_STATE_INVALID(42205)
—— 无任何 `-` 行,M2 错误码(40401/40902/40000…)定义未被修改或删除
```
三条共享面(契约快照、`patbond-common`、同一数据库实例)均验证为纯增量,
与实测的 11/11 结果互为印证:**M3 交付未破坏 M2 功能,无回归**。
---
## 6. 失败项
**无。** 六次脚本运行(M2 ×3、M3 ×3)全部 `exit 0`,输出中 `✗`/`FAIL` 计数为 0
无需区分「脚本环境问题」与「真实回归」。
---
## 7. 三仓状态(零改动核验)
```bash
cd <你的工作区>/patbond-api && git status --short # 空
cd <你的工作区>/patbond-flutter && git status --short # 空
cd <你的工作区>/patbond-doc && git status --short # 仅本报告(未 commit
```
| 仓 | HEAD |
| --- | --- |
| patbond-api | `8089c06` feat: 事件字典 v3 白名单扩充…(T3-20) |
| patbond-flutter | `0e87413` test: M3 E2E 烟囱脚本(T3-21 收官) |
| patbond-doc | `3ebe562` docs: M3 收官——E2E 报告与收官总结入档,验收 PASSED |
`patbond-api/*/target/` 下的重建产物为 gitignore 覆盖项,不产生工作区脏状态。)
---
## 8. 环境清理
```bash
cd <你的工作区>/patbond-api && docker compose down
```
数据卷 `pgdata` / `minio-data` 按既往做法保留(未加 `-v`),便于下次复跑与事后取证。
---
## 9. 遗留与后续
1. **真机挂起项不变**:M2 的两项(Android 事件落库真机观察、SessionTracker 30min 手测)与
M3 的真机项仍按方案 A 挂起,本次脚本直连不替代真机验证——见 `docs/development/device-verification.md`
2. **发布 checklist 后续步骤**:本报告只闭合 §3.3 第 2 步;第 3 步(命名统一 + api `main` 重建)、
第 4~8 步(合并/打标/分支保护/发布说明/hotfix 纪律)待拍板后执行。
3. **E2E 自动化仍为手动模式**iteration-3/08 §5 的结论(保持「波次收尾手动跑、证据入档」,
自动化做成 `workflow_dispatch` 手动工作流,低优先)未变;本次两份脚本 3 秒内跑完,
手动成本极低,暂无自动化紧迫性。