测试遵循约定顺序:① 安全 → ② 性能(两关全过)→ ③ 功能/完整 → ④ 端到端 + 截图。 全程在 Docker dev 环境(
LLM_PROVIDER=mock、EMBEDDING_PROVIDER=mock)执行,四大模块端到端真集成(API → Redis → Celery Worker → Qdrant / PostgreSQL / MinIO)。
| 阶段 | 工具 | 结果 |
|---|---|---|
| ① 安全 | pytest(-m security) + bandit + pip-audit | ✅ 9/9 通过;bandit High=0;依赖 CVE 已升级(见 §1.3) |
| ② 性能 | tests/perf/run_perf.py(SLA 断言) | ✅ 全部达标(搜索 P95 42ms<1s、RAG 首 token 6.7ms<3s) |
| ③ 功能 | pytest(-m functional) | ✅ 8/8 通过,覆盖四大模块 |
| ④ 端到端 | Playwright + 截图 | 见 §4 与 tests/screenshots/ |
| 用例 | 验证点 | 结果 |
|---|---|---|
| test_health_ok | 健康检查 | ✅ |
| test_protected_requires_auth | 未授权访问受保护接口返回 401(feed/tasks/kb/quota) | ✅ |
| test_invalid_token_rejected | 伪造 JWT 被拒 401 | ✅ |
| test_security_headers_present | 响应含 X-Content-Type-Options=nosniff、X-Frame-Options=SAMEORIGIN |
✅ |
| test_password_never_returned | 接口不回传口令/哈希 | ✅ |
| test_tenant_isolation | 租户 A 资源对租户 B 不可见、越权删除 404(多租户隔离) | ✅ |
| test_free_user_cannot_use_advanced_rag | 免费版 RAG 问答被门控 403 | ✅ |
| test_search_handles_injection_like_input | SQL 注入/XSS/路径穿越式输入不致 500 | ✅ |
| test_login_rate_limited | 登录暴力尝试触发 429 限流 | ✅ |
- 初次扫描发现 1 处 High:
hashlib.md5(mock embedding 的 token→维度哈希分桶)。 - 修复:加
usedforsecurity=False明确非安全用途。复扫 High=0(残留 3 处 Low 为爬虫try/except兜底,属正常防御式代码)。
| 依赖 | 处理 |
|---|---|
| cryptography 43→45 | ✅ 清除 CVE-2024-12797 |
| lxml 5→6.1 | ✅ 清除 PYSEC-2026-87 |
| markdownify 0.13→0.14.1 | ✅ 清除 CVE-2025-46656 |
| starlette→0.47.3(随 FastAPI 0.118) | ✅ 清除 CVE-2025-54121(multipart DoS) |
| 残留:cryptography(2026 预警,需 46/48)、starlette(需 1.x,与 FastAPI 不兼容)、pip/pytest(构建/测试工具,非运行时暴露) |
tests/perf/run_perf.py 对运行中的服务发压并断言 PRD 四-性能指标,任一不达标即退出非 0、阻断后续功能测试。
================ 性能 / SLA 报告 ================
指标 实测 SLA 结果
health 1.6ms <200ms ✅ PASS
search 42.2ms <1000ms ✅ PASS (PRD:语义搜索 <1s)
rag_first_token 6.7ms <3000ms ✅ PASS (PRD:RAG 首 token <3s)
================================================
[perf] 全部达标
说明:mock 供应商下 LLM 时延为本地确定性极小值;真实模型下,语义搜索仍不经 LLM(Qdrant HNSW 召回 + 重排)故稳定 <1s,RAG 首 token 取决于所选模型(方案以"流式先返首 token + 轻量快档 + 召回缓存"保障 <3s)。
| 用例 | 覆盖模块 / PRD | 结果 |
|---|---|---|
| test_feed_refresh_and_list | 资讯流抓取→时间轴列表(字段:中文精要/英文标题/留源URL) 2.1 | ✅ |
| test_feed_favorite_into_kb | 收藏→入库"AI资讯"分类,不占配额逻辑 2.1.4 | ✅ |
| test_analyze_github | GitHub 深度解剖→报告入库(开源项目分类、可读内容) 2.2 | ✅ |
| test_analyze_site_md | 站点熔炼→中文 MD 生成入库 2.3 | ✅ |
| test_analyze_quota_enforced_for_free | 免费版每月 3 次解析配额护栏 六-商业化 | ✅ |
| test_upload_and_tagging | 文档上传→解析→自动打标(Tag/摘要/分类) 2.4.1/2.4.2 | ✅ |
| test_semantic_search | 全局语义搜索召回命中分块 2.4.3 | ✅ |
| test_rag_chat_with_citations | RAG 流式问答帧(token/citations/done 或 not_found 防幻觉) 2.4.4 | ✅ |
覆盖率 59%:worker 任务代码在独立 worker 容器执行,覆盖率仅统计 api/测试进程,故
worker/*行覆盖偏低,但功能测试已端到端证明其正确路径。
经 Caddy(http://localhost)访问真实前端(Vite)+ 真实后端,API 预置数据后逐屏截图,产物在 tests/screenshots/:
| 截图 | 画面 |
|---|---|
| 01-login.png | 登录/注册(铜镜深色) |
| 02-feed.png | 见微 · 资讯流(发光时间轴 + 卡片瀑布流) |
| 03-github-report.png | 知著 · GitHub 深度解剖报告(档案卡 + 目录 + 正文) |
| 04-site.png | 重构 · 站点熔炼三栏阅读 |
| 05-kb.png | 鉴真 · 知识库(树状导航 + 网盘式文档) |
| 06-search.png / 07-rag.png | 语义搜索结果 / RAG 引用问答 |
Running 6 tests using 1 worker
ok 1 01 登录页 (2.3s)
ok 2 02 资讯流(见微) (3.0s)
ok 3 03 GitHub 深度解剖(知著) (3.0s)
ok 4 04 站点熔炼(重构) (2.6s)
ok 5 05 知识库(鉴真) (2.4s)
ok 6 06 语义搜索 + RAG (7.9s)
6 passed (27.5s)
产物(tests/screenshots/,均为真实「铜镜」UI + 真实预置数据,非空白):
01-login.png(137KB) · 02-feed.png(68KB) · 03-github-report.png(71KB) · 04-site.png(48KB) · 05-kb.png(67KB) · 06-search.png(78KB) · 07-rag.png(78KB)
要点核对(目视):登录页铜镜深色 + 赤金 CTA + 四模块文案;资讯流发光时间轴 + 卡片(赤金"最新"角标/中文精要/英文原题/留源);GitHub 报告档案卡 + 目录 + 正文;知识库 Notion 树导航 + 标签筛选 + Pro 配额条 + 顶部 RAG 对话框(水波纹"检索中" + 赤金引用提示) + 网盘式文档列表。
| # | 现象 | 根因 | 修复 |
|---|---|---|---|
| 1 | register/login 返回 422(body 被当 query) | slowapi @limiter.limit 装饰器破坏 FastAPI 签名 |
改为自写限流依赖 Depends(rate_limit(...)) |
| 2 | 测试 "Event loop is closed" | pytest 多事件循环复用全局异步连接池 | 测试进程 DB 用 NullPool、Redis 每次新建 |
| 3 | 注册 tenant_id NOT NULL 违约 |
flush 后才设 tenant_id | 注册时显式生成 id 并一并赋 tenant_id |
| 4 | bandit High(md5) | 哈希分桶被判安全用途 | usedforsecurity=False |
| 5 | 前端报告永不加载 | 前后端任务状态枚举漂移(success vs succeeded) | 前端兼容 success |
| 6 | RAG/搜索 worker "different loop" / async-with 不支持 | 全局 AsyncQdrantClient 跨循环 + 版本不支持 async with | 本地客户端 + try/finally await close() |
| 7 | worker 任务 DB/Redis "different loop" | asyncio.run 每任务新循环 |
worker 用常驻后台事件循环跑协程 |
| 8 | doc_tags 外键违约 | 同事务父子表插入顺序 | 插 Document 后 flush 再插 DocTag |