Files
mini-openclaw-v2/backend/test_report_agent.py
T
qiaoqichaoandClaude Code d56c699ce6 chore(v2): 从兴邦预审v2白名单抽取独立可运行基座
按 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>
2026-09-29 12:58:26 +08:00

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()