Newsletter validation updates

- Rename validator to newsletterValidator for clarity
- Add validation for ALC content
- Refactor tests to use data factory for consistency and to avoid
validation issues
- Add separate tests for NewsletterValidator service
- Add test helper for retrieving service with private properties
overridden by name

[MAILPOET-4236]
This commit is contained in:
John Oleksowicz
2022-04-18 14:00:43 -05:00
committed by Veljko V
parent a5103f9596
commit 9bfe2b2ca1
9 changed files with 184 additions and 41 deletions

View File

@ -17,12 +17,12 @@ use MailPoet\Listing;
use MailPoet\Newsletter\Listing\NewsletterListingRepository; use MailPoet\Newsletter\Listing\NewsletterListingRepository;
use MailPoet\Newsletter\NewsletterSaveController; use MailPoet\Newsletter\NewsletterSaveController;
use MailPoet\Newsletter\NewslettersRepository; use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\NewsletterValidator;
use MailPoet\Newsletter\Preview\SendPreviewController; use MailPoet\Newsletter\Preview\SendPreviewController;
use MailPoet\Newsletter\Preview\SendPreviewException; use MailPoet\Newsletter\Preview\SendPreviewException;
use MailPoet\Newsletter\Scheduler\PostNotificationScheduler; use MailPoet\Newsletter\Scheduler\PostNotificationScheduler;
use MailPoet\Newsletter\Scheduler\Scheduler; use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Newsletter\Url as NewsletterUrl; use MailPoet\Newsletter\Url as NewsletterUrl;
use MailPoet\Newsletter\Validator;
use MailPoet\Settings\SettingsController; use MailPoet\Settings\SettingsController;
use MailPoet\UnexpectedValueException; use MailPoet\UnexpectedValueException;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature; use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
@ -76,11 +76,8 @@ class Newsletters extends APIEndpoint {
/** @var NewsletterUrl */ /** @var NewsletterUrl */
private $newsletterUrl; private $newsletterUrl;
/** @var TrackingConfig */ /** @var NewsletterValidator */
private $trackingConfig; private $newsletterValidator;
/** @var Validator */
private $validator;
/** @var Scheduler */ /** @var Scheduler */
private $scheduler; private $scheduler;
@ -100,7 +97,7 @@ class Newsletters extends APIEndpoint {
NewsletterSaveController $newsletterSaveController, NewsletterSaveController $newsletterSaveController,
NewsletterUrl $newsletterUrl, NewsletterUrl $newsletterUrl,
Scheduler $scheduler, Scheduler $scheduler,
Validator $validator NewsletterValidator $newsletterValidator
) { ) {
$this->listingHandler = $listingHandler; $this->listingHandler = $listingHandler;
$this->wp = $wp; $this->wp = $wp;
@ -116,7 +113,7 @@ class Newsletters extends APIEndpoint {
$this->newsletterSaveController = $newsletterSaveController; $this->newsletterSaveController = $newsletterSaveController;
$this->newsletterUrl = $newsletterUrl; $this->newsletterUrl = $newsletterUrl;
$this->scheduler = $scheduler; $this->scheduler = $scheduler;
$this->validator = $validator; $this->newsletterValidator = $newsletterValidator;
} }
public function get($data = []) { public function get($data = []) {
@ -188,7 +185,7 @@ class Newsletters extends APIEndpoint {
} }
if ($status === NewsletterEntity::STATUS_ACTIVE) { if ($status === NewsletterEntity::STATUS_ACTIVE) {
$validationError = $this->validator->validate($newsletter); $validationError = $this->newsletterValidator->validate($newsletter);
if ($validationError !== null) { if ($validationError !== null) {
return $this->errorResponse([APIError::FORBIDDEN => $validationError], [], Response::STATUS_FORBIDDEN); return $this->errorResponse([APIError::FORBIDDEN => $validationError], [], Response::STATUS_FORBIDDEN);
} }

View File

@ -14,10 +14,10 @@ use MailPoet\Mailer\MailerFactory;
use MailPoet\Models\Newsletter; use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue as SendingQueueModel; use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Newsletter\NewslettersRepository; use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\NewsletterValidator;
use MailPoet\Newsletter\Scheduler\Scheduler; use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Newsletter\Sending\ScheduledTasksRepository; use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
use MailPoet\Newsletter\Sending\SendingQueuesRepository; use MailPoet\Newsletter\Sending\SendingQueuesRepository;
use MailPoet\Newsletter\Validator;
use MailPoet\Segments\SubscribersFinder; use MailPoet\Segments\SubscribersFinder;
use MailPoet\Tasks\Sending as SendingTask; use MailPoet\Tasks\Sending as SendingTask;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature; use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
@ -45,12 +45,12 @@ class SendingQueue extends APIEndpoint {
/** @var MailerFactory */ /** @var MailerFactory */
private $mailerFactory; private $mailerFactory;
/** @var NewsletterValidator */
private $newsletterValidator;
/** @var Scheduler */ /** @var Scheduler */
private $scheduler; private $scheduler;
/** @var Validator */
private $validator;
public function __construct( public function __construct(
SubscribersFeature $subscribersFeature, SubscribersFeature $subscribersFeature,
NewslettersRepository $newsletterRepository, NewslettersRepository $newsletterRepository,
@ -59,7 +59,7 @@ class SendingQueue extends APIEndpoint {
ScheduledTasksRepository $scheduledTasksRepository, ScheduledTasksRepository $scheduledTasksRepository,
MailerFactory $mailerFactory, MailerFactory $mailerFactory,
Scheduler $scheduler, Scheduler $scheduler,
Validator $validator NewsletterValidator $newsletterValidator
) { ) {
$this->subscribersFeature = $subscribersFeature; $this->subscribersFeature = $subscribersFeature;
$this->subscribersFinder = $subscribersFinder; $this->subscribersFinder = $subscribersFinder;
@ -68,7 +68,7 @@ class SendingQueue extends APIEndpoint {
$this->scheduledTasksRepository = $scheduledTasksRepository; $this->scheduledTasksRepository = $scheduledTasksRepository;
$this->mailerFactory = $mailerFactory; $this->mailerFactory = $mailerFactory;
$this->scheduler = $scheduler; $this->scheduler = $scheduler;
$this->validator = $validator; $this->newsletterValidator = $newsletterValidator;
} }
public function add($data = []) { public function add($data = []) {
@ -97,7 +97,7 @@ class SendingQueue extends APIEndpoint {
]); ]);
} }
$validationError = $this->validator->validate($newsletterEntity); $validationError = $this->newsletterValidator->validate($newsletterEntity);
if ($validationError) { if ($validationError) {
return $this->errorResponse([ return $this->errorResponse([
APIError::BAD_REQUEST => $validationError, APIError::BAD_REQUEST => $validationError,

View File

@ -391,7 +391,7 @@ class ContainerConfigurator implements IContainerConfigurator {
$container->autowire(\MailPoet\Newsletter\AutomaticEmailsRepository::class)->setPublic(true); $container->autowire(\MailPoet\Newsletter\AutomaticEmailsRepository::class)->setPublic(true);
$container->autowire(\MailPoet\Newsletter\NewsletterHtmlSanitizer::class)->setPublic(true); $container->autowire(\MailPoet\Newsletter\NewsletterHtmlSanitizer::class)->setPublic(true);
$container->autowire(\MailPoet\Newsletter\Url::class)->setPublic(true); $container->autowire(\MailPoet\Newsletter\Url::class)->setPublic(true);
$container->autowire(\MailPoet\Newsletter\Validator::class)->setPublic(true); $container->autowire(\MailPoet\Newsletter\NewsletterValidator::class)->setPublic(true);
$container->autowire(\MailPoet\Newsletter\Links\Links::class)->setPublic(true); $container->autowire(\MailPoet\Newsletter\Links\Links::class)->setPublic(true);
$container->autowire(\MailPoet\Newsletter\Listing\NewsletterListingRepository::class)->setPublic(true); $container->autowire(\MailPoet\Newsletter\Listing\NewsletterListingRepository::class)->setPublic(true);
$container->autowire(\MailPoet\Newsletter\Options\NewsletterOptionsRepository::class)->setPublic(true); $container->autowire(\MailPoet\Newsletter\Options\NewsletterOptionsRepository::class)->setPublic(true);

View File

@ -7,7 +7,7 @@ use MailPoet\Services\Bridge;
use MailPoet\Settings\TrackingConfig; use MailPoet\Settings\TrackingConfig;
use MailPoet\Validator\ValidationException; use MailPoet\Validator\ValidationException;
class Validator { class NewsletterValidator {
/** @var Bridge */ /** @var Bridge */
private $bridge; private $bridge;
@ -27,7 +27,8 @@ class Validator {
try { try {
$this->validateBody($newsletterEntity); $this->validateBody($newsletterEntity);
$this->validateUnsubscribeRequirements($newsletterEntity); $this->validateUnsubscribeRequirements($newsletterEntity);
$this->validateReengagementRequirements($newsletterEntity); $this->validateReEngagementRequirements($newsletterEntity);
$this->validateAutomaticLatestContentRequirements($newsletterEntity);
} catch (ValidationException $exception) { } catch (ValidationException $exception) {
return __($exception->getMessage(), 'mailpoet'); return __($exception->getMessage(), 'mailpoet');
} }
@ -61,7 +62,7 @@ class Validator {
} }
} }
private function validateReengagementRequirements(NewsletterEntity $newsletterEntity): void { private function validateReEngagementRequirements(NewsletterEntity $newsletterEntity): void {
if ($newsletterEntity->getType() !== NewsletterEntity::TYPE_RE_ENGAGEMENT) { if ($newsletterEntity->getType() !== NewsletterEntity::TYPE_RE_ENGAGEMENT) {
return; return;
} }
@ -74,4 +75,17 @@ class Validator {
throw new ValidationException('Re-engagement emails are disabled because open and click tracking is disabled in MailPoet → Settings → Advanced.'); throw new ValidationException('Re-engagement emails are disabled because open and click tracking is disabled in MailPoet → Settings → Advanced.');
} }
} }
private function validateAutomaticLatestContentRequirements(NewsletterEntity $newsletterEntity) {
if ($newsletterEntity->getType() !== NewsletterEntity::TYPE_NOTIFICATION) {
return;
}
$content = $newsletterEntity->getContent();
if (
strpos($content, '"type":"automatedLatestContent"') === false &&
strpos($content, '"type":"automatedLatestContentLayout"') === false
) {
throw new ValidationException('Please add an “Automatic Latest Content” widget to the email from the right sidebar.');
}
}
} }

View File

@ -2,6 +2,7 @@
namespace MailPoet\Test\DataFactories; namespace MailPoet\Test\DataFactories;
use Codeception\Util\Fixtures;
use MailPoet\DI\ContainerWrapper; use MailPoet\DI\ContainerWrapper;
use MailPoet\Entities\NewsletterEntity; use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterOptionEntity; use MailPoet\Entities\NewsletterOptionEntity;
@ -12,6 +13,7 @@ use MailPoet\Entities\ScheduledTaskSubscriberEntity;
use MailPoet\Entities\SegmentEntity; use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SendingQueueEntity; use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Entities\SubscriberEntity; use MailPoet\Entities\SubscriberEntity;
use MailPoet\Util\Security;
use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Carbon\Carbon;
use MailPoetVendor\Doctrine\ORM\EntityManager; use MailPoetVendor\Doctrine\ORM\EntityManager;
@ -57,6 +59,10 @@ class Newsletter {
return $this; return $this;
} }
public function withDefaultBody() {
return $this->withBody(json_decode(Fixtures::get('newsletter_body_template'), true));
}
/** /**
* @return Newsletter * @return Newsletter
*/ */
@ -140,6 +146,11 @@ class Newsletter {
return $this; return $this;
} }
public function withReengagementType() {
$this->data['type'] = NewsletterEntity::TYPE_RE_ENGAGEMENT;
return $this;
}
/** /**
* @return Newsletter * @return Newsletter
*/ */
@ -332,6 +343,7 @@ class Newsletter {
$newsletter->setSenderAddress('john.doe@example.com'); $newsletter->setSenderAddress('john.doe@example.com');
$newsletter->setSenderName('John Doe'); $newsletter->setSenderName('John Doe');
} }
$newsletter->setHash(Security::generateHash());
if (isset($this->data['parent'])) $newsletter->setParent($this->data['parent']); if (isset($this->data['parent'])) $newsletter->setParent($this->data['parent']);
if (isset($this->data['deleted_at'])) $newsletter->setDeletedAt($this->data['deleted_at']); if (isset($this->data['deleted_at'])) $newsletter->setDeletedAt($this->data['deleted_at']);
return $newsletter; return $newsletter;

View File

@ -3,7 +3,6 @@
namespace MailPoet\Test\API\JSON\v1; namespace MailPoet\Test\API\JSON\v1;
use Codeception\Stub\Expected; use Codeception\Stub\Expected;
use Codeception\Util\Fixtures;
use Codeception\Util\Stub; use Codeception\Util\Stub;
use Helper\WordPressHooks as WPHooksHelper; use Helper\WordPressHooks as WPHooksHelper;
use MailPoet\API\JSON\Response as APIResponse; use MailPoet\API\JSON\Response as APIResponse;
@ -26,6 +25,7 @@ use MailPoet\Models\SendingQueue;
use MailPoet\Newsletter\Listing\NewsletterListingRepository; use MailPoet\Newsletter\Listing\NewsletterListingRepository;
use MailPoet\Newsletter\NewsletterSaveController; use MailPoet\Newsletter\NewsletterSaveController;
use MailPoet\Newsletter\NewslettersRepository; use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\NewsletterValidator;
use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository; use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
use MailPoet\Newsletter\Options\NewsletterOptionsRepository; use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
use MailPoet\Newsletter\Preview\SendPreviewController; use MailPoet\Newsletter\Preview\SendPreviewController;
@ -36,13 +36,12 @@ use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
use MailPoet\Newsletter\Sending\SendingQueuesRepository; use MailPoet\Newsletter\Sending\SendingQueuesRepository;
use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository; use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
use MailPoet\Newsletter\Url; use MailPoet\Newsletter\Url;
use MailPoet\Newsletter\Validator;
use MailPoet\Router\Router; use MailPoet\Router\Router;
use MailPoet\Segments\SegmentsRepository; use MailPoet\Segments\SegmentsRepository;
use MailPoet\Settings\SettingsController; use MailPoet\Settings\SettingsController;
use MailPoet\Tasks\Sending as SendingTask; use MailPoet\Tasks\Sending as SendingTask;
use MailPoet\Test\DataFactories\Newsletter;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature; use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
use MailPoet\Util\Security;
use MailPoet\WooCommerce\Helper as WCHelper; use MailPoet\WooCommerce\Helper as WCHelper;
use MailPoet\WP\Emoji; use MailPoet\WP\Emoji;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
@ -113,8 +112,8 @@ class NewslettersTest extends \MailPoetTest {
), ),
] ]
); );
$this->newsletter = $this->createNewsletter('My Standard Newsletter', NewsletterEntity::TYPE_STANDARD); $this->newsletter = (new Newsletter())->withDefaultBody()->withSubject('My Standard Newsletter')->create();
$this->postNotification = $this->createNewsletter('My Post Notification', NewsletterEntity::TYPE_NOTIFICATION); $this->postNotification = (new Newsletter())->withPostNotificationsType()->withSubject('My Post Notification')->loadBodyFrom('newsletterWithALC.json')->create();
$this->createNewsletterOptionField(NewsletterOptionFieldEntity::NAME_IS_SCHEDULED, NewsletterEntity::TYPE_STANDARD); $this->createNewsletterOptionField(NewsletterOptionFieldEntity::NAME_IS_SCHEDULED, NewsletterEntity::TYPE_STANDARD);
$this->createNewsletterOptionField(NewsletterOptionFieldEntity::NAME_SCHEDULED_AT, NewsletterEntity::TYPE_STANDARD); $this->createNewsletterOptionField(NewsletterOptionFieldEntity::NAME_SCHEDULED_AT, NewsletterEntity::TYPE_STANDARD);
@ -129,7 +128,7 @@ class NewslettersTest extends \MailPoetTest {
if (!$sentAt) { if (!$sentAt) {
continue; continue;
} }
$sentNewsletters[$i] = $this->createNewsletter("Sent newsletter {$i}", NewsletterEntity::TYPE_STANDARD); $sentNewsletters[$i] = (new Newsletter())->withSubject("Sent newsletter {$i}")->create();
$sentNewsletters[$i]->setSentAt($sentAt); $sentNewsletters[$i]->setSentAt($sentAt);
} }
$this->newsletterRepository->flush(); $this->newsletterRepository->flush();
@ -695,21 +694,10 @@ class NewslettersTest extends \MailPoetTest {
$this->diContainer->get(NewsletterSaveController::class), $this->diContainer->get(NewsletterSaveController::class),
$this->diContainer->get(Url::class), $this->diContainer->get(Url::class),
$this->scheduler, $this->scheduler,
$this->diContainer->get(Validator::class) $this->diContainer->get(NewsletterValidator::class)
); );
} }
private function createNewsletter(string $subject, string $type): NewsletterEntity {
$newsletter = new NewsletterEntity();
$newsletter->setSubject($subject);
$newsletter->setBody((array)json_decode(Fixtures::get('newsletter_body_template'), true));
$newsletter->setType($type);
$newsletter->setHash(Security::generateHash());
$this->newsletterRepository->persist($newsletter);
$this->newsletterRepository->flush();
return $newsletter;
}
private function createNewsletterOptionField(string $name, string $type): NewsletterOptionFieldEntity { private function createNewsletterOptionField(string $name, string $type): NewsletterOptionFieldEntity {
$optionField = new NewsletterOptionFieldEntity(); $optionField = new NewsletterOptionFieldEntity();
$optionField->setName($name); $optionField->setName($name);

View File

@ -13,12 +13,12 @@ use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\SendingQueueEntity; use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Mailer\MailerFactory; use MailPoet\Mailer\MailerFactory;
use MailPoet\Newsletter\NewslettersRepository; use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\NewsletterValidator;
use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository; use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
use MailPoet\Newsletter\Options\NewsletterOptionsRepository; use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
use MailPoet\Newsletter\Scheduler\Scheduler; use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Newsletter\Sending\ScheduledTasksRepository; use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
use MailPoet\Newsletter\Sending\SendingQueuesRepository; use MailPoet\Newsletter\Sending\SendingQueuesRepository;
use MailPoet\Newsletter\Validator;
use MailPoet\Segments\SubscribersFinder; use MailPoet\Segments\SubscribersFinder;
use MailPoet\Services\Bridge; use MailPoet\Services\Bridge;
use MailPoet\Settings\SettingsController; use MailPoet\Settings\SettingsController;
@ -86,7 +86,7 @@ class SendingQueueTest extends \MailPoetTest {
$this->diContainer->get(ScheduledTasksRepository::class), $this->diContainer->get(ScheduledTasksRepository::class),
$this->diContainer->get(MailerFactory::class), $this->diContainer->get(MailerFactory::class),
$this->diContainer->get(Scheduler::class), $this->diContainer->get(Scheduler::class),
$this->diContainer->get(Validator::class) $this->diContainer->get(NewsletterValidator::class)
); );
$res = $sendingQueue->add(['newsletter_id' => $this->newsletter->getId()]); $res = $sendingQueue->add(['newsletter_id' => $this->newsletter->getId()]);
expect($res->status)->equals(APIResponse::STATUS_FORBIDDEN); expect($res->status)->equals(APIResponse::STATUS_FORBIDDEN);
@ -169,7 +169,7 @@ class SendingQueueTest extends \MailPoetTest {
$this->diContainer->get(ScheduledTasksRepository::class), $this->diContainer->get(ScheduledTasksRepository::class),
$this->diContainer->get(MailerFactory::class), $this->diContainer->get(MailerFactory::class),
$this->diContainer->get(Scheduler::class), $this->diContainer->get(Scheduler::class),
new Validator(Stub::make(Bridge::class, [ new NewsletterValidator(Stub::make(Bridge::class, [
'isMailpoetSendingServiceEnabled' => true, 'isMailpoetSendingServiceEnabled' => true,
]), $this->diContainer->get(TrackingConfig::class)) ]), $this->diContainer->get(TrackingConfig::class))
); );

View File

@ -0,0 +1,109 @@
<?php
namespace MailPoet\Test\Newsletter;
use Codeception\Util\Stub;
use MailPoet\Newsletter\NewsletterValidator;
use MailPoet\Services\Bridge;
use MailPoet\Settings\TrackingConfig;
use MailPoet\Test\DataFactories\Newsletter;
class NewsletterValidatorTest extends \MailPoetTest {
/** @var NewsletterValidator */
private $newsletterValidator;
public function _before() {
parent::_before();
$this->newsletterValidator = $this->diContainer->get(NewsletterValidator::class);
}
public function testUnsubscribeFooterIsNotRequiredIfNotUsingMSS() {
$newsletter = (new Newsletter())->loadBodyFrom('newsletterWithTextNoFooter.json')->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->null();
}
public function testUnsubscribeFooterRequiredIfUsingMSS() {
$newsletter = (new Newsletter())->loadBodyFrom('newsletterWithTextNoFooter.json')->create();
$bridge = Stub::make(Bridge::class, ['isMailpoetSendingServiceEnabled' => true]);
$validator = $this->getServiceWithOverrides(NewsletterValidator::class, ['bridge' => $bridge]);
$validationError = $validator->validate($newsletter);
expect($validationError)->equals('All emails must include an "Unsubscribe" link. Add a footer widget to your email to continue.');
}
public function testItRequiresBodyContent() {
$newsletter = (new Newsletter())->withBody('')->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->equals('Poet, please add prose to your masterpiece before you send it to your followers.');
}
public function testItRequiresContentBlocks() {
$newsletter = (new Newsletter())->withBody(['content' => ['type' => 'container', 'columnLayout' => false, 'orientation' => 'vertical', 'blocks' => []]])->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->equals('Poet, please add prose to your masterpiece before you send it to your followers.');
}
public function testItIsValidWithAContentBlock() {
$newsletter = (new Newsletter())->withBody(['content' => ['type' => 'container', 'columnLayout' => false, 'orientation' => 'vertical', 'blocks' => [
[
'type' => 'text',
'text' => 'Some text'
]
]]])->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->null();
}
public function testItRequiresReengagementShortcodes() {
$newsletter = (new Newsletter())->withReengagementType()->withDefaultBody()->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->equals('A re-engagement email must include a link with [link:subscription_re_engage_url] shortcode.');
}
public function testReengagementNewsletterIsValidWithRequiredShortcode() {
$newsletter = (new Newsletter())->withReengagementType()->withBody([
'content' => [
'blocks' => [
[
'type' => 'text',
'text' => '[link:subscription_re_engage_url]',
]
]
]
])->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->null();
}
public function testItRequiresTrackingForReengagementEmails() {
$newsletter = (new Newsletter())->withReengagementType()->withBody([
'content' => [
'blocks' => [
[
'type' => 'text',
'text' => '[link:subscription_re_engage_url]',
]
]
]
])->create();
$validator = $this->getServiceWithOverrides(NewsletterValidator::class, [
'trackingConfig' => Stub::make(TrackingConfig::class, ['isEmailTrackingEnabled' => false])
]);
$validationError = $validator->validate($newsletter);
expect($validationError)->equals('Re-engagement emails are disabled because open and click tracking is disabled in MailPoet → Settings → Advanced.');
}
public function testAlcEmailFailsValidationWithoutAlcBlock() {
$newsletter = (new Newsletter())->withDefaultBody()->withPostNotificationsType()->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->equals('Please add an “Automatic Latest Content” widget to the email from the right sidebar.');
}
public function testAlcEmailPassesWithAlcBlock() {
$newsletter = (new Newsletter())->loadBodyFrom('newsletterWithALC.json')->withPostNotificationsType()->create();
$validationError = $this->newsletterValidator->validate($newsletter);
expect($validationError)->null();
}
}

View File

@ -183,6 +183,29 @@ abstract class MailPoetTest extends \Codeception\TestCase\Test { // phpcs:ignore
return $method->invokeArgs($object, $parameters); return $method->invokeArgs($object, $parameters);
} }
/**
* Retrieve a clone of a DI service with specific private/protected properties replaced
*
* @template T of object
* @param class-string<T> $id
* @param array<string, Object> $overrides
* string = protected/private property name
* Object = replacement for that property
* @return T
*/
public function getServiceWithOverrides(string $id, array $overrides) {
$instance = $this->diContainer->get($id);
$clone = clone $instance;
$reflection = new \ReflectionClass($clone);
foreach ($overrides as $propertyName => $override) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($clone, $override);
$property->setAccessible(false);
}
return $clone;
}
public static function markTestSkipped(string $message = ''): void { public static function markTestSkipped(string $message = ''): void {
$branchName = getenv('CIRCLE_BRANCH'); $branchName = getenv('CIRCLE_BRANCH');
if ($branchName === 'master' || $branchName === 'release') { if ($branchName === 'master' || $branchName === 'release') {