#include "domain/project_storage.h" #include "services/logic_editor_service.h" #include "services/project_service.h" #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); } } ContactNodeConfig contact(int address) { return {RegisterAddress{RegisterArea::M, address}, ContactMode::NormallyOpen}; } void testStructuredEditingAndNormalization() { TestProjectStorage storage; ProjectService project_service(storage); LogicEditorService service(project_service); const std::string logic_id = service.ensureDefaultLogic().id; const std::string rung_id = service.firstRungId(logic_id); const LogicEditorResult first = service.appendCondition(logic_id, rung_id, contact(0)); const LogicEditorResult second = service.appendCondition(logic_id, rung_id, contact(1)); require(first.succeeded && second.succeeded, "series append must succeed"); const LadderRung *rung = service.findRung(logic_id, rung_id); require(rung->condition->kind == ConditionExpressionKind::Series && rung->condition->children.size() == 2U, "two appended nodes must form a series expression"); const std::string second_expression_id = second.id; const LogicEditorResult parallel = service.addParallelCondition( logic_id, rung_id, second_expression_id, contact(2)); require(parallel.succeeded, "parallel insertion must succeed"); rung = service.findRung(logic_id, rung_id); const ConditionExpression *parallel_expression = service.findExpression( logic_id, rung_id, rung->condition->children.at(1).id); require(parallel_expression != nullptr && parallel_expression->kind == ConditionExpressionKind::Parallel, "selected node must become a parallel expression"); const LogicEditorResult nested_series = service.insertCondition( logic_id, rung_id, parallel.id, SeriesInsertPosition::After, contact(3)); require(nested_series.succeeded, "a parallel branch must accept a series node"); rung = service.findRung(logic_id, rung_id); require(rung->condition->kind == ConditionExpressionKind::Series, "root must remain a series expression"); const ConditionExpression &nested_parallel_expression = rung->condition->children.at(1); require(nested_parallel_expression.kind == ConditionExpressionKind::Parallel && nested_parallel_expression.children.at(1).kind == ConditionExpressionKind::Series, "editor must express A AND (B OR (C AND D))"); require(service.removeNode(logic_id, nested_series.id).succeeded, "nested series node deletion must succeed"); rung = service.findRung(logic_id, rung_id); require(rung->condition->children.at(1).kind == ConditionExpressionKind::Parallel && rung->condition->children.at(1).children.at(1).kind == ConditionExpressionKind::Node, "single-child series container must collapse after deletion"); require(service.removeNode(logic_id, parallel.id).succeeded, "parallel leaf deletion must succeed"); rung = service.findRung(logic_id, rung_id); require(rung->condition->kind == ConditionExpressionKind::Series && rung->condition->children.size() == 2U, "single-child parallel container must collapse after deletion"); require(service.setOutput( logic_id, rung_id, CoilNodeConfig{RegisterAddress{RegisterArea::M, 10}, CoilMode::Normal}) .succeeded, "output coil must be set"); require(service.findLogic(logic_id)->validate(), "structured editing result must remain a valid draft"); } void testBranchLevelParallelInsertion() { TestProjectStorage storage; ProjectService project_service(storage); LogicEditorService service(project_service); const std::string logic_id = service.ensureDefaultLogic().id; const std::string rung_id = service.firstRungId(logic_id); service.appendCondition(logic_id, rung_id, contact(0)); service.appendCondition(logic_id, rung_id, contact(1)); const std::string series_id = service.findRung(logic_id, rung_id)->condition->id; const LogicEditorResult branch = service.addParallelCondition( logic_id, rung_id, series_id, contact(2)); require(branch.succeeded, "a whole series branch must accept a parallel condition"); const ConditionExpression &root = *service.findRung(logic_id, rung_id)->condition; require(root.kind == ConditionExpressionKind::Parallel && root.children.front().kind == ConditionExpressionKind::Series, "branch-level insertion must express (A AND B) OR C"); } } // namespace int main() { try { testStructuredEditingAndNormalization(); testBranchLevelParallelInsertion(); } 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; }