210 lines
9.2 KiB
Python
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()
|