Add valid step rule for custom step validation
[MAILPOET-4659]
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace MailPoet\Automation\Engine\Validation\WorkflowRules;
|
||||||
|
|
||||||
|
use MailPoet\Automation\Engine\Data\Step;
|
||||||
|
use MailPoet\Automation\Engine\Data\StepValidationArgs;
|
||||||
|
use MailPoet\Automation\Engine\Data\Workflow;
|
||||||
|
use MailPoet\Automation\Engine\Exceptions;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Payload;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Subject;
|
||||||
|
use MailPoet\Automation\Engine\Integration\ValidationException;
|
||||||
|
use MailPoet\Automation\Engine\Registry;
|
||||||
|
use MailPoet\Automation\Engine\Validation\WorkflowGraph\WorkflowNode;
|
||||||
|
use MailPoet\Automation\Engine\Validation\WorkflowGraph\WorkflowNodeVisitor;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class ValidStepValidationRule implements WorkflowNodeVisitor {
|
||||||
|
/** @var Registry */
|
||||||
|
private $registry;
|
||||||
|
|
||||||
|
/** @var array<string, array{step_id: string, message: string}> */
|
||||||
|
private $errors = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Registry $registry
|
||||||
|
) {
|
||||||
|
$this->registry = $registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initialize(Workflow $workflow): void {
|
||||||
|
$this->errors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function visitNode(Workflow $workflow, WorkflowNode $node): void {
|
||||||
|
$step = $node->getStep();
|
||||||
|
$registryStep = $this->registry->getStep($step->getKey());
|
||||||
|
if (!$registryStep) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// run custom step validation only for active workflows
|
||||||
|
if ($workflow->getStatus() !== Workflow::STATUS_ACTIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($registryStep instanceof Action) {
|
||||||
|
$subjects = $this->collectSubjects($workflow, $node->getParents());
|
||||||
|
$args = new StepValidationArgs($workflow, $step, $subjects);
|
||||||
|
$registryStep->validate($args);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->errors[$step->getId()] = [
|
||||||
|
'step_id' => $step->getId(),
|
||||||
|
'message' => $e instanceof ValidationException ? $e->getMessage() : __('Unknown error.', 'mailpoet'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function complete(Workflow $workflow): void {
|
||||||
|
if ($this->errors) {
|
||||||
|
throw Exceptions::workflowNotValid(__('Some steps are not valid', 'mailpoet'), $this->errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Step[] $parents
|
||||||
|
* @return Subject<Payload>[]
|
||||||
|
*/
|
||||||
|
private function collectSubjects(Workflow $workflow, array $parents): array {
|
||||||
|
$triggers = array_filter($parents, function (Step $step) {
|
||||||
|
return $step->getType() === Step::TYPE_TRIGGER;
|
||||||
|
});
|
||||||
|
|
||||||
|
$subjectKeys = [];
|
||||||
|
foreach ($triggers as $trigger) {
|
||||||
|
$registryTrigger = $this->registry->getTrigger($trigger->getKey());
|
||||||
|
if (!$registryTrigger) {
|
||||||
|
throw Exceptions::workflowTriggerNotFound($workflow->getId(), $trigger->getKey());
|
||||||
|
}
|
||||||
|
$subjectKeys = array_merge($subjectKeys, $registryTrigger->getSubjectKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
$subjects = [];
|
||||||
|
foreach (array_unique($subjectKeys) as $key) {
|
||||||
|
$subject = $this->registry->getSubject($key);
|
||||||
|
if (!$subject) {
|
||||||
|
throw Exceptions::subjectNotFound($key);
|
||||||
|
}
|
||||||
|
$subjects[] = $subject;
|
||||||
|
}
|
||||||
|
return $subjects;
|
||||||
|
}
|
||||||
|
}
|
@@ -13,6 +13,9 @@ use MailPoet\Automation\Engine\Validation\WorkflowRules\NoUnreachableStepsRule;
|
|||||||
use MailPoet\Automation\Engine\Validation\WorkflowRules\TriggersUnderRootRule;
|
use MailPoet\Automation\Engine\Validation\WorkflowRules\TriggersUnderRootRule;
|
||||||
use MailPoet\Automation\Engine\Validation\WorkflowRules\UnknownStepRule;
|
use MailPoet\Automation\Engine\Validation\WorkflowRules\UnknownStepRule;
|
||||||
use MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepArgsRule;
|
use MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepArgsRule;
|
||||||
|
use MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepRule;
|
||||||
|
use MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepOrderRule;
|
||||||
|
use MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepValidationRule;
|
||||||
|
|
||||||
class WorkflowValidator {
|
class WorkflowValidator {
|
||||||
/** @var WorkflowWalker */
|
/** @var WorkflowWalker */
|
||||||
@@ -21,16 +24,21 @@ class WorkflowValidator {
|
|||||||
/** @var ValidStepArgsRule */
|
/** @var ValidStepArgsRule */
|
||||||
private $validStepArgsRule;
|
private $validStepArgsRule;
|
||||||
|
|
||||||
|
/** @var ValidStepValidationRule */
|
||||||
|
private $validStepValidationRule;
|
||||||
|
|
||||||
/** @var UnknownStepRule */
|
/** @var UnknownStepRule */
|
||||||
private $unknownStepRule;
|
private $unknownStepRule;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
UnknownStepRule $unknownStepRule,
|
UnknownStepRule $unknownStepRule,
|
||||||
ValidStepArgsRule $validStepArgsRule,
|
ValidStepArgsRule $validStepArgsRule,
|
||||||
|
ValidStepValidationRule $validStepValidationRule,
|
||||||
WorkflowWalker $workflowWalker
|
WorkflowWalker $workflowWalker
|
||||||
) {
|
) {
|
||||||
$this->unknownStepRule = $unknownStepRule;
|
$this->unknownStepRule = $unknownStepRule;
|
||||||
$this->validStepArgsRule = $validStepArgsRule;
|
$this->validStepArgsRule = $validStepArgsRule;
|
||||||
|
$this->validStepValidationRule = $validStepValidationRule;
|
||||||
$this->workflowWalker = $workflowWalker;
|
$this->workflowWalker = $workflowWalker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +53,7 @@ class WorkflowValidator {
|
|||||||
new NoSplitRule(),
|
new NoSplitRule(),
|
||||||
$this->unknownStepRule,
|
$this->unknownStepRule,
|
||||||
$this->validStepArgsRule,
|
$this->validStepArgsRule,
|
||||||
|
$this->validStepValidationRule,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -132,6 +132,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
|||||||
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowGraph\WorkflowWalker::class)->setPublic(true);
|
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowGraph\WorkflowWalker::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowRules\UnknownStepRule::class)->setPublic(true);
|
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowRules\UnknownStepRule::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepArgsRule::class)->setPublic(true);
|
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepArgsRule::class)->setPublic(true);
|
||||||
|
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowRules\ValidStepValidationRule::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowValidator::class)->setPublic(true);
|
$container->autowire(\MailPoet\Automation\Engine\Validation\WorkflowValidator::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Automation\Engine\WordPress::class)->setPublic(true);
|
$container->autowire(\MailPoet\Automation\Engine\WordPress::class)->setPublic(true);
|
||||||
// Automation - API endpoints
|
// Automation - API endpoints
|
||||||
|
@@ -0,0 +1,99 @@
|
|||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace MailPoet\Automation\Engine\Validation\WorkflowRules;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/WorkflowRuleTest.php';
|
||||||
|
|
||||||
|
use Codeception\Stub\Expected;
|
||||||
|
use MailPoet\Automation\Engine\Control\RootStep;
|
||||||
|
use MailPoet\Automation\Engine\Data\NextStep;
|
||||||
|
use MailPoet\Automation\Engine\Data\Step;
|
||||||
|
use MailPoet\Automation\Engine\Data\StepValidationArgs;
|
||||||
|
use MailPoet\Automation\Engine\Data\Workflow;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Action;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Subject;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Trigger;
|
||||||
|
use MailPoet\Automation\Engine\Registry;
|
||||||
|
use MailPoet\Automation\Engine\Validation\WorkflowGraph\WorkflowWalker;
|
||||||
|
|
||||||
|
class ValidStepValidationRuleTest extends WorkflowRuleTest {
|
||||||
|
public function testItRunsStepValidation(): void {
|
||||||
|
$workflow = $this->getWorkflow();
|
||||||
|
$registry = $this->make(Registry::class, [
|
||||||
|
'steps' => [
|
||||||
|
'core:root' => $this->make(RootStep::class, [
|
||||||
|
'validate' => Expected::once(function (StepValidationArgs $args) use ($workflow) {
|
||||||
|
$this->assertSame($workflow, $args->getWorkflow());
|
||||||
|
$this->assertSame($workflow->getStep('root'), $args->getStep());
|
||||||
|
$this->assertSame([], $args->getSubjects());
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rule = new ValidStepValidationRule($registry);
|
||||||
|
(new WorkflowWalker())->walk($workflow, [$rule]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItCollectsAvailableSubjects(): void {
|
||||||
|
$subjectA = $this->makeEmpty(Subject::class, ['getKey' => 'test:subject-a']);
|
||||||
|
$subjectB = $this->makeEmpty(Subject::class, ['getKey' => 'test:subject-b']);
|
||||||
|
|
||||||
|
$registry = $this->make(Registry::class, [
|
||||||
|
'steps' => [
|
||||||
|
'core:root' => $this->make(RootStep::class, [
|
||||||
|
'validate' => Expected::once(function (StepValidationArgs $args) {
|
||||||
|
$this->assertSame([], $args->getSubjects());
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$registry->addTrigger(
|
||||||
|
$this->makeEmpty(Trigger::class, [
|
||||||
|
'getKey' => 'test:trigger',
|
||||||
|
'getSubjectKeys' => ['test:subject-a', 'test:subject-b'],
|
||||||
|
'validate' => Expected::once(function (StepValidationArgs $args) {
|
||||||
|
$this->assertSame([], $args->getSubjects());
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
$registry->addAction(
|
||||||
|
$this->makeEmpty(Action::class, [
|
||||||
|
'getKey' => 'test:action',
|
||||||
|
'validate' => Expected::once(function (StepValidationArgs $args) use ($subjectA, $subjectB) {
|
||||||
|
$this->assertSame([$subjectA, $subjectB], $args->getSubjects());
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$registry->addSubject($subjectA);
|
||||||
|
$registry->addSubject($subjectB);
|
||||||
|
|
||||||
|
$workflow = $this->make(Workflow::class, [
|
||||||
|
'getId' => 1,
|
||||||
|
'steps' => [
|
||||||
|
'root' => new Step('root', 'root', 'core:root', [], [new NextStep('t')]),
|
||||||
|
't' => new Step('t', 'trigger', 'test:trigger', [], [new NextStep('a')]),
|
||||||
|
'a' => new Step('a', 'action', 'test:action', [], []),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rule = new ValidStepValidationRule($registry);
|
||||||
|
(new WorkflowWalker())->walk($workflow, [$rule]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItSkipsArgsValidationForNonExistentStep(): void {
|
||||||
|
$registry = $this->make(Registry::class);
|
||||||
|
$rule = new ValidStepValidationRule($registry);
|
||||||
|
$workflow = $this->getWorkflow();
|
||||||
|
(new WorkflowWalker())->walk($workflow, [$rule]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getWorkflow(): Workflow {
|
||||||
|
return $this->make(Workflow::class, [
|
||||||
|
'steps' => [
|
||||||
|
'root' => new Step('root', 'root', 'core:root', [], []),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user