Files
secs-gem/tests/test_cem_objects.cpp
T
raphael d159bd39d7 G: CemObjectStore (E120 Common Equipment Model)
Hierarchical object tree for equipment self-description.  Each object
carries a CemObjectType (Equipment / Subsystem / IODevice / Module /
MaterialLocation / Other), an optional parent_objid, and a flat
attribute map keyed by name (the wire shape S14F1 / F3 returns).

Operations covered:
  add(CemObject)        - dedup'd, validates parent exists
  get / has             - lookup by objid
  get_attr / set_attr   - E14 GetAttr / SetAttr semantics
  children(parent)      - tree traversal; empty parent = roots

The flat-map representation matches how E14 ObjectService traffic
addresses nodes (by OBJSPEC string).  Wiring S14F1/F2 GetAttr and
S14F3/F4 SetAttr to this store is a downstream commit; the data model
is what was missing.

Joins EquipmentDataModel alongside the other top-level stores.  Three
test cases cover hierarchical add+dedup, children() traversal, and
get/set/missing attribute semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 03:35:05 +02:00

47 lines
1.8 KiB
C++

#include <doctest/doctest.h>
#include "secsgem/gem/store/cem_objects.hpp"
using namespace secsgem::gem;
namespace s2 = secsgem::secs2;
TEST_CASE("CemObjectStore: add / get / size") {
CemObjectStore s;
CHECK(s.add({"EQ-1", CemObjectType::Equipment, "", {}}) ==
CemObjectStore::CreateResult::Created);
CHECK(s.add({"EQ-1", CemObjectType::Equipment, "", {}}) ==
CemObjectStore::CreateResult::Denied_AlreadyExists);
CHECK(s.add({"SUB-1", CemObjectType::Subsystem, "EQ-1", {}}) ==
CemObjectStore::CreateResult::Created);
CHECK(s.add({"orphan", CemObjectType::Module, "ghost", {}}) ==
CemObjectStore::CreateResult::Denied_BadParent);
CHECK(s.size() == 2);
}
TEST_CASE("CemObjectStore: tree traversal via children()") {
CemObjectStore s;
s.add({"EQ-1", CemObjectType::Equipment, "", {}});
s.add({"SUB-A", CemObjectType::Subsystem, "EQ-1", {}});
s.add({"SUB-B", CemObjectType::Subsystem, "EQ-1", {}});
s.add({"MOD-1", CemObjectType::Module, "SUB-A", {}});
CHECK(s.children("").size() == 1); // one root
CHECK(s.children("EQ-1").size() == 2); // two subsystems
CHECK(s.children("SUB-A").size() == 1); // one module
CHECK(s.children("SUB-B").empty());
}
TEST_CASE("CemObjectStore: GetAttr / SetAttr E14 semantics") {
CemObjectStore s;
s.add({"EQ-1", CemObjectType::Equipment, "",
{{"MDLN", s2::Item::ascii("SECS-DEMO")}}});
auto mdln = s.get_attr("EQ-1", "MDLN");
REQUIRE(mdln.has_value());
CHECK(mdln->as_ascii() == "SECS-DEMO");
// SetAttr on existing attr overwrites.
CHECK(s.set_attr("EQ-1", "MDLN", s2::Item::ascii("v2")));
CHECK(s.get_attr("EQ-1", "MDLN")->as_ascii() == "v2");
// Unknown object.
CHECK_FALSE(s.set_attr("ghost", "x", s2::Item::ascii("y")));
CHECK_FALSE(s.get_attr("EQ-1", "UNKNOWN_ATTR").has_value());
}