Add valid step rule for custom step validation

[MAILPOET-4659]
This commit is contained in:
Jan Jakes
2022-10-04 15:01:34 +02:00
committed by Jan Jakeš
parent 14eb8db540
commit 9d8cb02006
4 changed files with 204 additions and 0 deletions

View File

@@ -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;
}
}

View File

@@ -13,6 +13,9 @@ use MailPoet\Automation\Engine\Validation\WorkflowRules\NoUnreachableStepsRule;
use MailPoet\Automation\Engine\Validation\WorkflowRules\TriggersUnderRootRule;
use MailPoet\Automation\Engine\Validation\WorkflowRules\UnknownStepRule;
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 {
/** @var WorkflowWalker */
@@ -21,16 +24,21 @@ class WorkflowValidator {
/** @var ValidStepArgsRule */
private $validStepArgsRule;
/** @var ValidStepValidationRule */
private $validStepValidationRule;
/** @var UnknownStepRule */
private $unknownStepRule;
public function __construct(
UnknownStepRule $unknownStepRule,
ValidStepArgsRule $validStepArgsRule,
ValidStepValidationRule $validStepValidationRule,
WorkflowWalker $workflowWalker
) {
$this->unknownStepRule = $unknownStepRule;
$this->validStepArgsRule = $validStepArgsRule;
$this->validStepValidationRule = $validStepValidationRule;
$this->workflowWalker = $workflowWalker;
}
@@ -45,6 +53,7 @@ class WorkflowValidator {
new NoSplitRule(),
$this->unknownStepRule,
$this->validStepArgsRule,
$this->validStepValidationRule,
]);
}
}

View File

@@ -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\WorkflowRules\UnknownStepRule::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\WordPress::class)->setPublic(true);
// Automation - API endpoints

View File

@@ -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', [], []),
],
]);
}
}