true]; /** @var SendingThrottlingHandler */ private $sendingThrottlingHandler; /** @var SendingErrorHandler */ private $sendingErrorHandler; /** @var SettingsController */ private $settings; /** @var StatsNotificationsScheduler */ private $statsNotificationsWorker; /** @var LoggerFactory */ private $loggerFactory; /** @var NewslettersRepository */ private $newslettersRepository; /** @var SubscribersFinder */ private $subscribersFinder; /** @var SegmentsRepository */ private $segmentsRepository; /** @var WPFunctions */ private $wp; /** @var TasksLinks */ private $tasksLinks; /** @var ScheduledTasksRepository */ private $scheduledTasksRepository; /** @var SubscribersRepository */ private $subscribersRepository; /** @var SendingQueuesRepository */ private $sendingQueuesRepository; /** @var ScheduledTaskSubscribersRepository */ private $scheduledTaskSubscribersRepository; /** @var ScheduledTaskEntity */ private $scheduledTask; /** @var StatisticsNewslettersRepository */ private $statisticsNewslettersRepository; /** @var AuthorizedEmailsController */ private $authorizedEmailsController; public function _before() { parent::_before(); $wpUsers = get_users(); wp_set_current_user($wpUsers[0]->ID); $this->settings = $this->diContainer->get(SettingsController::class); $populator = $this->diContainer->get(Populator::class); $populator->up(); $this->wp = $this->diContainer->get(WPFunctions::class); $this->newslettersRepository = $this->diContainer->get(NewslettersRepository::class); $this->scheduledTaskSubscribersRepository = $this->diContainer->get(ScheduledTaskSubscribersRepository::class); $this->segmentsRepository = $this->diContainer->get(SegmentsRepository::class); $this->segment = (new SegmentFactory())->withName('segment')->create(); $this->subscriber = $this->createSubscriber('john@doe.com', 'John', 'Doe', [$this->segment]); $this->newsletter = (new NewsletterFactory()) ->withType(NewsletterEntity::TYPE_STANDARD) ->withStatus(NewsletterEntity::STATUS_ACTIVE) ->withSubject(Fixtures::get('newsletter_subject_template')) ->withBody(Fixtures::get('newsletter_body_template')) ->withSegments([$this->segment]) ->create(); $this->sendingQueue = $this->createQueueWithTask($this->newsletter); $scheduledTask = $this->sendingQueue->getTask(); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->scheduledTask = $scheduledTask; $this->scheduledTaskSubscribersRepository->setSubscribers($this->scheduledTask, [$this->subscriber->getId()]); $queue = $this->newsletter->getLatestQueue(); $this->assertInstanceOf(SendingQueueEntity::class, $queue); $this->newsletterLink = new NewsletterLinkEntity( $this->newsletter, $queue, '[link:subscription_instant_unsubscribe_url]', 'abcde' ); $this->entityManager->persist($this->newsletterLink); $this->entityManager->flush(); $this->authorizedEmailsController = $this->diContainer->get(AuthorizedEmailsController::class); $this->subscribersFinder = $this->diContainer->get(SubscribersFinder::class); $this->sendingErrorHandler = $this->diContainer->get(SendingErrorHandler::class); $this->sendingThrottlingHandler = $this->diContainer->get(SendingThrottlingHandler::class); $this->statsNotificationsWorker = $this->makeEmpty(StatsNotificationsScheduler::class); $this->loggerFactory = LoggerFactory::getInstance(); $this->cronHelper = $this->diContainer->get(CronHelper::class); $this->tasksLinks = $this->diContainer->get(TasksLinks::class); $this->scheduledTasksRepository = $this->diContainer->get(ScheduledTasksRepository::class); $this->subscribersRepository = $this->diContainer->get(SubscribersRepository::class); $this->sendingQueuesRepository = $this->diContainer->get(SendingQueuesRepository::class); $this->statisticsNewslettersRepository = $this->diContainer->get(StatisticsNewslettersRepository::class); $this->sendingQueueWorker = $this->getSendingQueueWorker(); } private function getDirectUnsubscribeURL() { return SubscriptionUrlFactory::getInstance()->getUnsubscribeUrl($this->subscriber, (int)$this->sendingQueue->getId()); } private function getTrackedUnsubscribeURL() { $linkTokens = $this->diContainer->get(LinkTokens::class); $links = $this->diContainer->get(Links::class); $data = $links->createUrlDataObject( $this->subscriber->getId(), $linkTokens->getToken($this->subscriber), $this->sendingQueue->getId(), $this->newsletterLink->getHash(), false ); return Router::buildRequest( Track::ENDPOINT, Track::ACTION_CLICK, $data ); } public function testItConstructs() { expect($this->sendingQueueWorker->mailerTask instanceof MailerTask); expect($this->sendingQueueWorker->newsletterTask instanceof NewsletterTask); } public function testItReturnsCorrectBatchSize(): void { verify($this->sendingQueueWorker->getBatchSize())->equals(SendingThrottlingHandler::BATCH_SIZE); } public function testItEnforcesExecutionLimitsBeforeQueueProcessing() { $sendingQueueWorker = $this->make( $this->getSendingQueueWorker(), [ 'processQueue' => Expected::never(), 'enforceSendingAndExecutionLimits' => Expected::exactly(1, function() { throw new \Exception(); }), ]); $sendingQueueWorker->__construct( $this->sendingErrorHandler, $this->sendingThrottlingHandler, $this->statsNotificationsWorker, $this->loggerFactory, $this->cronHelper, $this->subscribersFinder, $this->segmentsRepository, $this->wp, $this->tasksLinks, $this->scheduledTasksRepository, $this->scheduledTaskSubscribersRepository, $this->diContainer->get(MailerTask::class), $this->subscribersRepository, $this->sendingQueuesRepository, $this->entityManager, $this->statisticsNewslettersRepository, $this->authorizedEmailsController ); try { $sendingQueueWorker->process(); self::fail('Execution limits function was not called.'); } catch (\Exception $e) { // No exception handling needed } } public function testItEnforcesExecutionLimitsAfterSendingWhenQueueStatusIsNotSetToComplete() { $sendingQueueWorker = $this->make( $this->getSendingQueueWorker(), [ 'enforceSendingAndExecutionLimits' => Expected::exactly(1), ]); $sendingQueueWorker->__construct( $this->sendingErrorHandler, $this->sendingThrottlingHandler, $this->statsNotificationsWorker, $this->loggerFactory, $this->cronHelper, $this->subscribersFinder, $this->segmentsRepository, $this->wp, $this->tasksLinks, $this->scheduledTasksRepository, $this->scheduledTaskSubscribersRepository, $this->make( new MailerTask($this->diContainer->get(MailerFactory::class)), [ 'sendBulk' => $this->mailerTaskDummyResponse, ] ), $this->subscribersRepository, $this->sendingQueuesRepository, $this->entityManager, $this->statisticsNewslettersRepository, $this->authorizedEmailsController ); $sendingQueueWorker->sendNewsletters( $this->scheduledTask, $preparedSubscribers = [], $preparedNewsletters = [], $preparedSubscribers = [], $statistics[] = [ 'newsletter_id' => 1, 'subscriber_id' => 1, 'queue_id' => $this->sendingQueue->getId(), ], microtime(true) ); } public function testItDoesNotEnforceExecutionLimitsAfterSendingWhenQueueStatusIsSetToComplete() { // when sending is done and there are no more subscribers to process, continue // without enforcing execution limits. this allows the newsletter to be marked as sent // in the process() method and after that execution limits will be enforced $this->scheduledTask->setStatus(SendingQueueEntity::STATUS_COMPLETED); $this->entityManager->persist($this->scheduledTask); $this->entityManager->flush(); $sendingQueueWorker = $this->make( $this->getSendingQueueWorker(), [ 'enforceSendingAndExecutionLimits' => Expected::never(), ]); $sendingQueueWorker->__construct( $this->sendingErrorHandler, $this->sendingThrottlingHandler, $this->statsNotificationsWorker, $this->loggerFactory, $this->cronHelper, $this->subscribersFinder, $this->segmentsRepository, $this->wp, $this->tasksLinks, $this->scheduledTasksRepository, $this->scheduledTaskSubscribersRepository, $this->make( new MailerTask($this->diContainer->get(MailerFactory::class)), [ 'sendBulk' => $this->mailerTaskDummyResponse, ] ), $this->subscribersRepository, $this->sendingQueuesRepository, $this->entityManager, $this->statisticsNewslettersRepository, $this->authorizedEmailsController ); $sendingQueueWorker->sendNewsletters( $this->scheduledTask, $preparedSubscribers = [], $preparedNewsletters = [], $preparedSubscribers = [], $statistics[] = [ 'newsletter_id' => 1, 'subscriber_id' => 1, 'queue_id' => $this->sendingQueue->getId(), ], microtime(true) ); } public function testItEnforcesExecutionLimitsAfterQueueProcessing() { $sendingQueueWorker = $this->make( $this->getSendingQueueWorker(), [ 'processQueue' => function() { // this function returns a queue object return (object)['status' => null, 'taskId' => 0]; }, 'enforceSendingAndExecutionLimits' => Expected::exactly(2), ]); $sendingQueueWorker->__construct( $this->sendingErrorHandler, $this->sendingThrottlingHandler, $this->statsNotificationsWorker, $this->loggerFactory, $this->cronHelper, $this->subscribersFinder, $this->segmentsRepository, $this->wp, $this->tasksLinks, $this->scheduledTasksRepository, $this->scheduledTaskSubscribersRepository, $this->diContainer->get(MailerTask::class), $this->subscribersRepository, $this->sendingQueuesRepository, $this->entityManager, $this->statisticsNewslettersRepository, $this->authorizedEmailsController ); $sendingQueueWorker->process(); } public function testItPassesExtraParametersToMailerWhenTrackingIsDisabled() { $this->settings->set('tracking.level', TrackingConfig::LEVEL_BASIC); $directUnsubscribeURL = $this->getDirectUnsubscribeURL(); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function($newsletter, $subscriber, $extraParams) use ($directUnsubscribeURL) { verify(isset($extraParams['unsubscribe_url']))->true(); verify($extraParams['unsubscribe_url'])->equals($directUnsubscribeURL); verify(isset($extraParams['meta']))->true(); verify($extraParams['meta']['email_type'])->equals('newsletter'); verify($extraParams['meta']['subscriber_status'])->equals('subscribed'); verify($extraParams['meta']['subscriber_source'])->equals('administrator'); return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); } public function testItPassesExtraParametersToMailerWhenTrackingIsEnabled() { $this->settings->set('tracking.level', TrackingConfig::LEVEL_PARTIAL); $trackedUnsubscribeURL = $this->getTrackedUnsubscribeURL(); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function($newsletter, $subscriber, $extraParams) use ($trackedUnsubscribeURL) { verify(isset($extraParams['unsubscribe_url']))->true(); verify($extraParams['unsubscribe_url'])->equals($trackedUnsubscribeURL); verify(isset($extraParams['meta']))->true(); verify($extraParams['meta']['email_type'])->equals('newsletter'); verify($extraParams['meta']['subscriber_status'])->equals('subscribed'); verify($extraParams['meta']['subscriber_source'])->equals('administrator'); return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); } public function testItCanProcessSubscribersOneByOne() { $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function($newsletter, $subscriber, $extraParams) { // newsletter body should not be empty verify(!empty($newsletter['body']['html']))->true(); verify(!empty($newsletter['body']['text']))->true(); return $this->mailerTaskDummyResponse; }), ] ) ); $this->subscriber->setEngagementScoreUpdatedAt(Carbon::now()->subDays(5)); $this->entityManager->flush(); $this->entityManager->refresh($this->subscriber); verify($this->subscriber->getLastSendingAt())->null(); verify($this->subscriber->getEngagementScoreUpdatedAt())->notNull(); $sendingQueueWorker->process(); $this->subscribersRepository->refresh($this->subscriber); verify($this->subscriber->getLastSendingAt())->notNull(); verify($this->subscriber->getEngagementScoreUpdatedAt())->null(); // newsletter status is set to sent verify($this->newsletter->getStatus())->equals(NewsletterEntity::STATUS_SENT); // queue status is set to completed $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $scheduledTask = $this->scheduledTasksRepository->findOneBySendingQueue($sendingQueue); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); verify($scheduledTask->getStatus())->equals(SendingQueueEntity::STATUS_COMPLETED); // queue subscriber processed/to process count is updated verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->arrayCount(0); $processedSubscribers = $scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED); verify($processedSubscribers)->equals([$this->subscriber]); verify($sendingQueue->getCountTotal())->equals(1); verify($sendingQueue->getCountProcessed())->equals(1); verify($sendingQueue->getCountToProcess())->equals(0); // statistics entry should be created $statistics = $this->statisticsNewslettersRepository->findOneBy([ 'newsletter' => $this->newsletter, 'subscriber' => $this->subscriber, 'queue' => $this->sendingQueue, ]); verify($statistics)->notEquals(false); } public function testItSendCorrectDataToSubscribersOneByOne() { $subscribersRepository = ContainerWrapper::getInstance()->get(SubscribersRepository::class); $subscriber1 = $this->createSubscriber('1@localhost.com', 'firstName', 'lastName'); $subscriber1->setStatus(SubscriberEntity::STATUS_SUBSCRIBED); $subscriber1->setSource('form'); $subscriber1->setEmail('1@localhost.com'); $subscribersRepository->persist($subscriber1); $subscriber2 = $this->createSubscriber('2@lcoalhost.com', 'first', 'last'); $subscriber2->setStatus(SubscriberEntity::STATUS_SUBSCRIBED); $subscriber2->setSource('form'); $subscriber2->setEmail('2@localhost.com'); $subscribersRepository->persist($subscriber2); $sendingQueue = $this->createQueueWithTask($this->newsletter); $sendingQueue->setNewsletterRenderedBody(['html' => '
Hello [subscriber:email]
', 'text' => 'Hello [subscriber:email]']); $sendingQueue->setNewsletterRenderedSubject('News for [subscriber:email]'); $scheduledTask = $sendingQueue->getTask(); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->scheduledTaskSubscribersRepository->setSubscribers($scheduledTask, [$subscriber1->getId(), $subscriber2->getId()]); $this->settings->set('tracking.level', TrackingConfig::LEVEL_BASIC); $newsletter = $this->newsletter; $timer = 1000000000000000000; $sendingQueueWorker = $this->getSendingQueueWorker( $this->make( MailerTask::class, [ 'prepareSubscriberForSending' => function($subscriber) { return $subscriber->getEmail(); }, 'getProcessingMethod' => 'individual', 'send' => Expected::exactly(2, function($newsletter, $subscriberEmail, $extraParams) use ($subscribersRepository, $sendingQueue) { $subscriber = $subscribersRepository->findOneBy(['email' => $subscriberEmail]); $subscriptionUrlFactory = SubscriptionUrlFactory::getInstance(); $unsubscribeUrl = $subscriptionUrlFactory->getUnsubscribeUrl($subscriber, (int)$sendingQueue->getId()); verify($newsletter['subject'])->equals('News for ' . $subscriberEmail); verify($newsletter['body']['html'])->equals('Hello ' . $subscriberEmail . '
'); verify($newsletter['body']['text'])->equals('Hello ' . $subscriberEmail); verify($extraParams['meta']['email_type'])->equals('newsletter'); verify($extraParams['meta']['subscriber_status'])->equals(SubscriberEntity::STATUS_SUBSCRIBED); verify($extraParams['meta']['subscriber_source'])->equals('form'); verify($extraParams['unsubscribe_url'])->equals($unsubscribeUrl); return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->processQueue($scheduledTask, $newsletter, [$subscriber1, $subscriber2], $timer); } public function testItCanProcessSubscribersInBulk() { $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'sendBulk' => Expected::exactly(1, function($newsletter, $subscriber) { // newsletter body should not be empty verify(!empty($newsletter[0]['body']['html']))->true(); verify(!empty($newsletter[0]['body']['text']))->true(); return $this->mailerTaskDummyResponse; }), 'getProcessingMethod' => Expected::exactly(1, function() { return 'bulk'; }), ] ) ); verify($this->subscriber->getLastSendingAt())->null(); $sendingQueueWorker->process(); $this->subscribersRepository->refresh($this->subscriber); verify($this->subscriber->getLastSendingAt())->notNull(); // newsletter status is set to sent verify($this->newsletter->getStatus())->equals(NewsletterEntity::STATUS_SENT); // queue status is set to completed $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $scheduledTask = $this->scheduledTasksRepository->findOneBySendingQueue($sendingQueue); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); verify($scheduledTask->getStatus())->equals(SendingQueueEntity::STATUS_COMPLETED); // queue subscriber processed/to process count is updated verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->arrayCount(0); $processedSubscribers = $scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED); verify($processedSubscribers)->equals([$this->subscriber]); verify($sendingQueue->getCountTotal())->equals(1); verify($sendingQueue->getCountProcessed())->equals(1); verify($sendingQueue->getCountToProcess())->equals(0); // statistics entry should be created $statistics = $this->statisticsNewslettersRepository->findOneBy([ 'newsletter' => $this->newsletter, 'subscriber' => $this->subscriber, 'queue' => $this->sendingQueue, ]); verify($statistics)->notEquals(false); } public function testItProcessesStandardNewsletters() { $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function($newsletter, $subscriber) { // newsletter body should not be empty verify(!empty($newsletter['body']['html']))->true(); verify(!empty($newsletter['body']['text']))->true(); return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); // queue status is set to completed $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $scheduledTask = $this->scheduledTasksRepository->findOneBySendingQueue($sendingQueue); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); verify($scheduledTask->getStatus())->equals(SendingQueueEntity::STATUS_COMPLETED); // newsletter status is set to sent and sent_at date is populated $updatedNewsletter = $this->newslettersRepository->findOneById($this->newsletter->getId()); $this->assertInstanceOf(NewsletterEntity::class, $updatedNewsletter); verify($updatedNewsletter->getStatus())->equals(NewsletterEntity::STATUS_SENT); verify($updatedNewsletter->getSentAt())->equalsWithDelta($scheduledTask->getProcessedAt(), 1); // queue subscriber processed/to process count is updated verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->arrayCount(0); $processedSubscribers = $scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED); verify($processedSubscribers)->equals([$this->subscriber]); verify($sendingQueue->getCountTotal())->equals(1); verify($sendingQueue->getCountProcessed())->equals(1); verify($sendingQueue->getCountToProcess())->equals(0); // statistics entry should be created $statistics = $this->statisticsNewslettersRepository->findOneBy([ 'newsletter' => $this->newsletter, 'subscriber' => $this->subscriber, 'queue' => $this->sendingQueue, ]); verify($statistics)->notEquals(false); } public function testItHandlesSendingErrorCorrectly() { $wrongSubscriber = $this->createSubscriber('doe@john.com>', 'Doe', 'John'); $sendingQueue = $this->createQueueWithTask($this->newsletter); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $scheduledTask = $sendingQueue->getTask(); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->scheduledTaskSubscribersRepository->setSubscribers($scheduledTask, [$this->subscriber->getId(), $wrongSubscriber->getId()]); // Error that simulates sending error from the bridge $mailerError = new MailerError( MailerError::OPERATION_SEND, MailerError::LEVEL_SOFT, 'Error while sending.', null, [new SubscriberError($wrongSubscriber->getEmail(), 'must be an email')] ); $sendingQueueWorker = $this->make( $this->getSendingQueueWorker() ); $sendingQueueWorker->__construct( $this->sendingErrorHandler, $this->sendingThrottlingHandler, $this->statsNotificationsWorker, $this->loggerFactory, $this->cronHelper, $this->subscribersFinder, $this->segmentsRepository, $this->wp, $this->tasksLinks, $this->scheduledTasksRepository, $this->scheduledTaskSubscribersRepository, $this->make( new MailerTask($this->diContainer->get(MailerFactory::class)), [ 'sendBulk' => Stub::consecutive(['response' => false, 'error' => $mailerError], $this->mailerTaskDummyResponse), ] ), $this->subscribersRepository, $this->sendingQueuesRepository, $this->entityManager, $this->statisticsNewslettersRepository, $this->authorizedEmailsController ); $sendingQueueWorker->sendNewsletters( $scheduledTask, [$this->subscriber->getId(), $wrongSubscriber->getId()], [], [$this->subscriber->getEmail(), $wrongSubscriber->getEmail()], ['newsletter_id' => 1, 'subscriber_id' => 1, 'queue_id' => $sendingQueue->getId()], microtime(true) ); // compare data after first sending $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED))->equals([$this->subscriber]); verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED))->equals([$wrongSubscriber]); verify($sendingQueue->getCountTotal())->equals(2); verify($sendingQueue->getCountProcessed())->equals(1); verify($sendingQueue->getCountToProcess())->equals(1); $sendingQueueWorker->sendNewsletters( $scheduledTask, [$this->subscriber->getId()], [], [$this->subscriber->getEmail()], ['newsletter_id' => 1, 'subscriber_id' => 1, 'queue_id' => $sendingQueue->getId()], microtime(true) ); // load queue and compare data after second sending $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED))->equals([]); verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED))->equals([$this->subscriber, $wrongSubscriber]); verify($sendingQueue->getCountTotal())->equals(2); verify($sendingQueue->getCountProcessed())->equals(2); verify($sendingQueue->getCountToProcess())->equals(0); } public function testItUpdatesUpdateTime() { $originalUpdated = Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))->subHours(5); $this->setUpdatedAtForEntity($this->scheduledTask, $originalUpdated); $this->newsletter->setType(NewsletterEntity::TYPE_WELCOME); $this->newsletter->getNewsletterSegments()->clear(); $this->entityManager->flush(); $sendingQueueWorker = $this->getSendingQueueWorker( $this->makeEmpty(MailerTask::class, []) ); $sendingQueueWorker->process(); verify($this->scheduledTask->getUpdatedAt())->notEquals($originalUpdated); } public function testItCanProcessWelcomeNewsletters() { $this->newsletter->setType(NewsletterEntity::TYPE_WELCOME); $this->newsletter->getNewsletterSegments()->clear(); $this->entityManager->flush(); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function($newsletter, $subscriber) { // newsletter body should not be empty verify(!empty($newsletter['body']['html']))->true(); verify(!empty($newsletter['body']['text']))->true(); return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); // newsletter status is set to sent verify($this->newsletter->getStatus())->equals(NewsletterEntity::STATUS_ACTIVE); // queue status is set to completed verify($this->scheduledTask->getStatus())->equals(SendingQueueEntity::STATUS_COMPLETED); // queue subscriber processed/to process count is updated verify($this->scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->equals([]); verify($this->scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED)) ->equals([$this->subscriber]); verify($this->sendingQueue->getCountTotal())->equals(1); verify($this->sendingQueue->getCountProcessed())->equals(1); verify($this->sendingQueue->getCountToProcess())->equals(0); // statistics entry should be created $statistics = $this->statisticsNewslettersRepository->findOneBy([ 'newsletter' => $this->newsletter, 'subscriber' => $this->subscriber, 'queue' => $this->sendingQueue, ]); verify($statistics)->notEquals(false); } public function testItPreventsSendingWelcomeEmailWhenSubscriberIsUnsubscribed() { $this->newsletter->setType(NewsletterEntity::TYPE_WELCOME); $this->subscriber->setStatus(SubscriberEntity::STATUS_UNSUBSCRIBED); $this->newsletter->getNewsletterSegments()->clear(); $this->entityManager->flush(); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(0), ] ) ); $sendingQueueWorker->process(); // queue status is set to completed $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $scheduledTask = $this->scheduledTasksRepository->findOneBySendingQueue($sendingQueue); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED)) ->equals([]); verify($sendingQueue->getCountTotal())->equals(0); verify($sendingQueue->getCountProcessed())->equals(0); verify($sendingQueue->getCountToProcess())->equals(0); } public function testItPreventsSendingNewsletterToRecipientWhoIsUnsubscribed() { $subscriberFactory = new \MailPoet\Test\DataFactories\Subscriber(); $unsubscribedSubscriber = $subscriberFactory ->withStatus(SubscriberEntity::STATUS_UNSUBSCRIBED) ->create(); $this->scheduledTaskSubscribersRepository->setSubscribers( $this->scheduledTask, [$this->subscriber->getId(), $unsubscribedSubscriber->getId()] ); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function() { return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); // queue status is set to completed $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $scheduledTask = $this->scheduledTasksRepository->findOneBySendingQueue($sendingQueue); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); // Unprocessable subscribers were removed verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED)) ->equals([$this->subscriber]); // subscriber that should be processed verify($sendingQueue->getCountTotal())->equals(1); verify($sendingQueue->getCountProcessed())->equals(1); verify($sendingQueue->getCountToProcess())->equals(0); } public function testItRemovesSubscribersFromProcessingListWhenNewsletterHasSegmentAndSubscriberIsNotPartOfIt() { $subscriberNotPartOfNewsletterSegment = $this->createSubscriber('subscriber1@mailpoet.com', 'Subscriber', 'One'); $this->scheduledTaskSubscribersRepository->setSubscribers( $this->scheduledTask, [$this->subscriber->getId(), $subscriberNotPartOfNewsletterSegment->getId()] ); $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function() { return $this->mailerTaskDummyResponse; }), ] ); $sendingQueueWorker->process(); // queue subscriber processed/to process count is updated verify($this->scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->equals([]); verify($this->scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED)) ->equals([$this->subscriber]); verify($this->sendingQueue->getCountTotal())->equals(1); verify($this->sendingQueue->getCountProcessed())->equals(1); verify($this->sendingQueue->getCountToProcess())->equals(0); // statistics entry should be created only for 1 subscriber $statistics = $this->statisticsNewslettersRepository->findAll(); verify(count($statistics))->equals(1); } public function testItRemovesSubscribersFromProcessingListWhenNewsletterHasNoSegment() { $this->newsletter->getNewsletterSegments()->clear(); $invalidSubscriberId = 99999; $this->scheduledTaskSubscribersRepository->setSubscribers( $this->scheduledTask, [$this->subscriber->getId(), $invalidSubscriberId] ); $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function() { return $this->mailerTaskDummyResponse; }), ] ); $sendingQueueWorker->process(); $scheduledTask = $this->scheduledTasksRepository->findOneBySendingQueue($this->sendingQueue); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->sendingQueuesRepository->refresh($this->sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); // queue subscriber processed/to process count is updated verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->equals([]); verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED)) ->equals([$this->subscriber]); verify($this->sendingQueue->getCountTotal())->equals(1); verify($this->sendingQueue->getCountProcessed())->equals(1); verify($this->sendingQueue->getCountToProcess())->equals(0); // statistics entry should be created only for 1 subscriber $statistics = $this->statisticsNewslettersRepository->findAll(); verify(count($statistics))->equals(1); } public function testItDoesNotCallMailerWithEmptyBatch() { $subscribers = []; while (count($subscribers) < 2 * SendingThrottlingHandler::BATCH_SIZE) { $subscribers[] = 1234564545 + count($subscribers); } $subscribers[] = $this->subscriber->getId(); $this->scheduledTaskSubscribersRepository->setSubscribers($this->scheduledTask, $subscribers); $this->sendingQueue->setCountTotal(count($subscribers)); $this->entityManager->persist($this->sendingQueue); $this->entityManager->flush(); $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function() { return $this->mailerTaskDummyResponse; }), ] ); $sendingQueueWorker->process(); $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $scheduledTask = $this->scheduledTasksRepository->findOneBySendingQueue($sendingQueue); $this->assertInstanceOf(ScheduledTaskEntity::class, $scheduledTask); $this->sendingQueuesRepository->refresh($sendingQueue); $this->scheduledTasksRepository->refresh($scheduledTask); // queue subscriber processed/to process count is updated verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->equals([]); verify($scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED)) ->equals([$this->subscriber]); verify($sendingQueue->getCountTotal())->equals(1); verify($sendingQueue->getCountProcessed())->equals(1); verify($sendingQueue->getCountToProcess())->equals(0); } public function testItUpdatesQueueSubscriberCountWhenNoneOfSubscribersExist() { $this->scheduledTaskSubscribersRepository->setSubscribers($this->scheduledTask, [123, 456,]); $this->sendingQueue->setCountTotal(2); $this->entityManager->persist($this->sendingQueue); $this->entityManager->flush(); $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], ['send' => $this->mailerTaskDummyResponse] ); $sendingQueueWorker->process(); // queue subscriber processed/to process count is updated verify($this->scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED)) ->equals([]); verify($this->scheduledTask->getSubscribersByProcessed(ScheduledTaskSubscriberEntity::STATUS_PROCESSED)) ->equals([]); verify($this->sendingQueue->getCountTotal())->equals(0); verify($this->sendingQueue->getCountProcessed())->equals(0); verify($this->sendingQueue->getCountToProcess())->equals(0); } public function testItDoesNotSendToTrashedSubscribers() { $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], ['send' => $this->mailerTaskDummyResponse] ); // newsletter is not sent to trashed subscriber $subscriber = $this->subscriber; $subscriber->setDeletedAt(Carbon::now()); $this->entityManager->flush(); $sendingQueueWorker->process(); $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $this->sendingQueuesRepository->refresh($sendingQueue); verify($sendingQueue->getCountTotal())->equals(0); } /** * @dataProvider dataForTestItSendsTransactionalEmails */ public function testItSendsTransactionalEmails(string $subscriberStatus, bool $expectSending) { $this->newsletter->setType(NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL); $this->newsletter->getNewsletterSegments()->clear(); $this->entityManager->flush(); $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], ['send' => $this->mailerTaskDummyResponse] ); $subscriber = $this->subscriber; $subscriber->setStatus($subscriberStatus); $this->entityManager->flush(); $sendingQueueWorker->process(); $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $this->sendingQueuesRepository->refresh($sendingQueue); verify($sendingQueue->getCountTotal())->equals($expectSending ? 1 : 0); // Transactional emails shouldn't update last sending at $this->subscribersRepository->refresh($subscriber); verify($subscriber->getLastSendingAt())->null(); } public function dataForTestItSendsTransactionalEmails(): array { return [ SubscriberEntity::STATUS_UNCONFIRMED => [SubscriberEntity::STATUS_UNCONFIRMED, true], SubscriberEntity::STATUS_SUBSCRIBED => [SubscriberEntity::STATUS_SUBSCRIBED, true], SubscriberEntity::STATUS_UNSUBSCRIBED => [SubscriberEntity::STATUS_UNSUBSCRIBED, true], SubscriberEntity::STATUS_BOUNCED => [SubscriberEntity::STATUS_BOUNCED, false], SubscriberEntity::STATUS_INACTIVE => [SubscriberEntity::STATUS_INACTIVE, true], ]; } public function testItDoesNotSendToGloballyUnsubscribedSubscribers() { $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], ['send' => $this->mailerTaskDummyResponse] ); // newsletter is not sent to globally unsubscribed subscriber $subscriber = $this->subscriber; $subscriber->setStatus(SubscriberEntity::STATUS_UNSUBSCRIBED); $this->entityManager->flush(); $sendingQueueWorker->process(); $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $this->sendingQueuesRepository->refresh($sendingQueue); verify($sendingQueue->getCountTotal())->equals(0); } public function testItDoesNotSendToSubscribersUnsubscribedFromSegments() { $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], ['send' => $this->mailerTaskDummyResponse] ); // newsletter is not sent to subscriber unsubscribed from segment $subscriberSegment = $this->subscriber->getSubscriberSegments()->first(); $this->assertInstanceOf(SubscriberSegmentEntity::class, $subscriberSegment); $subscriberSegment->setStatus(SubscriberEntity::STATUS_UNSUBSCRIBED); $this->entityManager->persist($subscriberSegment); $sendingQueueWorker->process(); $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $this->sendingQueuesRepository->refresh($sendingQueue); verify($sendingQueue->getCountTotal())->equals(0); } public function testItDoesNotSendToInactiveSubscribers() { $sendingQueueWorker = $this->sendingQueueWorker; $sendingQueueWorker->mailerTask = $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], ['send' => $this->mailerTaskDummyResponse] ); // newsletter is not sent to inactive subscriber $subscriber = $this->subscriber; $subscriber->setStatus(SubscriberEntity::STATUS_INACTIVE); $this->entityManager->flush(); $sendingQueueWorker->process(); $sendingQueue = $this->sendingQueuesRepository->findOneById($this->sendingQueue->getId()); $this->assertInstanceOf(SendingQueueEntity::class, $sendingQueue); $this->sendingQueuesRepository->refresh($sendingQueue); verify($sendingQueue->getCountTotal())->equals(0); } public function testItPausesSendingWhenProcessedSubscriberListCannotBeUpdated() { $scheduledTaskSubscribersRepository = $this->createMock(ScheduledTaskSubscribersRepository::class); $scheduledTaskSubscribersRepository ->method('updateProcessedSubscribers') ->willThrowException(new \Exception()); $sendingQueueWorker = $this->make( $this->getSendingQueueWorker() ); $sendingQueueWorker->__construct( $this->sendingErrorHandler, $this->sendingThrottlingHandler, $this->statsNotificationsWorker, $this->loggerFactory, $this->cronHelper, $this->subscribersFinder, $this->segmentsRepository, $this->wp, $this->tasksLinks, $this->scheduledTasksRepository, $scheduledTaskSubscribersRepository, $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'sendBulk' => $this->mailerTaskDummyResponse, ] ), $this->subscribersRepository, $this->sendingQueuesRepository, $this->entityManager, $this->statisticsNewslettersRepository, $this->authorizedEmailsController ); try { $sendingQueueWorker->sendNewsletters( $this->scheduledTask, $preparedSubscribers = [], $preparedNewsletters = [], $preparedSubscribers = [], $statistics = [], microtime(true) ); $this->fail('Paused sending exception was not thrown.'); } catch (\Exception $e) { verify($e->getMessage())->equals('Sending has been paused.'); } $mailerLog = MailerLog::getMailerLog(); verify($mailerLog['status'])->equals(MailerLog::STATUS_PAUSED); verify($mailerLog['error'])->equals( [ 'operation' => 'processed_list_update', 'error_message' => "QUEUE-{$this->sendingQueue->getId()}-PROCESSED-LIST-UPDATE", ] ); } public function testItDoesNotUpdateNewsletterHashDuringSending() { $originalHash = $this->newsletter->getHash(); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::once($this->mailerTaskDummyResponse), ] ) ); $sendingQueueWorker->process(); // newsletter is sent and hash remains intact verify($this->newsletter->getStatus())->equals(NewsletterEntity::STATUS_SENT); verify($this->newsletter->getHash())->equals($originalHash); } public function testItAllowsSettingCustomBatchSize() { $customBatchSizeValue = 10; $filter = function() use ($customBatchSizeValue) { return $customBatchSizeValue; }; $wp = new WPFunctions; $wp->addFilter('mailpoet_cron_worker_sending_queue_batch_size', $filter); $sendingQueueWorker = $this->getSendingQueueWorker(); verify($sendingQueueWorker->getBatchSize())->equals($customBatchSizeValue); $wp->removeFilter('mailpoet_cron_worker_sending_queue_batch_size', $filter); } public function testItReschedulesBounceTaskWhenPlannedInFarFuture() { $task = (new ScheduledTaskFactory())->create( Bounce::TASK_TYPE, ScheduledTaskEntity::STATUS_SCHEDULED, Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))->addMonths(1) ); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct(MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => $this->mailerTaskDummyResponse, ]) ); $sendingQueueWorker->process(); verify($task->getScheduledAt())->lessThan(Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))->addHours(42)); } public function testDoesNotRescheduleBounceTaskWhenPlannedInNearFuture() { $inOneHour = Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))->addHours(1); $task = (new ScheduledTaskFactory())->create( Bounce::TASK_TYPE, ScheduledTaskEntity::STATUS_SCHEDULED, $inOneHour ); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct(MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => $this->mailerTaskDummyResponse, ]) ); $sendingQueueWorker->process(); verify($task->getScheduledAt())->equals($inOneHour); } public function testItPauseSendingTaskThatHasTrashedSegment() { $newsletter = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, 'Subject With Trashed', NewsletterEntity::STATUS_SENDING); $queue = $this->createQueueWithTask($newsletter, null, ['html' => 'Hello', 'text' => 'Hello']); $segment = $this->createSegment('Segment test', SegmentEntity::TYPE_DEFAULT); $segment->setDeletedAt(new \DateTime()); $this->entityManager->flush(); $this->addSegmentToNewsletter($newsletter, $segment); $sendingQueueWorker = $this->getSendingQueueWorker(); $sendingQueueWorker->process(); $task = $queue->getTask(); $this->assertInstanceOf(ScheduledTaskEntity::class, $task); $this->entityManager->refresh($task); $this->entityManager->refresh($newsletter); verify($task->getStatus())->equals(ScheduledTaskEntity::STATUS_PAUSED); verify($newsletter->getStatus())->equals(NewsletterEntity::STATUS_SENDING); verify($this->wp->getTransient(SendingQueueWorker::EMAIL_WITH_INVALID_SEGMENT_OPTION))->equals('Subject With Trashed'); } public function testItPauseSendingTaskThatHasDeletedSegment() { $newsletter = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, 'Subject With Deleted', NewsletterEntity::STATUS_SENDING); $queue = $this->createQueueWithTask($newsletter, null, ['html' => 'Hello', 'text' => 'Hello']); $segment = $this->createSegment('Segment test', SegmentEntity::TYPE_DEFAULT); $this->addSegmentToNewsletter($newsletter, $segment); $this->entityManager->createQueryBuilder()->delete(SegmentEntity::class, 's') ->where('s.id = :id') ->setParameter('id', $segment->getId()) ->getQuery() ->execute(); $sendingQueueWorker = $this->getSendingQueueWorker(); $sendingQueueWorker->process(); $task = $queue->getTask(); $this->assertInstanceOf(ScheduledTaskEntity::class, $task); $this->entityManager->refresh($task); $this->entityManager->refresh($newsletter); verify($task->getStatus())->equals(ScheduledTaskEntity::STATUS_PAUSED); verify($newsletter->getStatus())->equals(NewsletterEntity::STATUS_SENDING); verify($this->wp->getTransient(SendingQueueWorker::EMAIL_WITH_INVALID_SEGMENT_OPTION))->equals('Subject With Deleted'); } public function testItPausesSendingTaskWhenSenderAddressDoesNotMeetRequirements() { $authorizedEmailControllerMock = $this->make( AuthorizedEmailsController::class, ['isSenderAddressValid' => false]); $this->newsletter->setStatus(NewsletterEntity::STATUS_SENDING); $this->entityManager->flush(); $sendingQueueWorker = $this->getSendingQueueWorker(null, $authorizedEmailControllerMock); $sendingQueueWorker->process(); $this->entityManager->refresh($this->scheduledTask); $this->entityManager->refresh($this->newsletter); verify($this->scheduledTask->getStatus())->equals(ScheduledTaskEntity::STATUS_PAUSED); verify($this->newsletter->getStatus())->equals(NewsletterEntity::STATUS_SENDING); } public function testItGeneratesPartOfAnMD5CampaignIdStoredAsSendingQueueMeta() { $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function($newsletter, $subscriber) { // newsletter body should not be empty verify(!empty($newsletter['body']['html']))->true(); verify(!empty($newsletter['body']['text']))->true(); return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); $meta = $this->sendingQueue->getMeta(); verify(isset($meta['campaignId']))->true(); $campaignId = $meta['campaignId']; verify(strlen($campaignId))->equals(16); } public function testItPassesCampaignIdToMailerViaExtraParamsMeta() { $mailerTaskExtraParams = []; $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(1, function($newsletter, $subscriber, $extraParams = []) use (&$mailerTaskExtraParams) { // newsletter body should not be empty verify(!empty($newsletter['body']['html']))->true(); verify(!empty($newsletter['body']['text']))->true(); $mailerTaskExtraParams = $extraParams; return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); $meta = $this->sendingQueue->getMeta(); verify(isset($meta['campaignId']))->true(); $campaignId = $meta['campaignId']; verify($mailerTaskExtraParams['meta']['campaign_id'])->equals($campaignId); } public function testCampaignIdsAreTheSameForDifferentSubscribers() { $mailerTaskCampaignIds = []; $secondSubscriber = $this->createSubscriber('sub2@example.com', 'Subscriber', 'Two', [$this->segment]); $this->scheduledTaskSubscribersRepository->setSubscribers( $this->scheduledTask, [$this->subscriber->getId(), $secondSubscriber->getId()] ); $sendingQueueWorker = $this->getSendingQueueWorker( $this->construct( MailerTask::class, [$this->diContainer->get(MailerFactory::class)], [ 'send' => Expected::exactly(2, function($newsletter, $subscriber, $extraParams = []) use (&$mailerTaskCampaignIds) { // newsletter body should not be empty verify(!empty($newsletter['body']['html']))->true(); verify(!empty($newsletter['body']['text']))->true(); $mailerTaskCampaignIds[$subscriber] = $extraParams['meta']['campaign_id']; return $this->mailerTaskDummyResponse; }), ] ) ); $sendingQueueWorker->process(); $meta = $this->sendingQueue->getMeta(); verify(isset($meta['campaignId']))->true(); $campaignId = $meta['campaignId']; verify(count($mailerTaskCampaignIds))->equals(2); foreach (array_values($mailerTaskCampaignIds) as $mailerTaskCampaignId) { verify($mailerTaskCampaignId)->equals($campaignId); } } public function testSendingGetsStuckWhenSubscribersAreUnsubscribed() { $newsletter = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, 'Subject With Deleted', NewsletterEntity::STATUS_SENDING); [$segment, $subscriber] = $this->createListWithSubscriber(); $this->addSegmentToNewsletter($newsletter, $segment); $queue = $this->createQueueWithTask($newsletter, null, ['html' => 'Hello', 'text' => 'Hello']); $subscriber->setStatus(SubscriberEntity::STATUS_UNSUBSCRIBED); $this->entityManager->persist($subscriber); $this->entityManager->flush(); $sendingQueueWorker = $this->getSendingQueueWorker(); $sendingQueueWorker->process(); $task = $queue->getTask(); $this->assertInstanceOf(ScheduledTaskEntity::class, $task); verify($task->getStatus())->equals(ScheduledTaskEntity::STATUS_INVALID); verify($newsletter->getStatus())->equals(NewsletterEntity::STATUS_SENDING); } public function testProcessMarksScheduledTaskInProgressAsFalseWhenProperlyProcessingTask() { $sendingQueueWorker = $this->getSendingQueueWorker(); $sendingQueueWorker->process(); $this->assertSame(false, $this->scheduledTask->getInProgress()); } public function testProcessMarksScheduledTaskProgressAsFinishedWhenThereIsAnErrorProcessingTask() { $mailerTask = $this->createMock(MailerTask::class); $mailerTask ->method('send') ->willThrowException(new \Exception()); $mailerTask ->method('getProcessingMethod') ->willReturn('individual'); $sendingQueueWorker = $this->getSendingQueueWorker($mailerTask); try { $sendingQueueWorker->process(); } catch (\Exception $e) { // do nothing } $this->assertSame(false, $this->scheduledTask->getInProgress()); } public function testItCanProcessPostNotificationHistoryWithoutPosts() { // Cleanup data from self::before $this->truncateEntity(NewsletterEntity::class); $this->truncateEntity(SendingQueueEntity::class); $this->truncateEntity(ScheduledTaskEntity::class); $this->entityManager->clear(); // Prepare post notification history for sending // The body added via createNewsletter doesn't contain ALC block so there will be no posts inserted and the newsletter will be deleted $parentNewsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION, 'Post Notification', NewsletterEntity::STATUS_ACTIVE); $newsletter = $this->createNewsletter(NewsletterEntity::TYPE_NOTIFICATION_HISTORY, 'To Delete', NewsletterEntity::STATUS_SENDING); $newsletter->setParent($parentNewsletter); $queue = $this->createQueueWithTask($newsletter); $sendingQueueWorker = $this->getSendingQueueWorker(); $sendingQueueWorker->process(); $task = $queue->getTask(); $this->assertInstanceOf(ScheduledTaskEntity::class, $task); // Re-fetch newsletter, task and queue from DB and verify they were deleted verify($this->entityManager->find(ScheduledTaskEntity::class, $task->getId()))->null(); verify($this->entityManager->find(SendingQueueEntity::class, $queue->getId()))->null(); verify($this->entityManager->find(NewsletterEntity::class, $newsletter->getId()))->null(); } private function createNewsletter(string $type, $subject, string $status = NewsletterEntity::STATUS_DRAFT): NewsletterEntity { $newsletter = new NewsletterEntity(); $newsletter->setType($type); $newsletter->setSubject($subject); $newsletter->setBody(Fixtures::get('newsletter_body_template')); $newsletter->setStatus($status); $this->entityManager->persist($newsletter); $this->entityManager->flush(); return $newsletter; } private function createSubscriber(string $email, string $firstName, string $lastName, $segments = []): SubscriberEntity { $subscriber = (new SubscriberFactory()) ->withEmail($email) ->withFirstName($firstName) ->withLastName($lastName) ->withStatus(SubscriberEntity::STATUS_SUBSCRIBED) ->withSource(Source::ADMINISTRATOR) ->withSegments($segments) ->create(); return $subscriber; } private function createSegment(string $name, string $type): SegmentEntity { $segment = new SegmentEntity($name, $type, 'Description'); $this->entityManager->persist($segment); $this->entityManager->flush(); return $segment; } private function addSegmentToNewsletter(NewsletterEntity $newsletter, SegmentEntity $segment) { $newsletterSegment = new NewsletterSegmentEntity($newsletter, $segment); $newsletter->getNewsletterSegments()->add($newsletterSegment); $this->entityManager->persist($newsletterSegment); $this->entityManager->flush(); } private function createQueueWithTask(NewsletterEntity $newsletter, $status = null, $body = null): SendingQueueEntity { $task = new ScheduledTaskEntity(); $task->setType(SendingQueueWorker::TASK_TYPE); $task->setStatus($status); $this->entityManager->persist($task); $queue = new SendingQueueEntity(); $queue->setNewsletter($newsletter); $queue->setTask($task); if ($body) { $queue->setNewsletterRenderedBody($body); } $this->entityManager->persist($queue); $newsletter->getQueues()->add($queue); $this->entityManager->flush(); $this->entityManager->refresh($queue); // I'm not sure why calling refresh() here is needed and why the tests fail without it (calling $task->getSendingQueue() returns null) return $queue; } private function getSendingQueueWorker($mailerMock = null, $authorizedEmailControllerMock = null): SendingQueueWorker { return new SendingQueueWorker( $this->sendingErrorHandler, $this->sendingThrottlingHandler, $this->statsNotificationsWorker, $this->loggerFactory, $this->cronHelper, $this->subscribersFinder, $this->segmentsRepository, $this->wp, $this->tasksLinks, $this->scheduledTasksRepository, $this->scheduledTaskSubscribersRepository, $mailerMock ?? $this->diContainer->get(MailerTask::class), $this->subscribersRepository, $this->sendingQueuesRepository, $this->entityManager, $this->statisticsNewslettersRepository, $authorizedEmailControllerMock ?? $this->authorizedEmailsController ); } private function createListWithSubscriber(): array { $segmentFactory = new SegmentFactory(); $segmentName = 'List ' . Security::generateRandomString(); $segment = $segmentFactory->withName($segmentName)->create(); $subscriberFactory = new SubscriberFactory(); $subscriberEmail = Security::generateRandomString() . '@domain.com'; $subscriberFirstName = 'John'; $subscriberLastName = 'Doe'; $subscriber = $subscriberFactory->withSegments([$segment]) ->withEmail($subscriberEmail) ->withFirstName($subscriberFirstName) ->withLastName($subscriberLastName) ->create(); return [$segment, $subscriber]; } }