"""The alert state machine, one sample at a time (core/alerts.next_step).""" import pytest from steward.core.alerts import Step, next_step from steward.models.alerts import AlertStateEnum as S # (state, fail, ok, breached, fail_required, ok_required) -> Step CASES = [ # inactive ((S.inactive, 0, 0, False, 1, 1), Step(S.inactive, 0, 0)), ((S.inactive, 0, 0, True, 1, 1), Step(S.firing, 1, 0, "firing")), ((S.inactive, 0, 0, True, 3, 1), Step(S.pending, 1, 0)), # pending ((S.pending, 1, 0, True, 3, 1), Step(S.pending, 2, 0)), ((S.pending, 2, 0, True, 3, 1), Step(S.firing, 3, 0, "firing")), ((S.pending, 2, 0, False, 3, 1), Step(S.inactive, 0, 0)), # firing ((S.firing, 1, 0, True, 1, 1), Step(S.firing, 2, 0)), ((S.firing, 1, 0, False, 1, 1), Step(S.inactive, 0, 0, "resolved")), ((S.firing, 1, 0, False, 1, 3), Step(S.firing, 1, 1)), ((S.firing, 1, 2, False, 1, 3), Step(S.inactive, 0, 0, "resolved")), ((S.firing, 1, 2, True, 1, 3), Step(S.firing, 2, 0)), # a breach resets the OK run # acknowledged: holds while breached, however long ((S.acknowledged, 1, 0, True, 1, 1), Step(S.acknowledged, 2, 0)), ((S.acknowledged, 50, 0, True, 2, 1), Step(S.acknowledged, 51, 0)), ((S.acknowledged, 5, 0, False, 1, 1), Step(S.inactive, 0, 0, "resolved")), ((S.acknowledged, 5, 0, False, 1, 2), Step(S.acknowledged, 5, 1)), ((S.acknowledged, 5, 1, True, 1, 2), Step(S.acknowledged, 6, 0)), ((S.acknowledged, 5, 1, False, 1, 2), Step(S.inactive, 0, 0, "resolved")), # the transient resolved state reads as inactive ((S.resolved, 0, 0, True, 2, 1), Step(S.pending, 1, 0)), ((S.resolved, 0, 0, False, 1, 1), Step(S.inactive, 0, 0)), ] @pytest.mark.parametrize("args,want", CASES) def test_next_step(args, want): assert next_step(*args) == want def test_every_state_and_outcome_is_covered(): seen = {(a[0], a[3]) for a, _ in CASES} assert seen == {(s, b) for s in S for b in (True, False)} assert {w.event for _, w in CASES} == {None, "firing", "resolved"} def test_an_acknowledged_alert_never_fires_again_while_breached(): state, fail, ok = S.acknowledged, 1, 0 for _ in range(100): step = next_step(state, fail, ok, True, 1, 1) assert step.state == S.acknowledged and step.event is None state, fail, ok = step.state, step.fail_count, step.ok_count def test_requirements_below_one_count_as_one(): assert next_step(S.inactive, 0, 0, True, 0, 0) == Step(S.firing, 1, 0, "firing") assert next_step(S.firing, 1, 0, False, 0, 0) == Step(S.inactive, 0, 0, "resolved")