diff --git a/mailpoet/lib/Automation/Engine/Validation/WorkflowRules/AtLeastOneTriggerRule.php b/mailpoet/lib/Automation/Engine/Validation/WorkflowRules/AtLeastOneTriggerRule.php new file mode 100644 index 0000000000..7b2e8b7104 --- /dev/null +++ b/mailpoet/lib/Automation/Engine/Validation/WorkflowRules/AtLeastOneTriggerRule.php @@ -0,0 +1,28 @@ +getSteps() as $step) { + if ($step->getType() === 'trigger') { + return; + } + } + + throw Exceptions::workflowStructureNotValid(__('There must be at least one trigger in the workflow.', 'mailpoet'), self::RULE_ID); + } + + public function visitNode(Workflow $workflow, WorkflowNode $node): void { + } + + public function complete(Workflow $workflow): void { + } +} diff --git a/mailpoet/lib/Automation/Engine/Validation/WorkflowRules/TriggerNeedsToBeFollowedByActionRule.php b/mailpoet/lib/Automation/Engine/Validation/WorkflowRules/TriggerNeedsToBeFollowedByActionRule.php new file mode 100644 index 0000000000..0e634ea0a4 --- /dev/null +++ b/mailpoet/lib/Automation/Engine/Validation/WorkflowRules/TriggerNeedsToBeFollowedByActionRule.php @@ -0,0 +1,37 @@ +getStep(); + if ($step->getType() !== Step::TYPE_TRIGGER) { + return; + } + $nextSteps = $step->getNextSteps(); + if (!count($nextSteps)) { + throw Exceptions::workflowStructureNotValid(__('A trigger needs to be followed by an action.', 'mailpoet'), self::RULE_ID); + } + foreach ($nextSteps as $step) { + $step = $workflow->getStep($step->getId()); + if ($step && $step->getType() === Step::TYPE_ACTION) { + continue; + } + throw Exceptions::workflowStructureNotValid(__('A trigger needs to be followed by an action.', 'mailpoet'), self::RULE_ID); + } + } + + public function complete(Workflow $workflow): void { + } +} diff --git a/mailpoet/lib/Automation/Engine/Validation/WorkflowValidator.php b/mailpoet/lib/Automation/Engine/Validation/WorkflowValidator.php index 8b9926e489..8856577175 100644 --- a/mailpoet/lib/Automation/Engine/Validation/WorkflowValidator.php +++ b/mailpoet/lib/Automation/Engine/Validation/WorkflowValidator.php @@ -4,12 +4,14 @@ namespace MailPoet\Automation\Engine\Validation; use MailPoet\Automation\Engine\Data\Workflow; use MailPoet\Automation\Engine\Validation\WorkflowGraph\WorkflowWalker; +use MailPoet\Automation\Engine\Validation\WorkflowRules\AtLeastOneTriggerRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\ConsistentStepMapRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\NoCycleRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\NoDuplicateEdgesRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\NoJoinRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\NoSplitRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\NoUnreachableStepsRule; +use MailPoet\Automation\Engine\Validation\WorkflowRules\TriggerNeedsToBeFollowedByActionRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\TriggersUnderRootRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\UnknownStepRule; use MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepArgsRule; @@ -53,6 +55,8 @@ class WorkflowValidator { new ConsistentStepMapRule(), new NoDuplicateEdgesRule(), new TriggersUnderRootRule(), + new AtLeastOneTriggerRule(), + new TriggerNeedsToBeFollowedByActionRule(), new NoCycleRule(), new NoJoinRule(), new NoSplitRule(), diff --git a/mailpoet/tests/unit/Automation/Engine/Validation/WorkflowRules/AtLeastOnTriggerTest.php b/mailpoet/tests/unit/Automation/Engine/Validation/WorkflowRules/AtLeastOnTriggerTest.php new file mode 100644 index 0000000000..2589d66f4a --- /dev/null +++ b/mailpoet/tests/unit/Automation/Engine/Validation/WorkflowRules/AtLeastOnTriggerTest.php @@ -0,0 +1,36 @@ + $this->createStep('root', Step::TYPE_ROOT, ['t']), + 't' => $this->createStep('t', Step::TYPE_TRIGGER), + ]; + $workflow = $this->make(Workflow::class, ['getSteps' => $steps, 'getStep' => function($id) use ($steps) { return $steps[$id]??null; }]); + + (new WorkflowWalker())->walk($workflow, [new AtLeastOneTriggerRule()]); + //no exception thrown. + } + + public function testItFailsWhenNoTriggerExists(): void { + $steps = [ + 'root' => $this->createStep('root', Step::TYPE_ROOT) + ]; + $workflow = $this->make(Workflow::class, ['getSteps' => $steps, 'getStep' => function($id) use ($steps) { return $steps[$id]??null; }]); + + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Invalid workflow structure: There must be at least one trigger in the workflow.'); + (new WorkflowWalker())->walk($workflow, [new AtLeastOneTriggerRule()]); + } +} diff --git a/mailpoet/tests/unit/Automation/Engine/Validation/WorkflowRules/TriggerNeedsNextStepsRuleTest.php b/mailpoet/tests/unit/Automation/Engine/Validation/WorkflowRules/TriggerNeedsNextStepsRuleTest.php new file mode 100644 index 0000000000..a02fe6aabe --- /dev/null +++ b/mailpoet/tests/unit/Automation/Engine/Validation/WorkflowRules/TriggerNeedsNextStepsRuleTest.php @@ -0,0 +1,52 @@ + $this->createStep('root', Step::TYPE_ROOT, ['t']), + 't' => $this->createStep('t', Step::TYPE_TRIGGER, ['a']), + 'a' => $this->createStep('a', Step::TYPE_ACTION, []), + ]; + $workflow = $this->make(Workflow::class, ['getSteps' => $steps, 'getStep' => function($id) use ($steps) { return $steps[$id]??null; }]); + + (new WorkflowWalker())->walk($workflow, [new TriggerNeedsToBeFollowedByActionRule()]); + //no exception thrown. + } + + public function testItFailsWhenNoActionIsFollowed(): void { + $steps = [ + 'root' => $this->createStep('root', Step::TYPE_ROOT, ['t']), + 't' => $this->createStep('t', Step::TYPE_TRIGGER, []), + ]; + $workflow = $this->make(Workflow::class, ['getSteps' => $steps, 'getStep' => function($id) use ($steps) { return $steps[$id]??null; }]); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Invalid workflow structure: A trigger needs to be followed by an action.'); + (new WorkflowWalker())->walk($workflow, [new TriggerNeedsToBeFollowedByActionRule()]); + } + + public function testItFailsWhenFollowedByAStepNotBeingAnAction(): void { + $steps = [ + 'root' => $this->createStep('root', Step::TYPE_ROOT, ['t1']), + 't1' => $this->createStep('t1', Step::TYPE_TRIGGER, ['a', 't2']), + 'a' => $this->createStep('a', Step::TYPE_ACTION, []), + 't2' => $this->createStep('t2', Step::TYPE_TRIGGER, ['a']), + ]; + $workflow = $this->make(Workflow::class, ['getSteps' => $steps, 'getStep' => function($id) use ($steps) { return $steps[$id]??null; }]); + + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Invalid workflow structure: A trigger needs to be followed by an action.'); + (new WorkflowWalker())->walk($workflow, [new TriggerNeedsToBeFollowedByActionRule()]); + } +}