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