Files
ai-contract-platform/backend/tests/test_clauses_api.py
T

210 lines
9.2 KiB
Python

import unittest
from fastapi.testclient import TestClient
from app.api.clauses import get_audit_log_service, get_clause_recommendation_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 StubClauseRecommendationService:
def __init__(self):
self.calls = []
def recommend_missing_clauses(self, current_user, **kwargs):
self.calls.append((current_user, kwargs))
return {
"contract_id": kwargs.get("contract_id"),
"recommendation_run_id": "clause-rec-run-test",
"document_type_code": kwargs.get("document_type_code") or "procurement_goods",
"contract_type": kwargs.get("contract_type"),
"items": [
{
"id": "clause-procurement_goods-acceptance",
"recommendation_record_id": "clrec-test-001",
"document_type_code": "procurement_goods",
"clause_key": "acceptance",
"clause_title": "验收标准与流程",
"clause_type": "acceptance",
"recommendation_kind": "missing_clause",
"basis_type": "legal_basis",
"industry_context": None,
"priority": "high",
"confidence": "high",
"insert_position": {
"strategy": "after_anchor_clause",
"anchor_text": "二、交付安排",
"description": "建议插入在交付条款之后。",
},
"suggested_text": "验收标准与流程:甲方应在收到货物后完成验收。",
"reason": "买卖合同需要把交付和验收衔接起来。",
"legal_basis": "《民法典》合同履行规则。",
"practice_basis": None,
"legal_article_refs": [],
"source_rule": "LAW-CLAUSE-PROCUREMENT-ACCEPTANCE",
"source_types": ["document_type_required_clause_list", "standard_clause_library"],
"detected_keywords": [],
}
],
"total": 1,
"summary": {
"recommended_count": 1,
"recommendation_keys": ["acceptance"],
"detected_clause_keys": [],
"missing_clause_keys": ["acceptance"],
"required_missing_clause_keys": ["acceptance"],
"industry_practice_keys": [],
"detected_industry_practice_keys": [],
"industry_practice_count": 0,
"source_count": 0,
"strategy": "rule_based_required_clause_gap_and_industry_practice",
"funnel_recorded_count": 1,
},
"sources": ["document_type_required_clause_list"],
}
def update_recommendation_action(self, current_user, recommendation_id, **kwargs):
self.calls.append((current_user, {"recommendation_id": recommendation_id, **kwargs}))
return {
"id": recommendation_id,
"recommendation_run_id": "clause-rec-run-test",
"contract_id": "contract-001",
"document_type_code": "procurement_goods",
"contract_type": None,
"clause_key": "acceptance",
"clause_title": "验收标准与流程",
"recommendation_kind": "missing_clause",
"priority": "high",
"action_status": kwargs.get("action_status"),
"action_note": kwargs.get("action_note"),
"applied_contract_version_id": kwargs.get("applied_contract_version_id"),
"action_detail": kwargs.get("action_detail"),
"created_at": None,
"updated_at": None,
"acted_at": None,
}
def rewrite_clause_wording(self, current_user, **kwargs):
self.calls.append((current_user, kwargs))
return {
"contract_id": kwargs.get("contract_id"),
"clause_id": kwargs.get("clause_id"),
"document_type_code": kwargs.get("document_type_code") or "procurement_goods",
"contract_type": kwargs.get("contract_type"),
"rewrite_type": kwargs.get("rewrite_type") or "professional",
"rewrite_type_label": "专业化",
"original_text": kwargs.get("clause_text"),
"rewritten_text": "乙方应在合同签订后若干工作日内完成交付。",
"changes": [
{
"from": "尽快交付",
"to": "若干工作日内完成交付",
"reason": "明确交付期限。",
}
],
"legal_boundary": "不能用模糊表述替代核心期限。",
"negotiation_advice": "建议把若干工作日改成具体数字。",
"risk_notes": ["请补齐具体期限。"],
"legal_article_refs": [],
"confidence": "high",
"source_types": ["wording_rewrite_rules"],
"summary": {
"changed": True,
"change_count": 1,
"risk_note_count": 1,
"source_count": 0,
"strategy": "rule_based_clause_wording_rewrite",
},
}
class ClausesApiTestCase(unittest.TestCase):
def setUp(self):
self.audit_log_service = StubAuditLogService()
self.clause_service = StubClauseRecommendationService()
app.dependency_overrides[get_clause_recommendation_service] = lambda: self.clause_service
app.dependency_overrides[get_audit_log_service] = lambda: self.audit_log_service
app.dependency_overrides[require_current_user] = lambda: {
"id": "user-001",
"tenant_id": "tenant-001",
"role": "admin",
}
self.client = TestClient(app)
def tearDown(self):
app.dependency_overrides.clear()
def test_recommend_missing_clauses_returns_items_and_records_audit(self):
response = self.client.post(
"/api/v1/clauses/recommend",
json={
"contract_id": "contract-001",
"document_type_code": "procurement_goods",
"contract_text": "采购合同正文,包含交付和付款,但缺少验收条款。",
"max_items": 5,
},
)
self.assertEqual(response.status_code, 200)
body = response.json()
self.assertEqual(body["code"], 0)
self.assertEqual(body["data"]["total"], 1)
self.assertEqual(body["data"]["items"][0]["clause_key"], "acceptance")
self.assertEqual(self.clause_service.calls[0][1]["contract_id"], "contract-001")
self.assertEqual(self.audit_log_service.events[0]["action"], "recommend_missing_clauses")
self.assertEqual(self.audit_log_service.events[0]["detail_json"]["missing_clause_keys"], ["acceptance"])
self.assertEqual(self.audit_log_service.events[0]["detail_json"]["industry_practice_count"], 0)
def test_rewrite_clause_wording_returns_result_and_records_audit(self):
response = self.client.post(
"/api/v1/clauses/rewrite",
json={
"contract_id": "contract-001",
"clause_id": "clause-001",
"document_type_code": "procurement_goods",
"clause_text": "乙方应尽快交付。",
"rewrite_type": "professional",
},
)
self.assertEqual(response.status_code, 200)
body = response.json()
self.assertEqual(body["code"], 0)
self.assertIn("若干工作日", body["data"]["rewritten_text"])
self.assertEqual(self.clause_service.calls[0][1]["clause_id"], "clause-001")
self.assertEqual(self.audit_log_service.events[0]["action"], "rewrite_clause_wording")
self.assertEqual(self.audit_log_service.events[0]["detail_json"]["rewrite_type"], "professional")
def test_record_clause_recommendation_action_updates_status_and_records_audit(self):
response = self.client.post(
"/api/v1/clauses/recommendations/clrec-test-001/action",
json={
"action_status": "applied",
"action_note": "插入正文并生成版本",
"applied_contract_version_id": "gcv-001",
"action_detail": {"clause_key": "acceptance"},
},
)
self.assertEqual(response.status_code, 200)
body = response.json()
self.assertEqual(body["code"], 0)
self.assertEqual(body["data"]["action_status"], "applied")
self.assertEqual(body["data"]["applied_contract_version_id"], "gcv-001")
self.assertEqual(self.clause_service.calls[0][1]["recommendation_id"], "clrec-test-001")
self.assertEqual(self.clause_service.calls[0][1]["action_status"], "applied")
self.assertEqual(self.audit_log_service.events[0]["action"], "record_clause_recommendation_action")
self.assertEqual(self.audit_log_service.events[0]["detail_json"]["clause_key"], "acceptance")
if __name__ == "__main__":
unittest.main()