Files
ai-contract-platform/backend/tests/test_governance_api.py
T
2026-05-02 22:06:03 +08:00

304 lines
12 KiB
Python

import unittest
from fastapi import HTTPException
from fastapi.testclient import TestClient
from app.api.governance import get_audit_log_service, get_governance_service, require_current_user
from app.main import app
class StubAuditLogService:
def __init__(self):
self.events = []
def record_event(self, **kwargs):
self.events.append(kwargs)
return kwargs
class StubGovernanceService:
def list_assets(self, current_user, page, page_size, asset_type, governance_status, review_status, production_status, assignment_role):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"items": [
{
"id": "govast-123",
"tenant_id": current_user["tenant_id"],
"asset_type": asset_type or "contract_template",
"asset_id": "TPL-SALES-GOODS-001",
"asset_name": "销售合同模板",
"source_table": "formal_launch_template_signoff",
"source_path": "project-docs/formal-launch-ops/signoff/production-template-signoff-register.csv",
"governance_status": governance_status or "reviewing",
"review_status": review_status or "pending",
"production_status": production_status or "blocked",
"latest_decision_result": None,
"latest_evidence_status": "template",
"metadata": {"contract_type": "sales"},
"notes": None,
"active_assignment_roles": [assignment_role or "legal_reviewer"],
"decision_count": 0,
"evidence_count": 1,
"created_at": "2026-05-02T00:00:00+00:00",
"updated_at": "2026-05-02T00:00:00+00:00",
}
],
"page": page,
"page_size": page_size,
"total": 1,
"summary": {
"total_assets": 1,
"governance_status": {"reviewing": 1},
"review_status": {"pending": 1},
"production_status": {"blocked": 1},
"pending_assignments": 1,
"in_review_assignments": 0,
"blocking_items": [{"type": "assignment", "status": "pending", "count": 1}],
},
}
def get_asset_detail(self, current_user, governance_asset_id):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"asset": {
"id": governance_asset_id,
"asset_name": "销售合同模板",
},
"assignments": [{"id": "govasg-123"}],
"decisions": [],
"evidences": [{"id": "govev-123"}],
}
def list_assignments(self, current_user, page, page_size, assignment_role, status_filter, assignee_user_id, governance_asset_id):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"items": [
{
"id": "govasg-123",
"tenant_id": current_user["tenant_id"],
"governance_asset_id": governance_asset_id or "govast-123",
"assignment_role": assignment_role or "legal_reviewer",
"assignee_user_id": assignee_user_id,
"assignee_name": "法务负责人A",
"due_at": "2026-05-03T00:00:00+00:00",
"status": status_filter or "pending",
"priority": "high",
"is_active": True,
"notes": None,
"created_at": "2026-05-02T00:00:00+00:00",
"updated_at": "2026-05-02T00:00:00+00:00",
"asset_name": "销售合同模板",
"asset_type": "contract_template",
}
],
"page": page,
"page_size": page_size,
"total": 1,
}
def save_assignment(self, current_user, governance_asset_id, assignment_role, assignee_user_id, assignee_name, due_at, status_value, priority, notes, assignment_id=None, is_active=True):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"id": assignment_id or "govasg-123",
"tenant_id": current_user["tenant_id"],
"governance_asset_id": governance_asset_id,
"assignment_role": assignment_role,
"assignee_user_id": assignee_user_id,
"assignee_name": assignee_name,
"due_at": due_at,
"status": status_value or "pending",
"priority": priority or "normal",
"is_active": is_active,
"notes": notes,
"created_at": "2026-05-02T00:00:00+00:00",
"updated_at": "2026-05-02T00:00:00+00:00",
"asset_name": "销售合同模板",
"asset_type": "contract_template",
}
def submit_decision(self, current_user, governance_asset_id, assignment_id, decision_role, decision_result, decision_summary, required_changes, decided_by_name, decision_at, metadata_json):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"decision": {
"id": "govdec-123",
"governance_asset_id": governance_asset_id,
"assignment_id": assignment_id,
"decision_role": decision_role,
"decision_result": decision_result,
},
"asset": {
"id": governance_asset_id,
"review_status": "approved" if decision_result == "approved" else "blocked",
},
}
def save_evidence(self, current_user, governance_asset_id, decision_id, file_id, evidence_type, storage_type, external_url, file_name, checksum, status_value, reviewer_name, collected_at, metadata_json):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"evidence": {
"id": "govev-123",
"governance_asset_id": governance_asset_id,
"decision_id": decision_id,
"file_id": file_id,
"storage_type": storage_type or "external",
"external_url": external_url,
"status": status_value or "ready",
},
"asset": {
"id": governance_asset_id,
"latest_evidence_status": status_value or "ready",
},
}
def get_release_summary(self, current_user):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"summary": {
"total_assets": 10,
"blocking_items": [{"type": "assignment", "status": "pending", "count": 4}],
},
"recent_batches": [
{
"id": "govrel-123",
"batch_name": "formal-launch-import",
"status": "blocked",
}
],
}
def get_release_detail(self, current_user, release_batch_id):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"release_batch": {"id": release_batch_id, "status": "blocked"},
"gate_runs": [{"id": "govgate-123", "gate_name": "formal_launch_import"}],
}
def import_formal_launch(self, current_user, batch_name=None):
if current_user["role"] != "admin":
raise HTTPException(status_code=403, detail="forbidden")
return {
"imported": {
"assets": 10,
"assignments": 12,
"decisions": 0,
"evidences": 10,
},
"summary": {"total_assets": 10},
"release_batch": {"id": "govrel-123", "batch_name": batch_name or "formal-launch-import", "status": "blocked"},
"gate_run": {"id": "govgate-123", "status": "blocked"},
}
class GovernanceApiTestCase(unittest.TestCase):
def setUp(self):
self.audit_log_service = StubAuditLogService()
app.dependency_overrides[get_governance_service] = lambda: StubGovernanceService()
app.dependency_overrides[get_audit_log_service] = lambda: self.audit_log_service
self.client = TestClient(app)
def tearDown(self):
app.dependency_overrides.clear()
def _set_admin(self):
app.dependency_overrides[require_current_user] = lambda: {
"id": "user-admin",
"tenant_id": "tenant-demo-001",
"role": "admin",
"display_name": "管理员",
}
def test_admin_can_list_governance_assets(self):
self._set_admin()
response = self.client.get("/api/v1/governance/assets", params={"asset_type": "contract_template"})
self.assertEqual(response.status_code, 200)
body = response.json()["data"]
self.assertEqual(body["items"][0]["asset_name"], "销售合同模板")
self.assertEqual(body["summary"]["total_assets"], 1)
def test_admin_can_list_governance_assignments(self):
self._set_admin()
response = self.client.get("/api/v1/governance/assignments", params={"assignment_role": "legal_reviewer"})
self.assertEqual(response.status_code, 200)
body = response.json()["data"]
self.assertEqual(body["items"][0]["assignment_role"], "legal_reviewer")
self.assertEqual(body["items"][0]["asset_name"], "销售合同模板")
def test_admin_can_save_assignment(self):
self._set_admin()
response = self.client.post(
"/api/v1/governance/assignments",
data={
"governance_asset_id": "govast-123",
"assignment_role": "legal_reviewer",
"assignee_name": "法务负责人A",
"status_value": "pending",
"priority": "high",
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["assignment_role"], "legal_reviewer")
self.assertEqual(self.audit_log_service.events[0]["action"], "save_governance_assignment")
def test_admin_can_submit_decision(self):
self._set_admin()
response = self.client.post(
"/api/v1/governance/decisions",
data={
"governance_asset_id": "govast-123",
"assignment_id": "govasg-123",
"decision_role": "legal_reviewer",
"decision_result": "approved",
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["decision"]["decision_result"], "approved")
self.assertEqual(self.audit_log_service.events[0]["action"], "submit_governance_decision")
def test_admin_can_save_evidence(self):
self._set_admin()
response = self.client.post(
"/api/v1/governance/evidences",
data={
"governance_asset_id": "govast-123",
"external_url": "https://example.com/evidence/1",
"status_value": "ready",
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["data"]["evidence"]["status"], "ready")
self.assertEqual(self.audit_log_service.events[0]["action"], "save_governance_evidence")
def test_admin_can_get_release_summary_and_import(self):
self._set_admin()
summary_response = self.client.get("/api/v1/governance/releases/summary")
import_response = self.client.post("/api/v1/governance/imports/formal-launch", data={"batch_name": "formal-launch-import"})
self.assertEqual(summary_response.status_code, 200)
self.assertEqual(summary_response.json()["data"]["recent_batches"][0]["status"], "blocked")
self.assertEqual(import_response.status_code, 200)
self.assertEqual(import_response.json()["data"]["release_batch"]["batch_name"], "formal-launch-import")
self.assertEqual(self.audit_log_service.events[-1]["action"], "import_formal_launch_governance")
if __name__ == "__main__":
unittest.main()