feat(web): 项目级配置 + 会话命名/历史 + 设计文档纳入影响调查

- 会话支持 name/project 字段,上传要件定义后自动命名;前端侧边栏会话历史 + localStorage 恢复,顶部只显示会话名
- 新增 ProjectsStore(SQLite)与 /api/projects CRUD;绑定项目后 _rebuild_source 合并模板/规则/代码库/设计文档,上传区仅要件定义
- StructuredSource.design_docs 与 ImpactReport.design_references;影响调查新增既有设计文档确定性交叉引用(无 LLM)
- 同步更新 docs/design.md §12.7、README、_AI_USAGE_LOG.md;全量测试 558 通过,覆盖率 99.10%
This commit is contained in:
lhl
2026-08-27 12:13:28 +08:00
parent 838a93720e
commit 916c5beed7
18 changed files with 1174 additions and 65 deletions
+68
View File
@@ -279,3 +279,71 @@ def test_real_mode_generate_without_key_500(tmp_path, monkeypatch):
client.post(f"/api/sessions/{sid}/confirm-parse")
r = client.post(f"/api/sessions/{sid}/generate", json={})
assert r.status_code == 500
# ---------- 项目配置(S6 ----------
def test_create_and_list_projects(client):
body = {
"name": "stock", "display_name": "股票系统",
"template": str(_SAMPLE / "template_design_ja.docx"),
"write_instruction": "", "rules": [],
"existing_system_code_dir": "", "design_docs_dir": "",
}
r = client.post("/api/projects", json=body)
assert r.status_code == 200
assert r.json()["name"] == "stock"
lst = client.get("/api/projects").json()
assert any(p["name"] == "stock" for p in lst)
one = client.get("/api/projects/stock").json()
assert one["template"] == str(_SAMPLE / "template_design_ja.docx")
def test_project_invalid_template_400(client):
r = client.post("/api/projects", json={"name": "p", "template": "/no/such.docx"})
assert r.status_code == 400
assert r.json()["detail"]["code"] == "PROJECT_CONFIG_INVALID"
def test_delete_project(client):
client.post("/api/projects", json={"name": "p", "template": str(_SAMPLE / "template_design_ja.docx")})
assert client.delete("/api/projects/p").json()["deleted"] is True
assert client.get("/api/projects/p").status_code == 404
def test_session_accepts_name_and_project(client):
r = client.post("/api/sessions", json={"user_id": "u1", "name": "我的会话", "project": "projA"})
assert r.status_code == 200
sid = r.json()["session_id"]
assert r.json()["name"] == "我的会话"
g = client.get(f"/api/sessions/{sid}").json()
assert g["name"] == "我的会话"
assert g["project"] == "projA"
def test_session_list_includes_name_project(client):
client.post("/api/sessions", json={"user_id": "u1", "name": "n1", "project": "pA"})
r = client.get("/api/sessions", params={"user_id": "u1"}).json()
assert any(s["name"] == "n1" and s["project"] == "pA" for s in r)
def test_generate_with_project_config_no_template_upload(client):
"""绑定项目(含模板/规则)后,仅上传要件定义即可解析生成。"""
client.post("/api/projects", json={
"name": "projA", "template": str(_SAMPLE / "template_design_ja.docx"),
"write_instruction": str(_SAMPLE / "rules_design_ja.docx"),
"rules": [str(_SAMPLE / "rules_entry_ja.docx")],
"existing_system_code_dir": "", "design_docs_dir": "",
})
sid = client.post("/api/sessions", json={"user_id": "u1", "project": "projA"}).json()["session_id"]
# 仅上传要件定义
r = client.post(f"/api/sessions/{sid}/files", data={"file_type": "requirements"},
files={"file": ("requirements_newdev.xlsx", (_SAMPLE / "requirements_newdev.xlsx").read_bytes())})
assert r.status_code == 200
p = client.post(f"/api/sessions/{sid}/start-parse")
assert p.status_code == 200, p.text
assert client.post(f"/api/sessions/{sid}/confirm-parse").status_code == 200
# 无既有系统 → 直接 writing,可生成
gen = client.post(f"/api/sessions/{sid}/generate", json={})
assert gen.status_code == 200, gen.text
assert client.get(f"/api/sessions/{sid}/result/download").status_code == 200