topfans/docs/superpowers/plans/2026-06-17-plan-d-notification-test.md
2026-06-22 17:19:48 +08:00

238 lines
10 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.

# Plan D: 通知模板 + 单元测试 + 集成测试
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**父计划:** `docs/superpowers/plans/2026-06-17-moderation-report-feedback-system.md`
**Goal:** 注册 6 个通知模板 + 单元测试 + 集成测试 + 监控指标
**Architecture:**
- 6 个模板注册到 notificationService替换 v1.5 stub 为真实现)
- Go 单元测试覆盖状态机 + claimed_* 一致性 + Lua + 并发
- FastAPI 单元测试覆盖 50008/50009/50014 区分
- 集成测试testcontainers端到端场景
- Prometheus 13+ 业务指标
**Tech Stack:** Go test / testify / testcontainers-go / pytest / pytest-asyncio / prometheus/client_golang
**仓库:** 双仓并行
- `/Users/liulujian/Documents/code/TopFansByGithub`Go service + Gateway + 集成测试)
- `/Users/liulujian/Documents/code/TopFans-activity-admin`FastAPI 测试)
---
## 阶段 D.1:通知模板注册
### Task D.1.1: 在 notificationService 注册 6 个模板
**Files:**
- Modify: `backend/services/notificationService/model/notification.go``repository/notification_repository.go`(按现有模板管理方式)
**6 个模板**spec §8
| 事件 | 模板名 | 接收方 | 字段 |
|------|--------|--------|------|
| 举报达到阈值触发自动隐藏 | `report_auto_hidden_notice` | 被举报方 | reporter_nickname, target_type, target_id, action, reason, related_url |
| 举报状态变更为 resolved/dismissed | `report_resolved` | 举报人 | 同上 |
| 举报成立 + takedown | `report_takedown_notice` | 被举报方 | 同上 |
| 举报成立 + ban | `report_ban_notice` | 被举报用户 | 同上 + 短信 |
| 举报成立 + warn | `report_warn_notice` | 被举报用户 | 同上 |
| 反馈被回复 | `feedback_replied` | 反馈人 | 同上 |
```sql
INSERT INTO notification_templates (code, name, channel, subject, body_template, enabled) VALUES
('report_auto_hidden_notice', '举报自动隐藏通知', 'site_msg',
'您的内容被多人举报已自动隐藏',
'您发布的{target_type}ID: {target_id})已被系统自动隐藏。如有异议请联系客服。',
TRUE),
('report_resolved', '举报处理结果', 'site_msg+push',
'您的举报已处理',
'您的举报已处理:{action}。{reason}',
TRUE),
('report_takedown_notice', '内容下架通知', 'site_msg',
'您的内容已被下架',
'您发布的{target_type}ID: {target_id})因{reason}已被下架。',
TRUE),
('report_ban_notice', '账号封禁通知', 'site_msg+sms',
'您的账号已被封禁',
'您的账号因{reason}已被封禁,如有异议请联系客服。',
TRUE),
('report_warn_notice', '账号警告通知', 'site_msg',
'您的账号已被警告',
'您的账号因{reason}已被警告,请遵守社区规范。',
TRUE),
('feedback_replied', '反馈已回复', 'site_msg+push',
'您的反馈已回复',
'您的反馈"{title}"已被回复:{reply_content}',
TRUE);
```
### Task D.1.2: 替换 moderationService notification_client.go stub
**Files:**
- Modify: `backend/services/moderationService/client/notification_client.go`
将 v1.5 stub 替换为真实 Dubbo 调用 `notificationService.SendNotification`
```go
func (c *NotificationClient) SendReportAutoHidden(ctx context.Context, userID int64, targetType string, targetID int64) error {
_, err := c.cli.SendNotification(ctx, &pbNotification.SendNotificationRequest{
UserID: userID,
Template: "report_auto_hidden_notice",
Vars: map[string]string{
"target_type": targetType,
"target_id": strconv.FormatInt(targetID, 10),
},
})
if err != nil {
logger.Sugar.Warnw("send notification failed", "template", "report_auto_hidden_notice", "err", err)
// 通知失败不影响主流程spec §11.2 "仅日志,不影响主流程"
}
return err
}
// 同模式SendReportResolved / SendReportTakedownNotice / SendReportBanNotice / SendReportWarnNotice / SendFeedbackReplied
```
---
## 阶段 D.2Go 单元测试
### Task D.2.1: report_service_test.go
**Files:**
- Create: `backend/services/moderationService/service/report_service_test.go`
**关键测试用例**spec §7.2
- `TestSubmitReport_Duplicate` —— 同 reporter+target+type 第二次提交返回 50004
- `TestSubmitReport_SelfReport` —— reporter_id == owner_uid 返回 50011
- `TestSubmitReport_AutoHideTrigger` —— 5 个独立用户举报 → status='auto_hidden'
- `TestClaimReport_Concurrent` —— 2 admin 同时 claim → 1 成功 1 返回 50008
- `TestReleaseReport_OnlyOwner` —— 非 claimed_by admin 释放返回 0 行
- `TestDismissPathA_ClearsClaimedBy` —— pending 状态必 claimed_by=NULLchk_reports_claimed_pair
- `TestDismissPathB_DismissesFromReviewing` —— reviewing → dismissed
- `TestDismissPathC_RestoresAsset` —— auto_hidden → reviewing → dismissed(restore=true) 恢复业务表
- `TestDismissPathD_PreservesHidden` —— auto_hidden → reviewing → dismissed(restore=false) 不恢复
- `TestResolveReport_Takedown` —— takedown 业务表软删除 + mts UPSERT
- `TestWithdrawReport_OnlyPending` —— 非 pending 撤回返回 0 行 → 50019
- `TestBulkDismiss_MultipleIds` —— 批量驳回返回 affected 数
- `TestForceRelease_AnyAdmin` —— 任何 admin 可强制释放他人认领
- `TestClearWarn_PreservesHistory` —— warn_cleared 保留 warn_count + last_warned_atv1.7 H2
- `TestAutoHideNoop_WhenAssetAlreadyInactive` —— 业务表已 inactive 时记 autohide_noop
### Task D.2.2: feedback_service_test.go
- `TestClaimFeedback_Concurrent`
- `TestReplyFeedback_ClearsClaimedBy` —— replied 终态清 claimed_by/claimed_atchk_feedbacks_claimed_pair
- `TestCloseFeedback_OnlyFromReviewing` —— 仅从 reviewing 入口
- `TestArchiveFeedback_NotFromReplied` —— replied 状态 archive 返回 0 行v2.0 CRITICAL
### Task D.2.3: auto_hide_service_test.go
- `TestLuaScript_FirstIncrement` —— 首次 INCR 返回 {0, 1}
- `TestLuaScript_DuplicateUser` —— 同用户第二次 SETNX 失败返回 {0, 1} 不再 INCR
- `TestLuaScript_ThresholdReached` —— 第 N 次 INCR 返回 {1, N}
- `TestLuaScript_ExpireOnFirst` —— n=1 时设置 EXPIRE
### Task D.2.4: concurrency_test.go
- `TestConcurrentReports_AutoHide` —— 100 用户同时举报同对象counter INCR 100 次但只触发 1 次自动隐藏
- `TestConcurrentClaim_OnlyOneWins` —— 2 admin 同时 claim1 成功 1 返回 50008
- `TestLockTTL_BypassAfterExpiry` —— 5s 锁过期后新请求可重入
- `TestLuaWithLock_Interaction` —— errgroup 模拟锁 + Lua 交互
### Task D.2.5: 跑测试
```bash
cd backend/services/moderationService
go test ./service/... -v -race
# 期望:全部 PASS
```
---
## 阶段 D.3FastAPI 单元测试
### Task D.3.1: test_moderation_admin.py
**Files:**
- Modify: `TopFans-activity-admin/backend/handlers/test_moderation_admin.py`
**关键测试**
- `test_list_reports` —— happy path + 筛选
- `test_claim_report_already_claimed` —— 返回 50008响应体含 claimed_by/claimed_at
- `test_claim_report_resolved` —— 返回 50009
- `test_claim_report_not_found` —— 返回 50007
- `test_execute_action_takedown` —— 业务表 + mts 双写
- `test_execute_action_dismiss_path_a` —— pending → dismissed 快速通道
- `test_execute_action_dismiss_path_c_restore` —— 业务表恢复 + Redis 计数清零
- `test_execute_action_warn_no_soft_delete` —— warn 不软删除 user
- `test_execute_action_restore_cte` —— restore 用 CTE 条件写 mtsv2.5 W6
- `test_bulk_dismiss` —— 批量
- `test_force_release` —— 强制释放他人认领
- `test_clear_warn_preserves_history` —— 保留 warn_count + last_warned_at
- `test_unauthorized` —— 无 token → 401
- Fixture`httpx.AsyncClient` + pytestadmin token 用现有 `make_test_token()`
---
## 阶段 D.4:集成测试
### Task D.4.1: testcontainers 集成测试
**Files:**
- Create: `backend/services/moderationService/integration/`
**隔离策略**spec §7.4
- testcontainers-go 启临时 PG 16 + Redis 7
- 业务表自动跑阶段 A.1 迁移
- Redis key 加 `test:mod:report:*` 前缀
**端到端场景**
- 提交举报 → 后台审核 → 状态变更 → 通知
- 自动隐藏5 个独立用户举报 → 自动隐藏 + 状态表写入
- dismiss 4 路径覆盖
- 跨服务事务回滚:业务表软删除成功但 mts UPSERT 失败 → 整体回滚
- Lua 脚本测试SETNX/INCR/EXPIRE 边界
---
## 阶段 D.5:监控指标
### Task D.5.1: 在 moderationService 注册 13+ Prometheus 指标
**Files:**
- Modify: `backend/services/moderationService/main.go`
按 spec §11.3 注册指标:
- `moderation.report.submitted.count (counter, tags: target_type, category_code)`
- `moderation.report.auto_hidden.count (counter)`
- `moderation.report.auto_hidden.noop.count (counter)` (v2.3)
- `moderation.report.resolve.duration (histogram)`
- `moderation.feedback.reply.duration (histogram)`
- `moderation.feedback.terminal_state.claim_attempt.count (counter, tag: status)` (v2.3)
- `moderation.actions.failed.count (counter, tags: action_type, error)`
- `moderation.actions.auto_hide.count (counter, tag: target_type)` (v2.3)
- `moderation.actions.warn_cleared.count (counter)` (v2.3)
- `moderation.report.duplicate.count (counter, tag: target_type)`
- `moderation.auto_hide.lock.contention.count (counter)`
- `moderation.notification.send_failed.count (counter, tag: template)`
- `moderation.transaction.rollback.count (counter, tag: flow)`
- `moderation.api.latency (histogram, tag: endpoint, status)`
- `moderation.dashboard.queue.pending (gauge)`
- `moderation.dashboard.queue.auto_hidden (gauge)`
- `moderation.redis.counter_reset.count (counter, tag: path=C|D)` (v2.3)
- `moderation.rate_limit.hit.count (counter, tag: type, scope)` (v2.3)
- `moderation.api.latency.evidence_upload.p95 (histogram)` (v2.3)
---
## 自审检查清单Plan D
- [ ] 6 个通知模板已注册到 notificationService
- [ ] moderationService notification_client.go stub 替换为真实现
- [ ] Go 单元测试全部 PASS含 claimed_* 一致性、Lua、并发
- [ ] FastAPI 单元测试全部 PASS含 50008/50009/50014 区分)
- [ ] 集成测试testcontainers通过
- [ ] 13+ Prometheus 指标注册
- [ ] 监控指标覆盖业务成功路径 + 失败/异常 + 延迟/SLO