85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""
|
|
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
|