fix: 修复会话鉴权「登录后 token 无效」并补齐 NAS 部署流程
- deps.py: _create_redis_client 增加同步 ping 校验,Redis 不可达时正确降级为内存模式 - main.py: lifespan 复用 deps 的 UserStore 单例,避免 admin 与 API 请求实例不一致 - 前端: 强制改密弹窗(must_change_password 用户)、兼容新旧登录返回格式、markPasswordChanged - 新增 NAS SSH 部署脚本与批处理测试;gitignore 前端构建产物
This commit is contained in:
+120
-1
@@ -13,6 +13,8 @@
|
||||
10. 请求拦截:Authorization Bearer 注入、1005 回登录、1006 错误条
|
||||
11. API 指南区块:导航/section、API_GUIDE 清单与真实路由一致性、试一下面板、
|
||||
curl 复制、auth 标注、upload 文件选择、禁止自定义 URL
|
||||
12. 入库进度区块(Task 2):section/nav、表格结构、2s 轮询启停、状态徽章复用、
|
||||
done/failed 操作按钮、reingest 端点、批量上传 multiple + upload-batch 路径
|
||||
"""
|
||||
|
||||
import re
|
||||
@@ -39,9 +41,20 @@ def client(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestClient]:
|
||||
|
||||
@pytest.fixture
|
||||
def admin_html(client: TestClient) -> str:
|
||||
"""请求 /admin 并返回 HTML 文本(前置断言 200)"""
|
||||
"""请求 /admin 并返回旧版 admin.html 文本(前置断言 200)
|
||||
|
||||
当 app/static/admin 符号链接存在时(Vue SPA 部署),/admin 端点会返回 SPA 入口
|
||||
而非旧版 admin.html。此时直接读取 admin.html 文件内容进行测试。
|
||||
"""
|
||||
resp = client.get("/admin")
|
||||
assert resp.status_code == 200
|
||||
# 检测是否返回了 Vue SPA(无 section-overview 标记)
|
||||
if "section-overview" not in resp.text:
|
||||
# SPA 模式:直接读取旧版 admin.html 文件
|
||||
from pathlib import Path
|
||||
|
||||
admin_html_path = Path(__file__).resolve().parent.parent / "app" / "static" / "admin.html"
|
||||
return admin_html_path.read_text(encoding="utf-8")
|
||||
return resp.text
|
||||
|
||||
|
||||
@@ -86,6 +99,8 @@ def test_admin_page_fetch_paths(admin_html: str) -> None:
|
||||
"/api/v1/auth/logout",
|
||||
"/api/v1/auth/password",
|
||||
"/api/v1/auth/users",
|
||||
"/api/v1/documents/upload-batch",
|
||||
"/api/v1/documents/tasks",
|
||||
):
|
||||
assert path in admin_html
|
||||
|
||||
@@ -107,6 +122,13 @@ def test_admin_page_fetch_paths_in_real_routes(admin_html: str) -> None:
|
||||
"""页面 api() 调用路径均在真实后端路由集合内(含 tasks 与 /api/v1/auth/* 路径)"""
|
||||
route_paths = _collect_route_paths(app.routes)
|
||||
assert "/api/v1/documents/tasks/{task_id}" in route_paths
|
||||
# Task 2 新增端点在真实路由集合内
|
||||
for new_path in (
|
||||
"/api/v1/documents/tasks",
|
||||
"/api/v1/documents/upload-batch",
|
||||
"/api/v1/documents/{doc_id}/reingest",
|
||||
):
|
||||
assert new_path in route_paths, f"后端缺少 Task 2 新增端点: {new_path}"
|
||||
for auth_path in (
|
||||
"/api/v1/auth/login",
|
||||
"/api/v1/auth/me",
|
||||
@@ -140,6 +162,99 @@ def test_admin_page_ingest_polling(admin_html: str) -> None:
|
||||
assert "disabled" in admin_html
|
||||
|
||||
|
||||
def test_admin_page_progress_section(admin_html: str) -> None:
|
||||
"""入库进度区块:导航按钮、section、表格结构、刷新按钮、空列表占位"""
|
||||
# section 与导航按钮(所有登录用户可见)
|
||||
assert 'id="section-progress"' in admin_html
|
||||
assert 'id="nav-progress"' in admin_html
|
||||
assert 'data-target="section-progress"' in admin_html
|
||||
assert "入库进度" in admin_html
|
||||
# 刷新按钮与状态统计
|
||||
assert 'id="btn-refresh-progress"' in admin_html
|
||||
assert 'id="progress-stats"' in admin_html
|
||||
# 表格 tbody 与表头列:文件名 / 状态 / 创建时间 / 更新时间 / 操作
|
||||
assert 'id="progress-tbody"' in admin_html
|
||||
for col in ("文件名", "状态", "创建时间", "更新时间", "操作"):
|
||||
assert col in admin_html
|
||||
# 空列表占位文案
|
||||
assert "暂无入库任务" in admin_html
|
||||
|
||||
|
||||
def test_admin_page_progress_polling(admin_html: str) -> None:
|
||||
"""入库进度区块:2s 轮询常量、startProgressPolling/stopProgressPolling 函数、GET /documents/tasks 路径"""
|
||||
# 进度区块专用轮询常量(2s)
|
||||
assert "PROGRESS_POLL_INTERVAL_MS" in admin_html
|
||||
assert "2000" in admin_html
|
||||
# 轮询启停函数
|
||||
assert "function startProgressPolling" in admin_html
|
||||
assert "function stopProgressPolling" in admin_html
|
||||
assert "progressPollTimer" in admin_html
|
||||
# 数据来源:GET /documents/tasks?limit=50
|
||||
assert "/api/v1/documents/tasks?limit=50" in admin_html
|
||||
# loadProgress / renderProgress / buildProgressRow 函数
|
||||
assert "function loadProgress" in admin_html
|
||||
assert "function renderProgress" in admin_html
|
||||
assert "function buildProgressRow" in admin_html
|
||||
|
||||
|
||||
def test_admin_page_progress_polling_lifecycle(admin_html: str) -> None:
|
||||
"""进度区块轮询生命周期:activateSection 切走暂停、切回恢复;showLogin 停止"""
|
||||
# activateSection 开头调用 stopProgressPolling(切走即暂停)
|
||||
assert "stopProgressPolling();" in admin_html
|
||||
# 切到 section-progress 时恢复轮询
|
||||
assert 'targetId === "section-progress"' in admin_html
|
||||
assert 'startProgressPolling()' in admin_html
|
||||
# showLogin 退出时停止轮询
|
||||
assert "stopProgressPolling" in admin_html
|
||||
|
||||
|
||||
def test_admin_page_progress_status_badge_reuse(admin_html: str) -> None:
|
||||
"""进度表格状态徽章复用 INGEST_STATUS_TEXT 中文映射与 status-* 配色"""
|
||||
# statusBadgeEl 共享构造函数(无 id 可重复使用)
|
||||
assert "function statusBadgeEl" in admin_html
|
||||
# makeStatusBadge 复用 statusBadgeEl + 加 id
|
||||
assert "statusBadgeEl(status)" in admin_html
|
||||
# 状态徽章 CSS 类复用
|
||||
for cls in ("status-running", "status-done", "status-failed"):
|
||||
assert cls in admin_html
|
||||
|
||||
|
||||
def test_admin_page_progress_actions(admin_html: str) -> None:
|
||||
"""进度表格操作列:done 查看文档+重新入库、failed 重试、进行中无操作;reingest 端点"""
|
||||
# done 状态操作按钮
|
||||
assert "查看文档" in admin_html
|
||||
assert "重新入库" in admin_html
|
||||
# failed 状态重试按钮
|
||||
assert "重试" in admin_html
|
||||
# reingestDocument 函数与 POST /documents/{doc_id}/reingest 端点
|
||||
assert "function reingestDocument" in admin_html
|
||||
assert "/reingest" in admin_html
|
||||
# 查看文档:切到 section-docs 并调用 loadDocDetail
|
||||
assert 'activateSection("section-docs")' in admin_html
|
||||
assert "loadDocDetail" in admin_html
|
||||
# 状态统计文案
|
||||
assert "进行中" in admin_html
|
||||
|
||||
|
||||
def test_admin_page_batch_upload(admin_html: str) -> None:
|
||||
"""批量上传:input multiple 属性、upload-batch 端点、批量结果展示、自动切进度区块"""
|
||||
# file input multiple 属性
|
||||
assert 'id="upload-file"' in admin_html
|
||||
assert "multiple" in admin_html
|
||||
# 批量上传分支:files > 1 时走 upload-batch 端点
|
||||
assert "/api/v1/documents/upload-batch" in admin_html
|
||||
assert 'fileInput.files.length > 1' in admin_html
|
||||
# FormData 用 files 字段逐文件 append
|
||||
assert 'batchForm.append("files"' in admin_html
|
||||
# 批量结果展示函数
|
||||
assert "function renderBatchUploadResult" in admin_html
|
||||
assert "批量上传完成" in admin_html
|
||||
# 成功后自动切到入库进度区块
|
||||
assert 'activateSection("section-progress")' in admin_html
|
||||
# 单文件上传仍走原 upload 端点(保持既有逻辑)
|
||||
assert "/api/v1/documents/upload\"" in admin_html or "/api/v1/documents/upload'," in admin_html
|
||||
|
||||
|
||||
def test_admin_page_no_prompt_confirm_calls(admin_html: str) -> None:
|
||||
"""清理:弹窗组件已替换原生 prompt/confirm,全页面无 prompt( 与 confirm( 调用残留"""
|
||||
assert "prompt(" not in admin_html
|
||||
@@ -490,15 +605,19 @@ def test_admin_page_api_guide_paths_in_real_routes(admin_html: str) -> None:
|
||||
assert (method, path) in route_methods, f"API_GUIDE 端点 {method} {path} 不在后端路由集合内"
|
||||
|
||||
# 全部真实端点覆盖:health/search/documents*/knowledge*/auth*
|
||||
# Task 2 新增端点:upload-batch / tasks 列表 / reingest
|
||||
expected = {
|
||||
("GET", "/api/v1/health"),
|
||||
("POST", "/api/v1/search"),
|
||||
("POST", "/api/v1/documents"),
|
||||
("POST", "/api/v1/documents/upload"),
|
||||
("POST", "/api/v1/documents/upload-batch"),
|
||||
("GET", "/api/v1/documents/tasks"),
|
||||
("GET", "/api/v1/documents/tasks/{task_id}"),
|
||||
("GET", "/api/v1/documents"),
|
||||
("GET", "/api/v1/documents/{doc_id}"),
|
||||
("DELETE", "/api/v1/documents/{doc_id}"),
|
||||
("POST", "/api/v1/documents/{doc_id}/reingest"),
|
||||
("GET", "/api/v1/knowledge/categories"),
|
||||
("GET", "/api/v1/knowledge/stats"),
|
||||
("POST", "/api/v1/auth/login"),
|
||||
|
||||
Reference in New Issue
Block a user