#include "domain/project_storage.h" #include "services/logic_editor_service.h" #include "services/project_service.h" #include #include #include namespace { class TestProjectStorage final : public ProjectStorage { public: ProjectSaveResult save(const Project &, const std::string &) override { return {true, ProjectStorageError::None, {}}; } ProjectLoadResult load(const std::string &) override { return {false, {}, ProjectStorageError::FileReadFailed, {}}; } }; void require(bool condition, const std::string &message) { if (!condition) { throw std::runtime_error(message); } } void testEditorOperations() { TestProjectStorage storage; ProjectService project_service(storage); LogicEditorService service(project_service); const LogicEditorResult logic_result = service.ensureDefaultLogic(); require(logic_result.succeeded, "default logic must be created"); const std::string logic_id = logic_result.id; const std::string rung_id = service.firstRungId(logic_id); require(!rung_id.empty(), "default logic must contain an editable rung"); const LogicEditorResult stop_result = service.appendCondition( logic_id, rung_id, ContactNodeConfig{ RegisterAddress{RegisterArea::M, 1}, ContactMode::NormallyClosed}); const LogicEditorResult start_result = service.appendCondition( logic_id, rung_id, ContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyOpen}); require(stop_result.succeeded && start_result.succeeded, "series contacts must be appended as ladder stages"); const std::string start_stage_id = service.stageIdForNode( logic_id, start_result.id); const LogicEditorResult hold_result = service.addParallelCondition( logic_id, rung_id, start_stage_id, ContactNodeConfig{ RegisterAddress{RegisterArea::M, 2}, ContactMode::NormallyOpen}); const LogicEditorResult coil_result = service.setOutput( logic_id, rung_id, CoilNodeConfig{ RegisterAddress{RegisterArea::M, 2}, CoilMode::Normal}); require(hold_result.succeeded && coil_result.succeeded, "parallel hold contact and output coil must be added"); const LadderRung *rung = service.findRung(logic_id, rung_id); require(rung != nullptr && rung->stages.size() == 2, "series conditions must occupy ordered stages"); require(rung->stages.at(1).branches.size() == 2, "parallel conditions must share one stage"); require(rung->output.has_value(), "rung output must be fixed separately"); require(service.findLogic(logic_id)->validate(), "configured self-hold ladder must pass full validation"); const LogicNode *start = service.findNode(logic_id, start_result.id); require(start != nullptr, "added condition must be discoverable"); require(service.updateNodeConfig( logic_id, start_result.id, ContactNodeConfig{ RegisterAddress{RegisterArea::M, 2}, ContactMode::NormallyClosed}) .succeeded, "contact properties must be editable"); require(service.removeNode(logic_id, hold_result.id).succeeded, "parallel branch deletion must succeed"); require(service.findRung(logic_id, rung_id)->stages.at(1).branches.size() == 1, "deleting one branch must keep the ladder stage"); require(service.removeNode(logic_id, start_result.id).succeeded, "last branch deletion must succeed"); require(service.findRung(logic_id, rung_id)->stages.size() == 1, "deleting the last branch must remove the empty stage"); const LogicEditorResult second_rung = service.addRung(logic_id); require(second_rung.succeeded, "additional ladder rungs must be supported"); require(service.removeRung(logic_id, second_rung.id).succeeded, "additional ladder rungs must be removable"); require(!service.removeRung(logic_id, rung_id).succeeded, "the only remaining ladder rung must not be removed"); } } // namespace int main() { try { testEditorOperations(); } catch (const std::exception &error) { std::cerr << "logic editor service tests failed: " << error.what() << '\n'; return 1; } std::cout << "logic editor service tests passed\n"; return 0; }