""" Unit tests for the SLA engine (app/utils/sla.py). These are pure-logic tests — they build a lightweight fake issue object with the three attributes `sla_status`/`sla_deadline`/`sla_hours_remaining` read (`severity`, `status`, `reported_at`) and assert the boundary behaviour of the SLA state machine. No database, no app context required. """ from datetime import timedelta from app.utils.sla import ( sla_status, sla_deadline, sla_hours_remaining, SLA_HOURS, AT_RISK_THRESHOLD, ) from app.utils.time_utils import now_eastern class FakeIssue: """Minimal stand-in for the Issue ORM object used by the SLA helpers.""" def __init__(self, severity, status='open', hours_ago=0.0): self.severity = severity self.status = status self.reported_at = now_eastern() - timedelta(hours=hours_ago) # ── sla_deadline ───────────────────────────────────────────────────────────── def test_deadline_matches_window(): issue = FakeIssue('high', hours_ago=0) expected = issue.reported_at + timedelta(hours=SLA_HOURS['high']) assert sla_deadline(issue) == expected def test_deadline_none_for_unknown_severity(): assert sla_deadline(FakeIssue('bogus')) is None # ── sla_status boundaries ──────────────────────────────────────────────────── def test_status_ok_when_fresh(): # critical window is 4h; reported 1h ago → well inside → ok assert sla_status(FakeIssue('critical', hours_ago=1)) == 'ok' def test_status_at_risk_past_threshold(): # critical window 4h, at-risk at 3h (0.75 * 4). Reported 3.5h ago → at_risk. assert AT_RISK_THRESHOLD == 0.75 assert sla_status(FakeIssue('critical', hours_ago=3.5)) == 'at_risk' def test_status_breached_past_window(): # critical window 4h; reported 5h ago → breached assert sla_status(FakeIssue('critical', hours_ago=5)) == 'breached' def test_status_none_when_resolved(): # A resolved issue has no SLA regardless of how old it is. assert sla_status(FakeIssue('critical', status='resolved', hours_ago=99)) is None def test_status_none_for_unknown_severity(): assert sla_status(FakeIssue('bogus', hours_ago=1)) is None def test_low_severity_window_is_120h_not_168h(): """Regression guard: CLAUDE.md once documented low=168h; code says 120h.""" assert SLA_HOURS['low'] == 120 # ── sla_hours_remaining ────────────────────────────────────────────────────── def test_hours_remaining_positive_when_fresh(): issue = FakeIssue('medium', hours_ago=0) # window 72h remaining = sla_hours_remaining(issue) assert 71.0 <= remaining <= 72.0 def test_hours_remaining_negative_when_breached(): issue = FakeIssue('critical', hours_ago=6) # window 4h → ~-2h assert sla_hours_remaining(issue) < 0 def test_hours_remaining_none_when_resolved(): assert sla_hours_remaining(FakeIssue('high', status='resolved')) is None