File size: 5,574 Bytes
3552405 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | """Tests for the Risk Scorer agent."""
import json
import pytest
from clauseguard.agents.risk_scorer import _parse_response
from clauseguard.models.findings import ScoredClause, Severity
def _make_mock_response(clauses_data: list) -> str:
"""Build a mock LLM JSON response string for testing."""
return json.dumps(clauses_data)
def test_ip_assignment_clause_is_critical() -> None:
"""Test that IP assignment of personal time/side projects is rated CRITICAL."""
mock_response = _make_mock_response([
{
"clause": {
"id": 1,
"raw_text": "Employee hereby assigns to Company all inventions and intellectual property created by Employee, whether during working hours or on Employee's own time, using Company equipment or Employee's personal equipment.",
"plain_english": None,
"clause_type": "IP_ASSIGNMENT",
"section_heading": "INTELLECTUAL PROPERTY",
"position": 1,
},
"finding": {
"clause_id": 1,
"severity": "CRITICAL",
"risk_title": "IP Assignment of Personal Work",
"risk_reason": "This clause claims ownership of all employee creations including those made on personal time and equipment with no carve-out for unrelated work.",
"recommended_action": "",
},
}
])
scored = _parse_response(mock_response)
assert len(scored) == 1
assert scored[0].finding.severity == Severity.CRITICAL
assert scored[0].clause.clause_type.value == "IP_ASSIGNMENT"
def test_governing_law_clause_is_low_or_info() -> None:
"""Test that a standard governing law clause is rated LOW or INFO."""
mock_response = _make_mock_response([
{
"clause": {
"id": 1,
"raw_text": "This Agreement shall be governed by and construed in accordance with the laws of the State of Delaware.",
"plain_english": None,
"clause_type": "GOVERNING_LAW",
"section_heading": "GOVERNING LAW",
"position": 1,
},
"finding": {
"clause_id": 1,
"severity": "LOW",
"risk_title": "Standard Governing Law",
"risk_reason": "Standard governing law clause selecting Delaware, a common jurisdiction.",
"recommended_action": "",
},
}
])
scored = _parse_response(mock_response)
assert len(scored) == 1
assert scored[0].finding.severity in (Severity.LOW, Severity.INFO)
def test_every_scored_clause_has_non_empty_risk_reason() -> None:
"""Test that every ScoredClause has a non-empty risk_reason."""
mock_response = _make_mock_response([
{
"clause": {
"id": 1,
"raw_text": "For two years, Employee shall not compete with Company anywhere in the United States.",
"plain_english": None,
"clause_type": "NON_COMPETE",
"section_heading": "NON-COMPETE",
"position": 1,
},
"finding": {
"clause_id": 1,
"severity": "CRITICAL",
"risk_title": "Overly Broad Non-Compete",
"risk_reason": "Non-compete duration of 2 years covers the entire US with no geographic relevance to Company's actual operations.",
"recommended_action": "",
},
},
{
"clause": {
"id": 2,
"raw_text": "Notice shall be sent to the address listed above.",
"plain_english": None,
"clause_type": "OTHER",
"section_heading": "NOTICES",
"position": 2,
},
"finding": {
"clause_id": 2,
"severity": "INFO",
"risk_title": "Standard Notice Provision",
"risk_reason": "Boilerplate notice provision with no unusual terms.",
"recommended_action": "",
},
},
])
scored = _parse_response(mock_response)
assert len(scored) == 2
for sc in scored:
assert sc.finding.risk_reason, f"Clause {sc.clause.id} has empty risk_reason"
assert len(sc.finding.risk_reason) > 5
def test_multiple_severity_levels() -> None:
"""Test that different severities are correctly parsed."""
mock_response = _make_mock_response([
{
"clause": {
"id": i,
"raw_text": f"Test clause {i}",
"plain_english": None,
"clause_type": "OTHER",
"section_heading": None,
"position": i,
},
"finding": {
"clause_id": i,
"severity": sev.value,
"risk_title": f"Risk {i}",
"risk_reason": f"Reason for clause {i}",
"recommended_action": "",
},
}
for i, sev in enumerate(
[Severity.CRITICAL, Severity.HIGH, Severity.MEDIUM, Severity.LOW, Severity.INFO], 1
)
])
scored = _parse_response(mock_response)
assert len(scored) == 5
severities = [sc.finding.severity for sc in scored]
assert Severity.CRITICAL in severities
assert Severity.HIGH in severities
assert Severity.MEDIUM in severities
assert Severity.LOW in severities
assert Severity.INFO in severities
|