|
- #include "domain/control_logic_model.h"
- #include "domain/hmi_model.h"
- #include "domain/project_model.h"
- #include "domain/register_address.h"
- #include "domain/register_repository.h"
- #include "domain/runtime_state.h"
-
- #include <cstdint>
- #include <exception>
- #include <iostream>
- #include <stdexcept>
- #include <string>
-
- namespace domain = integrated_platform::domain;
-
- namespace {
-
- void require(bool condition, const std::string &message)
- {
- if (!condition)
- {
- throw std::runtime_error(message);
- }
- }
-
- void testRegisterAddressBoundaries()
- {
- require(domain::RegisterAddress{domain::RegisterArea::M, 0}.isValid(),
- "M0 must be valid");
- require(domain::RegisterAddress{domain::RegisterArea::D, 4000}.isValid(),
- "D4000 must be valid");
- require(!(domain::RegisterAddress{domain::RegisterArea::M, -1}.isValid()),
- "negative register index must be rejected");
- require(!(domain::RegisterAddress{domain::RegisterArea::D, 4001}.isValid()),
- "register index above 4000 must be rejected");
- require(!(domain::RegisterAddress{static_cast<domain::RegisterArea>(99), 0}.isValid()),
- "unknown register area must be rejected");
- }
-
- void testRegisterRepositorySeparatesAreas()
- {
- domain::VirtualRegisterRepository repository;
- const domain::RegisterAddress m0{domain::RegisterArea::M, 0};
- const domain::RegisterAddress d0{domain::RegisterArea::D, 0};
-
- require(repository.writeBit(m0, true).succeeded, "M bit write must succeed");
- require(repository.readBit(m0).value, "M bit read must return written value");
- require(repository.writeWord(d0, static_cast<std::int16_t>(-123)).succeeded,
- "D word write must succeed");
- require(repository.readWord(d0).value == -123, "D word read must return written value");
- require(repository.readBit(d0).error == domain::RegisterError::AreaMismatch,
- "D address must not be read as a bit");
- require(repository.readWord(m0).error == domain::RegisterError::AreaMismatch,
- "M address must not be read as a word");
- }
-
- domain::Project makeValidProject()
- {
- domain::HmiControl start_button;
- start_button.id = "start-button";
- start_button.type = domain::HmiControlType::Button;
- start_button.text = "Start";
- start_button.binding = {domain::RegisterArea::M, 0};
-
- domain::HmiPage page;
- page.id = "main-page";
- page.name = "Main";
- page.controls.push_back(start_button);
-
- domain::LogicNode contact;
- contact.id = "start-contact";
- contact.config = domain::ContactNodeConfig{
- domain::RegisterAddress{domain::RegisterArea::M, 0},
- domain::ContactMode::NormallyOpen};
-
- domain::LogicNode coil;
- coil.id = "run-coil";
- coil.config = domain::CoilNodeConfig{
- domain::RegisterAddress{domain::RegisterArea::M, 1},
- domain::CoilMode::Normal};
-
- domain::ControlLogic logic;
- logic.id = "start-logic";
- logic.name = "Start logic";
- logic.nodes = {contact, coil};
- logic.connections.push_back({contact.id, coil.id});
-
- domain::Project project;
- project.metadata = {"sample-project", "Sample project", "1.0"};
- project.hmiPages.push_back(page);
- project.controlLogics.push_back(logic);
- return project;
- }
-
- void testLogicNodeConfigurationBoundaries()
- {
- domain::LogicNode contact;
- contact.id = "contact";
- contact.config = domain::ContactNodeConfig{
- domain::RegisterAddress{domain::RegisterArea::M, 0},
- domain::ContactMode::NormallyOpen};
- require(contact.validate(), "contact node bound to M address must be valid");
-
- contact.config = domain::ContactNodeConfig{
- domain::RegisterAddress{domain::RegisterArea::D, 0},
- domain::ContactMode::NormallyOpen};
- require(!contact.validate(), "contact node bound to D address must be rejected");
-
- domain::LogicNode comparison;
- comparison.id = "comparison";
- comparison.config = domain::CompareNodeConfig{
- domain::RegisterAddress{domain::RegisterArea::D, 0},
- domain::ComparisonOperator::GreaterThan,
- static_cast<std::int16_t>(100)};
- require(comparison.validate(), "comparison node bound to D address must be valid");
- }
-
- void testModelsValidateBindingsAndIdentifiers()
- {
- domain::Project project = makeValidProject();
- require(project.validate(), "valid project model must pass validation");
-
- project.hmiPages.front().controls.front().binding =
- domain::RegisterAddress{domain::RegisterArea::D, 0};
- require(!project.validate(), "button bound to D area must be rejected");
-
- project = makeValidProject();
- project.hmiPages.push_back(project.hmiPages.front());
- require(!project.validate(), "duplicate HMI page id must be rejected");
- }
-
- void testRuntimeStateBoundaries()
- {
- domain::RuntimeState state;
- require(state.policy().allowsProjectEditing, "editing mode must allow project editing");
- require(state.enterOfflineRunning().succeeded, "editing may enter offline running");
- require(state.policy().usesVirtualRegisters, "offline mode must use virtual registers");
- require(state.policy().runsLogicExecutor, "offline mode must run logic executor");
- require(state.enterOnlineRunning(true).error
- == domain::ModeTransitionError::MustReturnToEditing,
- "offline mode must not directly enter online mode");
-
- require(state.enterEditing().succeeded, "offline mode may return to editing");
- require(state.enterOnlineRunning(false).error
- == domain::ModeTransitionError::InitialPlcReadRequired,
- "online mode must require an initial PLC read");
- require(state.enterOnlineRunning(true).succeeded,
- "editing may enter online mode after initial PLC read");
- require(!state.policy().runsLogicExecutor,
- "online mode must keep the software logic executor stopped");
- require(state.policy().usesPlcRegisters, "online mode must use PLC registers");
- }
-
- } // namespace
-
- int main()
- {
- try
- {
- testRegisterAddressBoundaries();
- testRegisterRepositorySeparatesAreas();
- testLogicNodeConfigurationBoundaries();
- testModelsValidateBindingsAndIdentifiers();
- testRuntimeStateBoundaries();
- }
- catch (const std::exception &error)
- {
- std::cerr << "domain tests failed: " << error.what() << '\n';
- return 1;
- }
-
- std::cout << "domain tests passed\n";
- return 0;
- }
|