from __future__ import annotations import pytest from legal_mcp.services import corroboration as cor @pytest.mark.parametrize("raw,expected", [ ({"treatment": "followed"}, "followed"), ({"treatment": "OVERRULED"}, "overruled"), # case-insensitive ({"treatment": "bananas"}, "mentioned"), # unknown -> neutral default ({}, "mentioned"), # missing -> neutral default ]) def test_coerce_treatment(raw, expected): assert cor._coerce_treatment(raw) == expected def test_treatment_polarity(): assert cor.is_positive("followed") and cor.is_positive("explained") assert cor.is_negative("distinguished") and cor.is_negative("overruled") assert not cor.is_positive("mentioned") and not cor.is_negative("mentioned") def test_match_accepts_above_threshold(): assert cor.accept_match(("h1", 0.62), floor=0.50) == "h1" def test_match_rejects_below_threshold(): assert cor.accept_match(("h1", 0.41), floor=0.50) is None def test_match_rejects_empty(): assert cor.accept_match(None, floor=0.50) is None def _link(src, treatment): return {"source_id": src, "treatment": treatment} def test_aggregate_counts_distinct_positive(): links = [_link("d1","followed"), _link("d1","explained"), _link("d2","followed")] agg = cor.aggregate(links, min_cites=2) assert agg["positive_sources"] == 2 # d1 counted once (INV-COR4 independence) assert agg["has_negative"] is False assert agg["corroborated"] is True def test_aggregate_negative_blocks(): links = [_link("d1","followed"), _link("d2","followed"), _link("d3","distinguished")] agg = cor.aggregate(links, min_cites=2) assert agg["has_negative"] is True assert agg["corroborated"] is False # any negative -> not corroborated (INV-COR2) def test_aggregate_below_threshold(): agg = cor.aggregate([_link("d1","followed")], min_cites=2) assert agg["corroborated"] is False # single source insufficient (INV-COR4) # --- Phase 2: approval decision (INV-COR2/COR4) --- def test_approval_action_corroborated_approves(): agg = {"positive_sources": 2, "has_negative": False, "corroborated": True} assert cor.approval_action(agg, has_overruled=False) == "approve" def test_approval_action_overruled_demotes_even_if_corroborated(): # overruled outranks any positive count (INV-COR2 strong form) agg = {"positive_sources": 3, "has_negative": True, "corroborated": False} assert cor.approval_action(agg, has_overruled=True) == "demote" def test_approval_action_single_source_noop(): agg = {"positive_sources": 1, "has_negative": False, "corroborated": False} assert cor.approval_action(agg, has_overruled=False) is None def test_approval_action_negative_nonoverruled_noop(): # distinguished blocks approval but does not demote (no overruled) agg = {"positive_sources": 2, "has_negative": True, "corroborated": False} assert cor.approval_action(agg, has_overruled=False) is None