#include #include #include "secsgem/gem/control_state.hpp" using namespace secsgem::gem; namespace { struct Recorder { std::vector> changes; ControlStateMachine::StateChangeHandler handler() { return [this](ControlState from, ControlState to, ControlEvent ev) { changes.emplace_back(from, to, ev); }; } }; } // namespace TEST_CASE("default initial state is HostOffline") { ControlStateMachine sm; CHECK(sm.state() == ControlState::HostOffline); CHECK_FALSE(sm.online()); } TEST_CASE("custom initial state") { ControlStateMachine sm({.initial = ControlState::EquipmentOffline}); CHECK(sm.state() == ControlState::EquipmentOffline); } TEST_CASE("host request online from HostOffline -> OnlineRemote") { Recorder rec; ControlStateMachine sm; sm.set_state_change_handler(rec.handler()); CHECK(sm.on_host_request_online() == OnlineAck::Accept); CHECK(sm.state() == ControlState::OnlineRemote); CHECK(sm.online()); REQUIRE(rec.changes.size() == 2); CHECK(std::get<1>(rec.changes[0]) == ControlState::AttemptOnline); CHECK(std::get<2>(rec.changes[0]) == ControlEvent::HostRequestOnline); CHECK(std::get<1>(rec.changes[1]) == ControlState::OnlineRemote); CHECK(std::get<2>(rec.changes[1]) == ControlEvent::AttemptComplete); } TEST_CASE("host request online when already online -> AlreadyOnline, no transition") { ControlStateMachine sm; sm.on_host_request_online(); REQUIRE(sm.state() == ControlState::OnlineRemote); Recorder rec; sm.set_state_change_handler(rec.handler()); CHECK(sm.on_host_request_online() == OnlineAck::AlreadyOnline); CHECK(sm.state() == ControlState::OnlineRemote); CHECK(rec.changes.empty()); } TEST_CASE("host request online from EquipmentOffline -> NotAccept") { ControlStateMachine sm({.initial = ControlState::EquipmentOffline}); CHECK(sm.on_host_request_online() == OnlineAck::NotAccept); CHECK(sm.state() == ControlState::EquipmentOffline); } TEST_CASE("host request offline from OnlineRemote -> HostOffline") { ControlStateMachine sm; sm.on_host_request_online(); REQUIRE(sm.state() == ControlState::OnlineRemote); Recorder rec; sm.set_state_change_handler(rec.handler()); CHECK(sm.on_host_request_offline() == OfflineAck::Accept); CHECK(sm.state() == ControlState::HostOffline); REQUIRE(rec.changes.size() == 1); CHECK(std::get<1>(rec.changes[0]) == ControlState::HostOffline); CHECK(std::get<2>(rec.changes[0]) == ControlEvent::HostRequestOffline); } TEST_CASE("host request offline when already offline is idempotent Accept") { ControlStateMachine sm; Recorder rec; sm.set_state_change_handler(rec.handler()); CHECK(sm.on_host_request_offline() == OfflineAck::Accept); CHECK(sm.state() == ControlState::HostOffline); CHECK(rec.changes.empty()); } TEST_CASE("operator online from EquipmentOffline -> OnlineLocal by default") { ControlStateMachine sm({.initial = ControlState::EquipmentOffline}); CHECK(sm.operator_online()); CHECK(sm.state() == ControlState::OnlineLocal); } TEST_CASE("operator online with default_remote -> OnlineRemote") { ControlStateMachine sm({.initial = ControlState::HostOffline, .operator_default_remote = true}); CHECK(sm.operator_online()); CHECK(sm.state() == ControlState::OnlineRemote); } TEST_CASE("operator online when already online is rejected") { ControlStateMachine sm({.initial = ControlState::OnlineLocal}); CHECK_FALSE(sm.operator_online()); CHECK(sm.state() == ControlState::OnlineLocal); } TEST_CASE("operator offline from any online state -> HostOffline") { ControlStateMachine sm({.initial = ControlState::OnlineRemote}); CHECK(sm.operator_offline()); CHECK(sm.state() == ControlState::HostOffline); } TEST_CASE("operator local toggles only from OnlineRemote") { ControlStateMachine sm({.initial = ControlState::OnlineRemote}); CHECK(sm.operator_local()); CHECK(sm.state() == ControlState::OnlineLocal); CHECK_FALSE(sm.operator_local()); // already local } TEST_CASE("operator remote toggles only from OnlineLocal") { ControlStateMachine sm({.initial = ControlState::OnlineLocal}); CHECK(sm.operator_remote()); CHECK(sm.state() == ControlState::OnlineRemote); CHECK_FALSE(sm.operator_remote()); // already remote } TEST_CASE("is_online classifier") { CHECK_FALSE(is_online(ControlState::EquipmentOffline)); CHECK_FALSE(is_online(ControlState::AttemptOnline)); CHECK_FALSE(is_online(ControlState::HostOffline)); CHECK(is_online(ControlState::OnlineLocal)); CHECK(is_online(ControlState::OnlineRemote)); }