ClauseGuard-AI / clauseguard /tests /test_risk_scorer.py
muhammadbinmurtza
Restructure: clauseguard as package subfolder, app_file: clauseguard/app.py
913a064
"""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