Validate that fields in filters are provided by subjects available in the automation

[MAILPOET-5586]
This commit is contained in:
Jan Jakes
2023-10-12 16:24:24 +02:00
committed by Aschepikov
parent 0203f6e3a3
commit c33da588af
2 changed files with 127 additions and 3 deletions

View File

@@ -2,7 +2,11 @@
namespace MailPoet\Automation\Engine\Validation\AutomationRules;
use MailPoet\Automation\Engine\Control\SubjectTransformerHandler;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\Filter;
use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject;
use MailPoet\Automation\Engine\Registry;
use MailPoet\Automation\Engine\Validation\AutomationGraph\AutomationNode;
use MailPoet\Automation\Engine\Validation\AutomationGraph\AutomationNodeVisitor;
@@ -13,14 +17,19 @@ class ValidStepFiltersRule implements AutomationNodeVisitor {
/** @var Registry */
private $registry;
/** @var SubjectTransformerHandler */
private $subjectTransformerHandler;
/** @var Validator */
private $validator;
public function __construct(
Registry $registry,
SubjectTransformerHandler $subjectTransformerHandler,
Validator $validator
) {
$this->registry = $registry;
$this->subjectTransformerHandler = $subjectTransformerHandler;
$this->validator = $validator;
}
@@ -41,6 +50,17 @@ class ValidStepFiltersRule implements AutomationNodeVisitor {
$this->validator->validate($registryFilter->getArgsSchema($filter->getCondition()), $filter->getArgs());
} catch (ValidationException $e) {
$errors[$filter->getId()] = $e->getWpError()->get_error_code();
continue;
}
// ensure that the field is available with the provided subjects
$subjectKeys = $this->subjectTransformerHandler->getSubjectKeysForAutomation($automation);
$filterSubject = $this->getFilterSubject($filter);
if (!$filterSubject) {
$errors[$filter->getId()] = __('Field not found', 'mailpoet');
} elseif (!in_array($filterSubject->getKey(), $subjectKeys, true)) {
// translators: %s is the name of a subject (data structure) that provides the field
$errors[$filter->getId()] = sprintf(__('A trigger that provides %s is required', 'mailpoet'), $filterSubject->getName());
}
}
}
@@ -56,4 +76,16 @@ class ValidStepFiltersRule implements AutomationNodeVisitor {
public function complete(Automation $automation): void {
}
/** @return Subject<Payload> */
private function getFilterSubject(Filter $filter): ?Subject {
foreach ($this->registry->getSubjects() as $subject) {
foreach ($subject->getFields() as $field) {
if ($field->getKey() === $filter->getFieldKey()) {
return $subject;
}
}
}
return null;
}
}

View File

@@ -6,33 +6,45 @@ require_once __DIR__ . '/AutomationRuleTest.php';
use Codeception\Stub\Expected;
use MailPoet\Automation\Engine\Control\RootStep;
use MailPoet\Automation\Engine\Control\SubjectTransformerHandler;
use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Data\Field;
use MailPoet\Automation\Engine\Data\Filter as FilterData;
use MailPoet\Automation\Engine\Data\FilterGroup;
use MailPoet\Automation\Engine\Data\Filters;
use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Data\Subject as SubjectData;
use MailPoet\Automation\Engine\Integration\Filter;
use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject;
use MailPoet\Automation\Engine\Registry;
use MailPoet\Automation\Engine\Validation\AutomationGraph\AutomationWalker;
use MailPoet\Automation\Engine\Validation\AutomationRules\AutomationRuleTest;
use MailPoet\Automation\Engine\Validation\AutomationRules\ValidStepFiltersRule;
use MailPoet\Validator\Builder;
use MailPoet\Validator\Schema\ObjectSchema;
use MailPoet\Validator\ValidationException;
use MailPoet\Validator\Validator;
class ValidStepFiltersRuleTest extends AutomationRuleTest {
public function testItRunsFiltersValidation(): void {
$field = new Field('test:field', Field::TYPE_STRING, 'Test field', [$this, 'callback']);
$registry = $this->make(Registry::class, [
'steps' => ['core:root' => new RootStep()],
'filters' => ['string' => $this->getFilter()],
'subjects' => [$this->getSubject('test:subject-key', 'Test subject', [$field])],
]);
$validator = $this->make(Validator::class, [
'validate' => Expected::once(),
]);
$filters = [new FilterData('f1', 'string', 'test:key', 'is', ['value' => 'test'])];
$rule = new ValidStepFiltersRule($registry, $validator);
$transformer = $this->make(SubjectTransformerHandler::class, [
'getSubjectKeysForAutomation' => ['test:subject-key'],
]);
$filters = [new FilterData('f1', 'string', 'test:field', 'is', ['value' => 'test'])];
$rule = new ValidStepFiltersRule($registry, $transformer, $validator);
$automation = $this->getAutomation($filters);
(new AutomationWalker())->walk($automation, [$rule]);
}
@@ -42,13 +54,48 @@ class ValidStepFiltersRuleTest extends AutomationRuleTest {
$validator = $this->make(Validator::class, [
'validate' => Expected::never(),
]);
$transformer = $this->make(SubjectTransformerHandler::class);
$filters = [new FilterData('f1', 'string', 'test:key', 'is', ['value' => 'test'])];
$rule = new ValidStepFiltersRule($registry, $validator);
$rule = new ValidStepFiltersRule($registry, $transformer, $validator);
$automation = $this->getAutomation($filters);
(new AutomationWalker())->walk($automation, [$rule]);
}
public function testItValidatesFieldsAreProvidedBySubjects(): void {
$field = new Field('test:field', Field::TYPE_STRING, 'Test field', [$this, 'callback']);
$registry = $this->make(Registry::class, [
'steps' => ['core:root' => new RootStep()],
'filters' => ['string' => $this->getFilter()],
'subjects' => [$this->getSubject('test:subject', 'Test subject', [$field])],
]);
$validator = $this->make(Validator::class, [
'validate' => Expected::exactly(2),
]);
$transformer = $this->make(SubjectTransformerHandler::class, [
'getSubjectKeysForAutomation' => [],
]);
$filters = [
new FilterData('f1', 'string', 'test:field', 'is', ['value' => 'test']),
new FilterData('f2', 'string', 'test:unknown-field', 'is', ['value' => 'test']),
];
$rule = new ValidStepFiltersRule($registry, $transformer, $validator);
$automation = $this->getAutomation($filters);
$error = null;
try {
(new AutomationWalker())->walk($automation, [$rule]);
} catch (ValidationException $error) {
$this->assertCount(2, $error->getErrors());
$this->assertSame('A trigger that provides Test subject is required', $error->getErrors()['f1']);
$this->assertSame('Field not found', $error->getErrors()['f2']);
}
$this->assertNotNull($error);
}
private function getAutomation(array $filters): Automation {
$filters = new Filters('and', [new FilterGroup('g1', 'and', $filters)]);
return $this->make(Automation::class, [
@@ -77,4 +124,49 @@ class ValidStepFiltersRuleTest extends AutomationRuleTest {
}
};
}
/** @return Subject<Payload> */
private function getSubject(string $key, string $name, array $fields): Subject {
return new class($key, $name, $fields) implements Subject {
/** @var string */
private $key;
/** @var string */
private $name;
/** @var array */
private $fields;
public function __construct(
string $key,
string $name,
array $fields
) {
$this->key = $key;
$this->name = $name;
$this->fields = $fields;
}
public function getKey(): string {
return $this->key;
}
public function getName(): string {
return $this->name;
}
public function getArgsSchema(): ObjectSchema {
return Builder::object();
}
public function getFields(): array {
return $this->fields;
}
public function getPayload(SubjectData $subjectData): Payload {
return new class() implements Payload {
};
}
};
}
}