"""
T-04 · 合同规则引擎单元测试(投稿整改补强,SUBMISSION_HARDENING P1-01)
覆盖:
✓ 后端规则总数固定为 22(5 类:5+4+5+4+4)
✓ CONTRACT_TYPES 6 种合同类型映射
✓ 高风险文本检测
✓ 无风险文本 → 空结果
✓ 空文本 / 超长文本边界(不崩)
运行:
python -m pytest tests/test_contract_rules.py -v
"""
from __future__ import annotations
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.contract_rules import (
ALL_RULES, CONTRACT_TYPES, analyze_contract, scan_contract_risks,
)
class TestContractRulesCount(unittest.TestCase):
"""规则数量与分类锁死(FORBIDDEN_CHANGES §13)。"""
def test_total_rules_is_22(self):
"""后端规则总数固定 22 条。"""
total = sum(len(rules) for _, rules in ALL_RULES)
self.assertEqual(total, 22, f"期望 22 条,实际 {total}")
def test_categories_is_5(self):
"""5 类风险。"""
self.assertEqual(len(ALL_RULES), 5)
def test_category_distribution(self):
"""5+4+5+4+4 分布(收入确认 5 / 售后回购 4 / 关联方 5 / 担保 4 / 租赁 4)。"""
dist = {name: len(rules) for name, rules in ALL_RULES}
self.assertEqual(dist, {
"收入确认风险": 5,
"售后回购/退货风险": 4,
"关联方/资金占用风险": 5,
"担保/或有负债风险": 4,
"租赁识别风险": 4,
})
def test_contract_types_six(self):
"""6 种合同类型映射不可删(sales/purchase/lease/guarantee/related_party/mixed)。"""
expected = {"sales", "purchase", "lease", "guarantee", "related_party", "mixed"}
self.assertEqual(set(CONTRACT_TYPES.keys()), expected)
class TestContractRiskDetection(unittest.TestCase):
"""风险文本检测。"""
def test_high_risk_text_detected(self):
"""含关联方资金占用 + 担保的高风险文本应被识别。"""
text = (
"甲方将向关联方提供 5000 万元借款,并由母公司提供连带责任担保。"
"若关联方未按期偿还,乙方有权处置抵押物。本合同含售后回购条款。"
)
risks = scan_contract_risks(text)
self.assertGreater(len(risks), 0, "高风险文本未检出任何风险")
def test_no_risk_text_empty(self):
"""无风险文本 → 空结果。"""
text = "本合同为普通商品买卖,甲方按约定时间交付货物,乙方按约定价格付款。"
risks = scan_contract_risks(text)
high_risks = [r for r in risks if getattr(r, "level", "") == "高风险"]
self.assertEqual(len(high_risks), 0, f"无风险文本检出高风险: {high_risks}")
def test_empty_text_no_crash(self):
"""空文本不崩。"""
risks = scan_contract_risks("")
self.assertEqual(len(risks), 0)
def test_long_text_no_crash(self):
"""超长文本(10000 字)不崩。"""
text = "关联方资金占用。" * 2000
risks = scan_contract_risks(text)
self.assertIsNotNone(risks)
def test_analyze_contract_returns_result(self):
"""analyze_contract 返回完整结构(risks + overall_level + contract_type)。"""
text = "甲方为关联方提供担保,含售后回购条款。"
result = analyze_contract(text)
self.assertIsNotNone(result)
from dataclasses import asdict
d = asdict(result)
self.assertIn("risks", d)
self.assertIn("overall_level", d)
self.assertIn("contract_type", d)
if __name__ == "__main__":
unittest.main(verbosity=2)