from __future__ import annotations import pytest from app.intelligence.leaver_risk import ( LEAVER_RISK_VERSION, LeaverRiskInput, aggregate_leaver_risk, build_leaver_risk, ) def test_empty_leaver_is_low_and_versioned() -> None: result = build_leaver_risk(LeaverRiskInput()) assert result.score == 0 and result.level == "low" assert result.blast_radius == 0 and result.risk_version == LEAVER_RISK_VERSION def test_missing_successor_is_critical_even_without_known_resources() -> None: result = build_leaver_risk(LeaverRiskInput(successor_available=False)) assert result.level == "critical" and result.score == 80 assert result.reasons == ("successor_missing",) def test_privileged_overdue_and_handover_facts_are_explainable() -> None: result = build_leaver_risk(LeaverRiskInput( resource_counts=(("permission", 2), ("cloud_scope", 1)), pending_count=2, overdue_count=1, missing_handover_count=1, privileged_count=1, data_handover_complete=False, blocked=True, )) assert result.level == "critical" and result.score == 80 assert result.blast_radius == 3 assert result.reasons == ( "data_handover_incomplete", "pending_items_overdue", "privileged_access_present", "ownership_handover_missing", "offboarding_blocked", ) def test_resources_are_sorted_for_stable_hashes() -> None: result = build_leaver_risk(LeaverRiskInput( resource_counts=(("seat", 3), ("account", 1)), )) assert result.resource_counts == (("account", 1), ("seat", 3)) def test_aggregate_is_anonymous_and_empty_safe() -> None: assert aggregate_leaver_risk(()) == { "leaver_count": 0, "critical_count": 0, "high_count": 0, "blocked_count": 0, "pending_count": 0, "overdue_count": 0, "blast_radius": 0, "missing_handover_count": 0, "privileged_count": 0, } totals = aggregate_leaver_risk(( LeaverRiskInput(resource_counts=(("asset", 2),), blocked=True), LeaverRiskInput(pending_count=1, overdue_count=1, successor_available=False), )) assert totals["leaver_count"] == 2 and totals["critical_count"] == 2 assert totals["blocked_count"] == 1 and totals["blast_radius"] == 2 @pytest.mark.parametrize("kwargs", [ {"resource_counts": (("unknown", 1),)}, {"resource_counts": (("asset", -1),)}, {"pending_count": -1}, {"pending_count": 1, "overdue_count": 2}, {"resource_counts": (("asset", 1),), "missing_handover_count": 2}, ]) def test_invalid_fact_contract_fails_closed(kwargs: dict[str, object]) -> None: with pytest.raises(ValueError): LeaverRiskInput(**kwargs)