--- sidebar_position: 10 title: 测试要求 sidebar_label: 测试要求 description: Plaza 的自动化测试策略、权限矩阵、E2E 场景、性能基准和验收标准 --- # 测试要求 ## 测试策略 ```text E2E 测试(Playwright) 覆盖核心用户流程 │ 集成测试(真实 DB + Redis) 覆盖 API 契约和权限矩阵 │ 单元测试 覆盖热度算法、格式化函数、状态机 ``` 所有测试必须使用真实数据库连接,不允许 Mock DB(与 MindSpace 测试策略一致)。 --- ## 权限矩阵 每个 API 必须测试以下角色的访问结果: | 接口 | 游客 | 普通用户(非作者) | 作者本人 | 运营人员 | | --- | --- | --- | --- | --- | | GET /feed | 200 ✅ | 200 ✅ | 200 ✅ | 200 ✅ | | GET /posts/:id | 200 ✅ | 200 ✅ | 200 ✅ | 200 ✅ | | POST /posts(发布到广场) | 401 ❌ | 200 ✅ | 200 ✅ | 200 ✅ | | DELETE /posts/:id | 401 ❌ | 403 ❌ | 200 ✅ | - | | POST /posts/:id/reactions | 401 ❌ | 200 ✅ | 200 ✅ | 200 ✅ | | POST /posts/:id/comments | 401 ❌ | 200 ✅ | 200 ✅ | 200 ✅ | | DELETE /comments/:id(他人评论) | 401 ❌ | 403 ❌ | 403 ❌ | - | | DELETE /comments/:id(自己评论) | 401 ❌ | 200 ✅ | 200 ✅ | - | | POST /users/:slug/follow(关注自己) | 401 ❌ | - | 422 ❌ | - | | POST /ops/review/posts/:id | 401 ❌ | 403 ❌ | 403 ❌ | 200 ✅ | | GET /ops/analytics | 401 ❌ | 403 ❌ | 403 ❌ | 200 ✅ | --- ## 必测 API 场景 ### 发布到广场 ``` ✅ 正常发布:publication online → 创建 plaza_posts status=pending_review ✅ publication 不是 online 状态 → 422 PUBLICATION_NOT_ONLINE ✅ 同一 publication 重复 POST → 409 ALREADY_PUBLISHED ✅ 同一 publication 更新元数据 → PATCH /posts/:id 成功 ✅ publication 归属其他用户 → 403 ✅ 用户被封禁发帖权限 → 403 POST_PERMISSION_DENIED ✅ 下线 publication 后,对应 plaza_posts status 自动变为 hidden ``` ### Feed 接口 ``` ✅ 默认返回 hot 排序 ✅ sort=new 返回按 published_at 倒序 ✅ 按 category slug 筛选只返回该分类帖子 ✅ 游客请求 viewer_reacted 字段为 null ✅ 登录用户请求 viewer_reacted 正确反映点赞/收藏状态 ✅ status=pending_review 和 hidden 的帖子不出现在 feed 中 ✅ 游标翻页不重复、不遗漏 ``` ### 点赞 ``` ✅ 点赞成功 → plaza_posts.like_count +1(经 Redis → MySQL 同步后) ✅ 重复点赞 → 幂等,返回 200,计数不重复增加 ✅ 取消点赞 → 计数 -1,不出现负数 ✅ 取消未点赞的帖子 → 404 或 204(幂等) ``` ### 评论 ``` ✅ 发表一级评论 → comment_count +1 ✅ 发表二级回复 → 父评论 reply_count +1 ✅ 三级嵌套(parent_id 指向二级评论)→ 422 REPLY_DEPTH_EXCEEDED ✅ 对关闭评论的帖子发评论 → 422 COMMENT_DISABLED ✅ 评论超过 500 字符 → 422 ✅ 软删除评论 → content 为空,status=deleted,reply_count 保留 ✅ 帖子删除(status=hidden)后,已有评论不暴露给公开接口 ``` ### 关注 ``` ✅ 关注他人 → follower_count / following_count 各 +1(事务内) ✅ 取消关注 → -1 ✅ 关注自己 → 422 SELF_FOLLOW ✅ 重复关注 → 幂等返回 200,计数不重复 ``` ### 运营审核 ``` ✅ 审核通过 → status=published,写入 ops_audit_log ✅ 拒绝 → status=rejected,必须有 reason,发送通知 ✅ 隐藏 → status=hidden,帖子从 feed 消失 ✅ 普通用户调用审核接口 → 403 OPS_PERMISSION_DENIED ✅ 审核日志不可修改(不提供 UPDATE/DELETE 接口) ``` --- ## 热度算法测试 ``` ✅ 新帖 hot_score = 0(无互动,发布即刻) ✅ 相同互动量、发布时间越早 hot_score 越低(时间衰减验证) ✅ 修改 plaza_algorithm_config 权重后,下次计算周期生效 ✅ hot_score 不出现负数 ✅ 大量帖子(1000 条)批量计算完成时间 < 5 秒 ``` --- ## 数据一致性测试 ``` ✅ 配额预留和确认在同一事务(从 MindSpace 继承,无需新测) ✅ 关注关系表 UNIQUE 约束:并发关注不产生重复行 ✅ 点赞 UNIQUE 约束:并发点赞不超过 1 条记录 ✅ 发布到广场 UNIQUE(publication_id):并发请求不产生重复帖子 ✅ publication 下线事务:plaza_posts 同步隐藏在同一事务内完成 ``` --- ## E2E 场景(Playwright) ### 场景 1:完整发布流程 ``` 1. 用户 A 登录 MindSpace 2. 发布一个 MindSpace 页面(publication online) 3. 点击「发布到广场」 4. 选择分类「职场报告」,输入标签「复盘」 5. 确认发布 6. 验证:/plaza 首页最新列表出现该帖子 7. 验证:curl /plaza/p/{id} 的 HTML 包含帖子标题(SSR 验证) ``` ### 场景 2:互动完整流程 ``` 1. 游客打开帖子详情 /plaza/p/{id} 2. 点击点赞 → 弹出登录引导浮层 3. 游客登录 4. 登录后自动回到帖子详情,点赞成功 5. 刷新页面,点赞状态保持(viewer_reacted.liked = true) 6. 点击取消点赞,状态恢复 ``` ### 场景 3:评论和回复 ``` 1. 用户 A 在帖子下发表评论「很好!」 2. 用户 B 登录后回复「+1」 3. 验证:评论区展示 A 的评论,B 的回复在其下方 4. A 删除自己的评论 5. 验证:评论显示「该评论已删除」,B 的回复仍然可见 6. 尝试三级嵌套 → 前端禁用回复按钮 ``` ### 场景 4:运营审核 ``` 1. 用户发布帖子到广场,status=pending_review 2. 游客访问 /plaza,该帖子不可见 3. 运营人员登录 /ops/review 4. 点击「通过」审核 5. 验证:帖子在广场首页可见 6. 验证:ops_audit_log 有一条记录 ``` ### 场景 5:越权访问 ``` 1. 用户 A 发布帖子到广场 2. 用户 B 尝试 DELETE /api/plaza/v1/posts/{A的帖子id} 3. 验证:返回 403 4. 用户 B 尝试发布用户 A 的 publication → 返回 403 5. 游客尝试发表评论 → 返回 401 ``` ### 场景 6:SEO 验证 ``` 1. 发布一个帖子到广场 2. curl -A "Googlebot" https://go.tkmind.cn/plaza/p/{id} 3. 验证响应 HTML 中包含: - 标签含帖子标题 - <meta property="og:title"> - <meta property="og:image"> - <meta property="og:description"> - <script type="application/ld+json"> Article schema 4. curl https://go.tkmind.cn/sitemap.xml 5. 验证帖子 URL 出现在 sitemap 中 ``` ### 场景 7:publication 下线联动 ``` 1. 帖子在广场正常展示(status=published) 2. 作者在 MindSpace 将 publication 下线 3. 验证:plaza_posts.status 自动变为 hidden 4. 验证:/plaza feed 中该帖子消失 5. 验证:直接访问 /plaza/p/{id} 返回 404 ``` --- ## 性能基准 在测试环境(单机 2C4G)满足: | 接口 | p50 | p99 | 说明 | | --- | --- | --- | --- | | GET /feed(第一页,冷缓存) | < 200ms | < 500ms | MySQL 查询 | | GET /feed(命中 Redis 缓存) | < 30ms | < 100ms | 缓存读取 | | GET /posts/:id | < 100ms | < 300ms | | | POST /posts/reactions | < 100ms | < 300ms | Redis INCR | | POST /posts/comments | < 150ms | < 400ms | MySQL 写入 | | 热度计算(1000 帖子) | < 5s | - | 定时任务 | | Next.js SSR(帖子详情) | < 300ms | < 800ms | 含 API 调用 | --- ## 前端测试 ```bash # Plaza Next.js cd ui/plaza pnpm test # Jest 单元测试 pnpm e2e # Playwright E2E # 测试文件位置 ui/plaza/__tests__/ api.test.ts # API 封装函数测试 format.test.ts # 数字格式化测试 algorithm.test.ts # hot_score 公式验证(前端展示用) ui/plaza/e2e/ publish-flow.spec.ts # 发布完整流程 interaction.spec.ts # 互动场景 seo.spec.ts # SSR 和 meta 验证 ``` --- ## CI 检查清单 每个 PR 合并前 CI 必须通过: ``` □ pnpm build(ui/plaza、ui/ops)— h5-plaza job □ 后端测试:node --test ui/h5/plaza-*.test.mjs(含 MySQL 集成,CI 提供 MySQL 8 服务) □ just check-h5-openapi-schema(Plaza/Ops 路由已纳入 ui/h5/openapi.json) □ E2E:core 场景(场景 1-4)— 待 Playwright 落地 □ 新的 SQL 迁移文件可在空库执行(initSchema + plaza-integration 覆盖) □ 新的 SQL 迁移文件可在含现有数据的库执行(db.mjs ALTER 幂等) ``` 本地运行集成测试: ```bash # 需 MySQL(或 CI 同款环境变量) export MYSQL_HOST=127.0.0.1 MYSQL_USER=root MYSQL_PASSWORD=... MYSQL_DATABASE=... cd ui/h5 && node --test plaza-integration.test.mjs ```