"""前端审计修复验证(docs/frontend_audit_plan.md §7.2/§7.3)。 覆盖: - 预览契约返回 HTML(非 JSON) - 进度消息持久化 role='progress'(重载会话可见) - 错误消息持久化 role='error' - /chat_state.js 静态路由可用 """ from __future__ import annotations import pytest from pathlib import Path from fastapi.testclient import TestClient from genesis.chat.agent import ChatAgent from genesis.server.app import create_app from genesis.server.service import GenesisService from genesis.server.store import ProjectsStore, SessionStore _SAMPLE = Path(__file__).resolve().parents[1] / "sample" @pytest.fixture def client(tmp_path): app = create_app( store=SessionStore(db_path=str(tmp_path / "s.db")), data_root=str(tmp_path / "data"), engine="fake", ) return TestClient(app) def _upload_core(client, sid): files = [ ("requirements", "requirements_newdev.xlsx", _SAMPLE / "requirements_newdev.xlsx"), ("template", "template_design_ja.docx", _SAMPLE / "template_design_ja.docx"), ("write_instruction", "rules_design_ja.docx", _SAMPLE / "rules_design_ja.docx"), ("rules", "rules_entry_ja.docx", _SAMPLE / "rules_entry_ja.docx"), ] for ft, name, path in files: r = client.post(f"/api/sessions/{sid}/files", data={"file_type": ft}, files={"file": (name, path.read_bytes())}) assert r.status_code == 200, r.text def test_result_preview_returns_html(client): """预览链接应返回渲染后的 HTML,而非 {'html': ...} JSON(§4.3)。""" sid = client.post("/api/sessions", json={"user_id": "u1"}).json()["session_id"] _upload_core(client, sid) client.post(f"/api/chat/{sid}/messages", json={"content": "请生成概要设计书"}) r = client.get(f"/api/sessions/{sid}/result/preview") assert r.status_code == 200 assert "text/html" in r.headers["content-type"] assert r.text.lstrip().lower().startswith("") def test_result_preview_missing_returns_404(client): """无结果文档时预览返回 404(RESULT_NOT_FOUND)。""" sid = client.post("/api/sessions", json={"user_id": "u1"}).json()["session_id"] r = client.get(f"/api/sessions/{sid}/result/preview") assert r.status_code == 404 assert r.json()["detail"]["code"] == "RESULT_NOT_FOUND" def test_progress_messages_persisted(client): """生成全流程的进度条目应以 role='progress' 入库,重载会话可见(§4.6)。""" sid = client.post("/api/sessions", json={"user_id": "u1"}).json()["session_id"] _upload_core(client, sid) client.post(f"/api/chat/{sid}/messages", json={"content": "请生成概要设计书"}) msgs = client.get(f"/api/chat/{sid}/messages").json() assert any(m["role"] == "progress" for m in msgs) def test_error_role_persisted(tmp_path): """错误回复以 role='error' 入库(§4.6 后端 _store_error)。""" store = SessionStore(db_path=str(tmp_path / "s.db")) projects = ProjectsStore(db_path=str(tmp_path / "s.db")) service = GenesisService(store=store, data_root=str(tmp_path / "data"), engine="fake", projects=projects) agent = ChatAgent(service=service, fake=True, engine="fake") sid = service.create_session("u1").session_id agent._store_error(sid, "解析失败:boom", "generate") msgs = service.store.list_messages(sid) assert any(m["role"] == "error" for m in msgs) def test_chat_state_js_route(client): """/chat_state.js 静态路由返回 JS(前端模块可加载,§4.5)。""" r = client.get("/chat_state.js") assert r.status_code == 200 assert "application/javascript" in r.headers["content-type"] assert "GenesisState" in r.text or "autoBindProject" in r.text