304 lines
12 KiB
Python
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()
|