"""Tests for ``arautopilot.core.knob_state``.""" from __future__ import annotations from datetime import UTC import pytest from arautopilot.core.knob_state import ( KnobFunction, KnobMode, KnobState, ) def test_idle_state_defaults() -> None: s = KnobState.idle() assert s.mode is KnobMode.LIBRE assert s.function is KnobFunction.NONE assert s.pending_value is None assert s.armed_at is None assert s.timeout_remaining_s == 0.0 def test_libre_state_is_immutable() -> None: s = KnobState.idle() with pytest.raises((TypeError, ValueError)): s.mode = KnobMode.ARMADO # type: ignore[misc] def test_arm_transitions_to_armado() -> None: s = KnobState.idle().arm(KnobFunction.RUMBO, current_value=180.0) assert s.mode is KnobMode.ARMADO assert s.function is KnobFunction.RUMBO assert s.current_value == pytest.approx(180.0) assert s.armed_at is not None assert s.timeout_remaining_s > 0 def test_arming_with_none_function_rejected() -> None: with pytest.raises(ValueError): KnobState.idle().arm(KnobFunction.NONE, current_value=0.0) def test_arming_from_non_libre_rejected() -> None: armed = KnobState.idle().arm(KnobFunction.RUMBO, current_value=90.0) with pytest.raises(ValueError): armed.arm(KnobFunction.BRILLO, current_value=50.0) def test_propose_then_confirm_round_trip() -> None: armed = KnobState.idle().arm(KnobFunction.RUMBO, current_value=180.0) pending = armed.propose(185.0) assert pending.mode is KnobMode.CONFIRMANDO assert pending.pending_value == pytest.approx(185.0) assert pending.current_value == pytest.approx(180.0) confirmed = pending.confirm() assert confirmed.mode is KnobMode.ARMADO assert confirmed.pending_value is None assert confirmed.current_value == pytest.approx(185.0) def test_propose_from_libre_rejected() -> None: with pytest.raises(ValueError): KnobState.idle().propose(10.0) def test_confirm_without_pending_rejected() -> None: armed = KnobState.idle().arm(KnobFunction.RUMBO, current_value=180.0) with pytest.raises(ValueError): armed.confirm() def test_disarm_returns_to_libre() -> None: s = ( KnobState.idle() .arm(KnobFunction.RUMBO, current_value=180.0) .propose(190.0) .disarm() ) assert s == KnobState.idle() def test_libre_with_armed_at_set_invalid() -> None: from datetime import datetime with pytest.raises(ValueError): KnobState( mode=KnobMode.LIBRE, function=KnobFunction.NONE, armed_at=datetime.now(UTC), ) def test_armado_without_armed_at_invalid() -> None: with pytest.raises(ValueError): KnobState( mode=KnobMode.ARMADO, function=KnobFunction.RUMBO, current_value=180.0, armed_at=None, )