10 KiB
10 KiB
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 |
反馈人 | 同上 |
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。
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.2:Go 单元测试
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 第二次提交返回 50004TestSubmitReport_SelfReport—— reporter_id == owner_uid 返回 50011TestSubmitReport_AutoHideTrigger—— 5 个独立用户举报 → status='auto_hidden'TestClaimReport_Concurrent—— 2 admin 同时 claim → 1 成功 1 返回 50008TestReleaseReport_OnlyOwner—— 非 claimed_by admin 释放返回 0 行TestDismissPathA_ClearsClaimedBy—— pending 状态必 claimed_by=NULL(chk_reports_claimed_pair)TestDismissPathB_DismissesFromReviewing—— reviewing → dismissedTestDismissPathC_RestoresAsset—— auto_hidden → reviewing → dismissed(restore=true) 恢复业务表TestDismissPathD_PreservesHidden—— auto_hidden → reviewing → dismissed(restore=false) 不恢复TestResolveReport_Takedown—— takedown 业务表软删除 + mts UPSERTTestWithdrawReport_OnlyPending—— 非 pending 撤回返回 0 行 → 50019TestBulkDismiss_MultipleIds—— 批量驳回返回 affected 数TestForceRelease_AnyAdmin—— 任何 admin 可强制释放他人认领TestClearWarn_PreservesHistory—— warn_cleared 保留 warn_count + last_warned_at(v1.7 H2)TestAutoHideNoop_WhenAssetAlreadyInactive—— 业务表已 inactive 时记 autohide_noop
Task D.2.2: feedback_service_test.go
TestClaimFeedback_ConcurrentTestReplyFeedback_ClearsClaimedBy—— replied 终态清 claimed_by/claimed_at(chk_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} 不再 INCRTestLuaScript_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 同时 claim,1 成功 1 返回 50008TestLockTTL_BypassAfterExpiry—— 5s 锁过期后新请求可重入TestLuaWithLock_Interaction—— errgroup 模拟锁 + Lua 交互
Task D.2.5: 跑测试
cd backend/services/moderationService
go test ./service/... -v -race
# 期望:全部 PASS
阶段 D.3:FastAPI 单元测试
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—— 返回 50009test_claim_report_not_found—— 返回 50007test_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 不软删除 usertest_execute_action_restore_cte—— restore 用 CTE 条件写 mts(v2.5 W6)test_bulk_dismiss—— 批量test_force_release—— 强制释放他人认领test_clear_warn_preserves_history—— 保留 warn_count + last_warned_attest_unauthorized—— 无 token → 401- Fixture:
httpx.AsyncClient+ pytest;admin 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