--- title: "chore: GEO Platform Production Hardening & Test Infrastructure" type: chore status: completed date: 2026-06-04 --- # GEO Platform Production Hardening & Test Infrastructure ## Summary 在 Plan 008(生产就绪)基础上,完成收尾工作并进一步加固生产环境:实现限流中间件 Redis 双后端、补全标签验证逻辑、加固认证错误处理、清理过时注释。同时全面深化 E2E 测试体系:提取共享 Playwright fixture、补充交互测试和错误状态测试、建立 API mock 层。完成后平台具备多实例部署能力和高质量测试保障。 ## Problem Frame Plan 008 的 U1-U6 已完成代码变更,但 U7 验证和 U8 尚未收尾,所有变更未提交 Git。深度调研发现多个生产阻塞问题:限流中间件基于内存存储,多实例部署时限流失效;`platform_rules.py` 中标签验证逻辑(min_tags/max_tags)未实现;认证模块 catch 块吞掉错误无日志;Sentry 集成已完成但 TODO 注释未清理。E2E 测试方面,19 个测试文件中 loginAndWait 函数重复定义,测试以页面渲染验证为主,缺少交互测试、错误状态测试和 API mock 层,测试深度不足以支撑生产信心。 --- ## Requirements **Plan 008 收尾** R1. Plan 008 U7 测试文件迁移后 pytest 验证通过 R2. Plan 008 U8 pytest-cov 覆盖率配置完成,`pytest` 自动生成覆盖率报告 **限流中间件加固** R3. 限流中间件支持 Redis 后端,生产环境自动使用 Redis R4. Redis 不可用时自动降级到内存后端,不影响服务启动 R5. 内存后端添加定期清理机制,防止长期运行内存增长 **标签验证逻辑** R6. `platform_rules.py` 中 min_tags/max_tags 规则实际执行校验 **认证错误处理** R7. 认证模块 authorize 和 refreshAccessToken 中 catch 块记录错误日志 **代码卫生** R8. 已完成的 Sentry 集成点 TODO 注释清理 **E2E 测试基础设施** R9. 19 个 E2E 测试文件中 loginAndWait 提取为共享 Playwright fixture R10. 关键业务流程补充交互测试(数据输入、提交、验证) R11. 补充错误状态测试(网络错误、API 500、空数据) R12. 建立 API mock 层,E2E 测试不依赖真实后端数据 --- ## Key Technical Decisions KTD1. **限流中间件采用 Redis 优先 + 内存 fallback 双后端。** 生产环境通过 `RATE_LIMIT_BACKEND=redis` 使用 Redis,开发/测试环境默认使用内存后端。Redis 连接失败时自动降级到内存后端并记录警告,不阻塞服务启动。内存后端添加 `asyncio.create_task` 后台清理任务。 KTD2. **E2E API mock 层使用 Playwright route interception。** 不引入额外的 mock server 依赖,利用 Playwright 原生的 `page.route()` 拦截 API 请求并返回预设数据。mock 数据按场景组织为 JSON fixture 文件,测试可按需加载。 KTD3. **共享 Playwright fixture 放在 `e2e/fixtures/` 目录。** 包含 `authenticatedPage`(自动登录的 page)、`mockApi`(API mock 辅助函数)、`testData`(测试数据工厂)。各测试文件通过 `import` 引用,消除 loginAndWait 重复。 KTD4. **标签验证在现有 `validate_content` 方法中扩展。** 不新建验证器类,在 `platform_rules.py` 的 `validate_content` 方法中增加标签数量校验分支,读取 content 的 tags 字段与 min_tags/max_tags 规则比对。 --- ## High-Level Technical Design ```mermaid flowchart TB subgraph Phase1["Phase 1 — Plan 008 收尾"] U1[U1. U7 验证 + U8 覆盖率配置] end subgraph Phase2["Phase 2 — 生产加固"] U2[U2. 限流中间件双后端] U3[U3. 标签验证逻辑] U4[U4. 认证错误处理] U5[U5. 代码卫生清理] end subgraph Phase3["Phase 3 — E2E 测试基础设施"] U6[U6. 共享 Playwright fixture] U7[U7. API mock 层] U8[U8. 交互测试 + 错误状态测试] end U1 --> U2 U2 --> U3 U2 --> U4 U4 --> U5 U5 --> U6 U6 --> U7 U7 --> U8 ``` --- ## Implementation Units ### U1. Plan 008 Completion — U7 Verification + U8 Coverage Config **Goal:** 完成 Plan 008 剩余工作:验证测试文件迁移无回归,配置 pytest-cov 自动覆盖率 **Requirements:** R1, R2 **Dependencies:** none(Plan 008 U1-U7 代码变更已存在) **Files:** - `backend/pyproject.toml` — 添加 pytest-cov addopts 和 coverage 配置 - `backend/requirements.txt` — 确认 pytest-cov 已存在 - `backend/tests/test_infrastructure/` — 验证迁移后测试通过 - `backend/tests/test_services/` — 验证迁移后测试通过 **Approach:** 1. 运行 `pytest tests/` 验证 U7 测试文件迁移后无回归 2. 在 `pyproject.toml` 的 `[tool.pytest.ini_options]` 添加 `addopts = "--cov=app --cov-report=term-missing --cov-report=html"` 3. 添加 `[tool.coverage.run]` 配置 omit 列表(排除 `tests/*`、`migrations/*`、`__pycache__/*`) 4. 添加 `[tool.coverage.report]` 配置 `exclude_lines`(排除 `pragma: no cover`、`if __name__` 等) 5. 验证 `pytest` 自动生成覆盖率报告 **Patterns to follow:** pytest-cov 标准配置模式 **Test scenarios:** - `pytest tests/` 全部通过,通过率与迁移前一致 - `pytest` 自动生成覆盖率报告,显示各模块覆盖率百分比 - 覆盖率报告排除 migrations 和测试文件 - HTML 覆盖率报告可正常打开查看 **Verification:** `pytest` 成功生成覆盖率报告,`find tests/ -maxdepth 1 -name "test_*.py"` 返回空 --- ### U2. Rate Limiter Dual Backend — Redis + Memory Fallback **Goal:** 限流中间件支持 Redis 后端,生产环境自动使用 Redis,Redis 不可用时降级到内存 **Requirements:** R3, R4, R5 **Dependencies:** U1(Plan 008 收尾后再改限流) **Files:** - `backend/app/middleware/rate_limit.py` — 重构为双后端架构 - `backend/app/config.py` — 添加 `RATE_LIMIT_BACKEND` 配置 - `backend/tests/test_infrastructure/test_rate_limit.py` — 新建限流测试 - `.env.production.example` — 添加 `RATE_LIMIT_BACKEND=redis` **Approach:** 1. 定义 `RateLimitBackend` 抽象接口(`is_rate_limited`、`reset` 方法) 2. 实现 `MemoryRateLimitBackend`(当前逻辑提取,添加后台清理 task) 3. 实现 `RedisRateLimitBackend`(使用 Redis sorted set + ZRANGEBYSCORE 实现滑动窗口) 4. `RateLimitMiddleware.__init__` 根据 `RATE_LIMIT_BACKEND` 配置选择后端 5. Redis 后端初始化时连接失败则降级到内存后端并记录警告 6. 内存后端添加 `asyncio.create_task` 后台清理任务,每 60 秒清理过期记录 7. 保留 `RATE_LIMIT_DISABLED` 环境变量用于 E2E 测试 **Patterns to follow:** 策略模式(Strategy Pattern),现有 Redis 连接管理(`app/core/redis.py`) **Test scenarios:** - `RATE_LIMIT_BACKEND=memory` 时使用内存后端,限流正常工作 - `RATE_LIMIT_BACKEND=redis` 且 Redis 可用时使用 Redis 后端 - `RATE_LIMIT_BACKEND=redis` 且 Redis 不可用时降级到内存后端,记录警告 - Redis 后端多实例共享限流状态(模拟两个客户端连接同一 Redis) - 内存后端后台清理任务运行,过期记录被清理 - `RATE_LIMIT_DISABLED=1` 时所有后端跳过限流 - 现有 4 层限流规则(auth_strict、auth、query_run、global)在新后端下均正常 **Verification:** `pytest tests/test_infrastructure/test_rate_limit.py` 通过,生产配置 Redis 后端时限流生效 --- ### U3. Tag Validation Logic Implementation **Goal:** 实现 `platform_rules.py` 中 min_tags/max_tags 标签数量校验 **Requirements:** R6 **Dependencies:** U2 **Files:** - `backend/app/services/distribution/platform_rules.py` — 补全标签验证逻辑 - `backend/tests/test_services/test_platform_rules.py` — 新建或扩展标签验证测试 **Approach:** 1. 定位 `validate_content` 方法中 TODO 注释(约第 1015 行) 2. 从 content 数据中提取 tags 字段(支持 list 和 comma-separated string 两种格式) 3. 与规则中的 min_tags/max_tags 比对 4. 返回验证结果:tags 数量不足时返回错误信息,包含当前 tags 数和要求的范围 5. 无 tags 字段时视为 0 个标签 **Patterns to follow:** 现有 `validate_content` 中其他规则的校验模式 **Test scenarios:** - content tags 数量在 min_tags 和 max_tags 范围内 → 验证通过 - content tags 数量 < min_tags → 验证失败,错误信息包含当前数量和最小要求 - content tags 数量 > max_tags → 验证失败,错误信息包含当前数量和最大限制 - content 无 tags 字段 → 视为 0 个标签,按 min_tags 规则校验 - content tags 为 comma-separated string → 正确解析并计数 - 规则无 min_tags/max_tags → 跳过标签校验 **Verification:** `pytest tests/test_services/test_platform_rules.py` 通过,标签规则校验逻辑覆盖全部场景 --- ### U4. Auth Error Handling Hardening **Goal:** 认证模块 catch 块添加错误日志记录,不再静默吞掉异常 **Requirements:** R7 **Dependencies:** U2 **Files:** - `frontend/lib/auth.ts` — authorize 和 refreshAccessToken 添加错误日志 - `frontend/lib/logger.ts` — 新建前端日志工具(或使用 console.error + Sentry) **Approach:** 1. 在 `authorize` 函数的 catch 块中添加 `console.error("[Auth] Login failed:", error)` 2. 在 `refreshAccessToken` 的 catch 块中添加 `console.error("[Auth] Token refresh failed:", error)` 3. 如果 Sentry 已初始化,同时调用 `Sentry.captureException(error)` 上报 4. 保持现有返回值逻辑不变(authorize 返回 null,refreshAccessToken 标记 error) 5. 可选:创建简单的 `logger.ts` 工具封装 console.error + Sentry 上报 **Patterns to follow:** 后端 `app/core/logging.py` 的日志模式 **Test scenarios:** - 登录失败时控制台输出错误日志 - Token 刷新失败时控制台输出错误日志 - Sentry 已配置时错误自动上报 - 错误日志不影响现有认证流程(返回值不变) **Verification:** 模拟登录失败和 token 刷新失败,确认日志输出和 Sentry 上报 --- ### U5. Code Hygiene — Sentry Comment Cleanup **Goal:** 清理已完成的 Sentry 集成点 TODO 注释,更新为准确描述 **Requirements:** R8 **Dependencies:** U4 **Files:** - `backend/app/middleware/metrics.py` — 更新 Sentry 集成点注释 - `frontend/components/ErrorBoundary.tsx` — 更新 Sentry 集成点注释 **Approach:** 1. `metrics.py`:将"预留 Sentry 集成点(TODO 注释标注)"更新为"已集成 Sentry 性能监控" 2. `ErrorBoundary.tsx`:将"预留 Sentry 集成点(搜索 TODO:SENTRY)"更新为"已集成 Sentry 错误上报" 3. 确认两处实际 Sentry 代码正常工作 **Test expectation:** none — 纯注释更新,无行为变更 **Verification:** grep 搜索确认无残留的 Sentry TODO 注释 --- ### U6. Shared Playwright Fixtures **Goal:** 提取 19 个 E2E 测试文件中重复的 loginAndWait 为共享 Playwright fixture **Requirements:** R9 **Dependencies:** U5 **Files:** - `frontend/e2e/fixtures/auth.ts` — 新建,authenticatedPage fixture - `frontend/e2e/fixtures/index.ts` — 新建,统一导出 - `frontend/e2e/tests/*.spec.ts` — 19 个文件重构为使用共享 fixture **Approach:** 1. 创建 `e2e/fixtures/auth.ts`,定义 `authenticatedPage` fixture: - 基于 Playwright `test.extend()` 扩展 - 自动执行登录流程(复用当前 loginAndWait 逻辑) - 测试结束后自动清理 2. 创建 `e2e/fixtures/index.ts`,统一导出所有 fixture 3. 逐个重构 19 个测试文件: - 移除文件内的 `loginAndWait` 函数定义 - 将 `loginAndWait(page)` 调用替换为 `authenticatedPage` fixture 使用 - 保持测试逻辑不变 4. 验证重构后所有 E2E 测试通过 **Patterns to follow:** Playwright 官方 fixture 模式(`test.extend()`) **Test scenarios:** - `authenticatedPage` fixture 自动完成登录,测试可直接使用已认证的 page - 所有 19 个 E2E 测试重构后行为与重构前一致 - 无测试文件内重复定义 loginAndWait **Verification:** `npx playwright test` 全部通过,grep 确认无残留的 loginAndWait 函数定义 --- ### U7. E2E API Mock Layer **Goal:** 建立 Playwright route interception API mock 层,E2E 测试不依赖真实后端数据 **Requirements:** R12 **Dependencies:** U6 **Files:** - `frontend/e2e/fixtures/api-mock.ts` — 新建,API mock 辅助函数 - `frontend/e2e/fixtures/mock-data/` — 新建,mock 数据 JSON 文件目录 - `frontend/e2e/fixtures/index.ts` — 更新导出 **Approach:** 1. 创建 `api-mock.ts`,提供以下功能: - `mockApi(page, endpoint, response)` — 拦截指定 API 请求返回预设数据 - `mockApiError(page, endpoint, statusCode)` — 模拟 API 错误响应 - `mockApiDelay(page, endpoint, ms)` — 模拟 API 延迟 - `clearApiMocks(page)` — 清除所有 mock 2. 创建 `mock-data/` 目录,按业务场景组织 JSON fixture: - `health-score.json` — 健康评分数据 - `citations.json` — 引用记录数据 - `competitors.json` — 竞品分析数据 - `analytics.json` — 数据监测数据 - `knowledge.json` — 知识库数据 - `empty-state.json` — 空状态数据 3. 所有 mock 使用 Playwright `page.route()` 实现,不引入额外依赖 4. mock 辅助函数作为 fixture 导出,测试可按需使用 **Patterns to follow:** Playwright route interception 官方文档模式 **Test scenarios:** - `mockApi` 成功拦截 API 请求并返回预设数据 - `mockApiError` 成功模拟 500 错误响应 - `mockApiDelay` 成功模拟 API 延迟 - 多个 mock 同时生效不冲突 - `clearApiMocks` 清除后请求正常发送到真实后端 - mock 数据 JSON 格式正确,可被前端正确解析 **Verification:** 使用 mock 的 E2E 测试在无后端环境下通过 --- ### U8. E2E Interaction and Error State Tests **Goal:** 补充关键业务流程的交互测试和错误状态测试 **Requirements:** R10, R11 **Dependencies:** U7 **Files:** - `frontend/e2e/tests/health-score-interaction.spec.ts` — 新建,健康评分交互测试 - `frontend/e2e/tests/citation-flow.spec.ts` — 新建,引用记录完整流程测试 - `frontend/e2e/tests/competitor-interaction.spec.ts` — 新建,竞品分析交互测试 - `frontend/e2e/tests/error-states.spec.ts` — 新建,错误状态测试 - `frontend/e2e/tests/knowledge-interaction.spec.ts` — 新建,知识库交互测试 **Approach:** 1. **交互测试**(3 个文件): - 健康评分:选择时间范围 → 查看评分变化 → 查看维度详情 - 引用记录:搜索引用 → 筛选来源 → 查看引用详情 - 竞品分析:添加竞品 → 查看对比 → 删除竞品 - 知识库:上传文档 → 查看文档列表 → 删除文档 2. **错误状态测试**(1 个文件): - API 500 错误:页面显示错误提示,提供重试按钮 - 网络断开:页面显示离线提示 - 空数据状态:页面显示空状态引导 - 认证过期:自动跳转登录页 3. 所有测试使用 U7 的 API mock 层,不依赖真实后端 4. 使用 U6 的 `authenticatedPage` fixture,无需手动登录 **Patterns to follow:** 现有 E2E 测试结构,Playwright 最佳实践 **Test scenarios:** - 健康评分交互:切换时间范围后数据更新 - 引用记录搜索:输入关键词后结果过滤 - 竞品添加:填写表单提交后列表更新 - API 500:页面显示错误提示,点击重试后重新请求 - 空数据:页面显示引导文案和操作按钮 - 认证过期:跳转登录页,重新登录后恢复 **Verification:** `npx playwright test` 全部通过,新增测试覆盖交互和错误状态 --- ## Scope Boundaries **In scope:** - Plan 008 收尾(U7 验证 + U8 覆盖率配置) - 限流中间件 Redis 双后端实现 - 标签验证逻辑补全 - 认证错误处理加固 - Sentry 注释清理 - E2E 共享 fixture 提取 - E2E API mock 层建立 - E2E 交互测试和错误状态测试 **Deferred for later:** - 真实微信/支付宝 SDK 商户密钥配置(需外部资质审批) - Redis 连接池统一管理 - 前端组件级单元测试扩展(当前仅 13 个) - 依赖大版本升级(Next.js 14→15、Tailwind 3→4、ESLint 8→9) - Playwright 移动端设备测试 - 性能/负载测试框架(Locust/k6) - 视觉回归测试 - 生产环境域名和 HTTPS 配置 - Docker 日志驱动和日志轮转配置 - Plan 001/002 状态更新(行政性工作,随 Git 提交一起处理) **Outside this plan:** - 新功能开发 - 代码重构(除限流中间件架构调整外) - UI 打磨 --- ## Risks & Dependencies - **限流中间件重构可能影响现有请求处理。** 内存后端逻辑提取为独立类时需保持行为一致。缓解:先写测试覆盖现有行为,再重构。 - **Redis 后端实现需确保原子性。** 滑动窗口的 ZADD + ZRANGEBYSCORE 需要使用 Redis pipeline 或 Lua script 保证原子性。缓解:使用 pipeline 执行多命令。 - **E2E fixture 重构涉及 19 个文件。** 逐个重构时可能遗漏或引入不一致。缓解:重构后全量运行 E2E 测试验证。 - **API mock 数据可能与真实 API 响应格式不同步。** 后端 API 变更时 mock 数据可能过时。缓解:mock 数据基于 TypeScript 类型定义生成,类型变更时编译报错。 - **交互测试依赖前端组件稳定性。** 前端 UI 变更可能导致选择器失效。缓解:使用 data-testid 属性而非 CSS 选择器。 --- ## Open Questions **Deferred to implementation:** - Redis 连接使用现有 `app/core/redis.py` 还是新建连接?需确认现有 Redis 客户端配置 - E2E mock 数据的初始数据集从哪里获取?可从开发环境 API 响应录制 - 前端 logger 是否需要统一的日志级别控制?还是简单的 console.error + Sentry 即可 --- ## Sources & Research - `docs/plans/2026-06-04-008-chore-geo-production-readiness-plan.md` — 当前计划(status: active) - `docs/plans/2026-05-31-002-test-quality-assurance-system-plan.md` — 测试质量体系(status: active) - `backend/app/middleware/rate_limit.py` — 当前内存限流实现 - `backend/app/services/distribution/platform_rules.py` — 标签验证 TODO(第 1015 行) - `frontend/lib/auth.ts` — 认证模块 - `frontend/e2e/tests/` — 19 个 E2E 测试文件 - `backend/pyproject.toml` — 缺少 pytest-cov 配置