#include #include "secsgem/gem/data_model.hpp" using namespace secsgem::gem; TEST_CASE("SVID add / get / list / set value") { EquipmentDataModel m; m.add_status_variable({1, "Clock", "", s2::Item::ascii("19700101000000")}); m.add_status_variable({2, "EventsEnabled", "", s2::Item::boolean(true)}); CHECK(m.status_variable(1).has_value()); CHECK(m.status_variable(99).has_value() == false); CHECK(m.all_status_variables().size() == 2); m.set_status_value(2, s2::Item::boolean(false)); CHECK(m.status_variable(2)->value == s2::Item::boolean(false)); } TEST_CASE("ECID set rejects unknown id") { EquipmentDataModel m; m.add_equipment_constant({10, "MaxSpool", "msgs", s2::Item::u4(uint32_t{100}), s2::Item::u4(uint32_t{100}), "0", "1000"}); CHECK(m.set_equipment_constant_value(10, s2::Item::u4(uint32_t{50})) == EquipmentAck::Accept); CHECK(m.equipment_constant(10)->value == s2::Item::u4(uint32_t{50})); CHECK(m.set_equipment_constant_value(999, s2::Item::u4(uint32_t{1})) == EquipmentAck::Denied_UnknownEcid); } TEST_CASE("clock string has 16 digit characters") { EquipmentDataModel m; auto s = m.current_time_string(); REQUIRE(s.size() == 16); for (char c : s) CHECK(c >= '0'); } TEST_CASE("set_time_string accepts well-formed and rejects malformed") { EquipmentDataModel m; CHECK(m.set_time_string("20260101000000") == TimeAck::Accept); CHECK(m.set_time_string("2026010100000000") == TimeAck::Accept); CHECK(m.set_time_string("not-a-date-12345") == TimeAck::Error); CHECK(m.set_time_string("short") == TimeAck::Error); } TEST_CASE("host command registry") { EquipmentDataModel m; m.register_command("START", [](const auto&) { return HostCmdAck::Accept; }); m.register_command("STOP", [](const auto&) { return HostCmdAck::CannotDoNow; }); CHECK(m.has_command("START")); CHECK_FALSE(m.has_command("PAUSE")); CHECK(m.dispatch_command("START", {}) == HostCmdAck::Accept); CHECK(m.dispatch_command("STOP", {}) == HostCmdAck::CannotDoNow); CHECK(m.dispatch_command("UNKNOWN", {}) == HostCmdAck::InvalidCommand); } // ---- Event reports ------------------------------------------------------- TEST_CASE("define report rejects unknown VID, accepts known VIDs") { EquipmentDataModel m; m.add_status_variable({1, "A", "", s2::Item::u4(uint32_t{10})}); m.add_status_variable({2, "B", "", s2::Item::u4(uint32_t{20})}); CHECK(m.define_reports({{100, {1, 2}}}) == DefineReportAck::Accept); CHECK(m.define_reports({{101, {1, 999}}}) == DefineReportAck::InvalidVid); } TEST_CASE("link / enable / compose: full event-report pipeline") { EquipmentDataModel m; m.add_status_variable({1, "ControlState", "", s2::Item::ascii("OnlineRemote")}); m.add_status_variable({2, "Clock", "", s2::Item::ascii("19700101000000")}); m.register_event({100, "ControlStateChanged"}); m.register_event({200, "AlarmSetEvent"}); REQUIRE(m.define_reports({{1000, {1}}, {1001, {1, 2}}}) == DefineReportAck::Accept); REQUIRE(m.link_event_reports({{100, {1000, 1001}}, {200, {1001}}}) == LinkEventAck::Accept); CHECK_FALSE(m.is_event_enabled(100)); REQUIRE(m.enable_events(true, {100}) == EnableEventAck::Accept); CHECK(m.is_event_enabled(100)); CHECK_FALSE(m.is_event_enabled(200)); auto reports = m.compose_reports_for(100); REQUIRE(reports.size() == 2); CHECK(reports[0].rptid == 1000); REQUIRE(reports[0].values.size() == 1); CHECK(reports[0].values[0] == s2::Item::ascii("OnlineRemote")); CHECK(reports[1].rptid == 1001); REQUIRE(reports[1].values.size() == 2); } TEST_CASE("enable_events with empty CEID list enables all registered events") { EquipmentDataModel m; m.register_event({1, "a"}); m.register_event({2, "b"}); CHECK(m.enable_events(true, {}) == EnableEventAck::Accept); CHECK(m.is_event_enabled(1)); CHECK(m.is_event_enabled(2)); CHECK(m.enable_events(false, {}) == EnableEventAck::Accept); CHECK_FALSE(m.is_event_enabled(1)); } TEST_CASE("link_event_reports rejects unknown CEID or RPTID") { EquipmentDataModel m; m.add_status_variable({1, "A", "", s2::Item::u4(uint32_t{0})}); m.register_event({100, "x"}); m.define_reports({{500, {1}}}); CHECK(m.link_event_reports({{999, {500}}}) == LinkEventAck::UnknownCeid); CHECK(m.link_event_reports({{100, {999}}}) == LinkEventAck::UnknownRptid); CHECK(m.link_event_reports({{100, {500}}}) == LinkEventAck::Accept); } TEST_CASE("define_reports empty list clears reports and links") { EquipmentDataModel m; m.add_status_variable({1, "A", "", s2::Item::u4(uint32_t{0})}); m.register_event({100, "x"}); m.define_reports({{500, {1}}}); m.link_event_reports({{100, {500}}}); CHECK(m.define_reports({}) == DefineReportAck::Accept); CHECK(m.all_reports().empty()); CHECK(m.compose_reports_for(100).empty()); } // ---- Alarms -------------------------------------------------------------- TEST_CASE("alarm enable / set / clear / list") { EquipmentDataModel m; m.add_alarm({1, "Chiller Temp High", 4}); m.add_alarm({2, "Door Open", 1}); CHECK(m.set_alarm_enabled(1, true) == AlarmAck::Accept); CHECK(m.alarm_enabled(1)); CHECK(m.set_alarm_enabled(999, true) == AlarmAck::Error); auto alcd_set = m.alarm_set(1); REQUIRE(alcd_set.has_value()); CHECK((*alcd_set & 0x80) != 0); CHECK((*alcd_set & 0x7F) == 4); CHECK(m.alarm_active(1)); auto alcd_clr = m.alarm_clear(1); REQUIRE(alcd_clr.has_value()); CHECK((*alcd_clr & 0x80) == 0); CHECK_FALSE(m.alarm_active(1)); CHECK(m.all_alarms().size() == 2); } // ---- Process programs --------------------------------------------------- TEST_CASE("process program CRUD") { EquipmentDataModel m; m.add_process_program("RECIPE-A", "step1\nstep2\n"); m.add_process_program("RECIPE-B", "alt body"); CHECK(m.process_program_list().size() == 2); CHECK(m.process_program("RECIPE-A").value() == "step1\nstep2\n"); CHECK_FALSE(m.process_program("UNKNOWN").has_value()); CHECK(m.delete_process_program("RECIPE-A") == ProcessProgramAck::Accept); CHECK_FALSE(m.process_program("RECIPE-A").has_value()); CHECK(m.delete_process_program("RECIPE-A") == ProcessProgramAck::PpidNotFound); }