# 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.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 第二次提交返回 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=NULL(chk_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_at(v1.7 H2) - `TestAutoHideNoop_WhenAssetAlreadyInactive` —— 业务表已 inactive 时记 autohide_noop ### Task D.2.2: feedback_service_test.go - `TestClaimFeedback_Concurrent` - `TestReplyFeedback_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} 不再 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 同时 claim,1 成功 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.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` —— 返回 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 条件写 mts(v2.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` + 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