按 docs/v2/05-implementation-plan.md §1 候选迁移表与 claude_use/T00 白名单, 从 55e2cdb 抽取 224 个文件(2.48 MB)到独立目录 F:/mini-OpenClaw-v2。 - 排除:凭据(.env/server-deploy.conf/旧DELIVERY.md)、运行数据(xingbang_data 等 4 个 SQLite)、依赖(venv/node_modules 符号链接)、归档(_backup*/test-evidence/ ori_file)、Office 临时文件 - 目标目录独立 git 仓库,初始恢复点;Gitea 远程与分支待 OQ-06 Q2 定案 - 未部署、未启动服务、未调用模型 Co-Authored-By: Claude Code <noreply@anthropic.com>
89 lines
6.0 KiB
Python
89 lines
6.0 KiB
Python
import tempfile
|
|
import json
|
|
import unittest
|
|
from io import BytesIO
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from docx import Document
|
|
from api import risk_agent_config as risk
|
|
from api.report_agent import router
|
|
|
|
|
|
class ReportTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp=tempfile.TemporaryDirectory();self.dbpatch=patch.object(risk,"DB_PATH",Path(self.temp.name)/"test.sqlite");self.dbpatch.start()
|
|
self.env=patch.dict("os.environ",{"XINGBANG_AUTH_USERS":"","XINGBANG_REQUIRE_AUTH":"0","XINGBANG_LOCAL_MODEL_URL":""});self.env.start()
|
|
app=FastAPI();app.include_router(risk.router);app.include_router(router)
|
|
self.client=TestClient(app);self.base="/api/xingbang/risk-agent"
|
|
run=self.client.post(self.base+"/plans/plan-default/run",json={"project_id":"XB-2026-001"}).json()
|
|
self.doc=self.client.post(self.base+"/reports/documents",json={"run_id":run["id"],"confirmation":"已核对来源,现金流缺失待补充"}).json()
|
|
def tearDown(self):
|
|
self.client.close();self.dbpatch.stop();self.env.stop();self.temp.cleanup()
|
|
def action(self,kind,**extra):
|
|
response=self.client.post(self.base+f"/reports/documents/{self.doc['id']}/actions",json={"kind":kind,"version":self.doc["version"],**extra})
|
|
if response.status_code==200:self.doc=response.json()
|
|
return response
|
|
def finalize(self):
|
|
self.action("edit",section_id="2",text="负债较高,需核实新增借款用途。")
|
|
self.action("edit",section_id="3",text="补充现金流材料后复核。")
|
|
for i in range(4):self.action("lock",section_id=str(i),locked=True)
|
|
return self.action("finalize",note="逐项确认,保留未解决信息缺口")
|
|
def test_source_facts_cannot_be_overwritten(self):
|
|
self.assertEqual(self.action("edit",section_id="0",text="伪造金额").status_code,409)
|
|
self.assertIn("facility.amount: 5000",self.doc["sections"][0]["text"])
|
|
def test_locked_and_final_sections_immutable(self):
|
|
self.action("lock",section_id="2",locked=True)
|
|
self.assertEqual(self.action("edit",section_id="2",text="覆盖").status_code,409)
|
|
self.action("lock",section_id="2",locked=False)
|
|
self.assertEqual(self.finalize().status_code,200)
|
|
self.assertEqual(self.doc["experience"]["status"],"草稿")
|
|
self.assertEqual(self.action("lock",section_id="2",locked=False).status_code,409)
|
|
def test_finalize_requires_all_locks(self):
|
|
self.assertEqual(self.action("finalize",note="确认").status_code,409)
|
|
def test_experience_needs_outcome(self):
|
|
self.finalize()
|
|
for _ in range(2):self.assertEqual(self.action("experience",note="审核意见").status_code,200)
|
|
self.assertEqual(self.action("experience",note="批准共享").status_code,422)
|
|
self.assertEqual(self.action("experience",note="审核完成",approval_result="审批通过").status_code,200)
|
|
self.assertEqual(self.action("experience",note="验证").status_code,422)
|
|
self.assertEqual(self.action("experience",note="租后已核对",post_lease_result="连续12个月正常还款").status_code,200)
|
|
self.assertEqual(self.doc["experience"]["status"],"已验证")
|
|
def test_docx_is_valid_and_has_source(self):
|
|
response=self.client.get(self.base+f"/reports/documents/{self.doc['id']}/export")
|
|
self.assertEqual(response.status_code,200)
|
|
document=Document(BytesIO(response.content))
|
|
self.assertIn(self.doc["source_id"],"\n".join(p.text for p in document.paragraphs))
|
|
def test_model_candidate_not_overwrite(self):
|
|
before=self.doc["sections"][2]["text"]
|
|
with patch.object(risk,"local_model",return_value={"text":"建议核查现金流","engine":"mini-openclaw/AgentManager","messages":[]}):
|
|
response=self.client.post(self.base+f"/reports/documents/{self.doc['id']}/generate",json={"kind":"generate","version":self.doc["version"],"section_id":"2"})
|
|
self.assertEqual(response.status_code,200,response.text)
|
|
self.assertEqual(response.json()["sections"][2]["text"],before)
|
|
self.assertEqual(response.json()["proposals"][0]["text"],"建议核查现金流")
|
|
|
|
def test_manager_project_scope_and_read_only(self):
|
|
users=[{"id":"manager","token":"test-manager","role":"manager","projects":[]}]
|
|
with patch.dict("os.environ",{"XINGBANG_AUTH_USERS":json.dumps(users)}):
|
|
self.client.headers["authorization"]="Bearer test-manager"
|
|
self.assertEqual(self.client.get(self.base+"/reports/documents").json(),[])
|
|
self.assertEqual(self.client.get(self.base+f"/reports/documents/{self.doc['id']}/export").status_code,403)
|
|
self.assertEqual(self.action("edit",section_id="2",text="changed").status_code,403)
|
|
users[0]["projects"]=[self.doc["project_id"]]
|
|
with patch.dict("os.environ",{"XINGBANG_AUTH_USERS":json.dumps(users)}):
|
|
self.assertEqual(len(self.client.get(self.base+"/reports/documents").json()),1)
|
|
self.assertEqual(self.client.get(self.base+f"/reports/documents/{self.doc['id']}/export").status_code,200)
|
|
|
|
def test_shared_experience_requires_independent_reviewer(self):
|
|
users=[{"id":"reviewer-one","token":"one","role":"reviewer","projects":[self.doc["project_id"]]},{"id":"reviewer-two","token":"two","role":"reviewer","projects":[self.doc["project_id"]]}]
|
|
with patch.dict("os.environ",{"XINGBANG_AUTH_USERS":json.dumps(users)}):
|
|
self.client.headers["authorization"]="Bearer one"
|
|
self.assertEqual(self.finalize().status_code,200)
|
|
for _ in range(2):self.assertEqual(self.action("experience",note="审核意见").status_code,200)
|
|
self.assertEqual(self.action("experience",note="共享",approval_result="有条件通过").status_code,403)
|
|
self.client.headers["authorization"]="Bearer two"
|
|
self.assertEqual(self.action("experience",note="独立审核通过",approval_result="有条件通过").status_code,200)
|
|
|
|
if __name__=="__main__":unittest.main()
|