242 lines
8.8 KiB
PHP
242 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace MailPoet\Test\Cron;
|
|
|
|
use Codeception\Stub;
|
|
use Codeception\Stub\Expected;
|
|
use MailPoet\Cron\CronHelper;
|
|
use MailPoet\Cron\CronWorkerRunner;
|
|
use MailPoet\Cron\Workers\SimpleWorkerMockImplementation;
|
|
use MailPoet\Entities\ScheduledTaskEntity;
|
|
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
|
|
use MailPoet\Test\DataFactories\ScheduledTask as ScheduledTaskFactory;
|
|
use MailPoet\WP\Functions as WPFunctions;
|
|
use MailPoetVendor\Carbon\Carbon;
|
|
|
|
require_once __DIR__ . '/Workers/SimpleWorkerMockImplementation.php';
|
|
|
|
class CronWorkerRunnerTest extends \MailPoetTest {
|
|
/** @var CronWorkerRunner */
|
|
private $cronWorkerRunner;
|
|
|
|
/** @var CronHelper */
|
|
private $cronHelper;
|
|
|
|
/** @var ScheduledTasksRepository */
|
|
private $scheduledTasksRepository;
|
|
|
|
public function _before() {
|
|
$this->cronWorkerRunner = $this->diContainer->get(CronWorkerRunner::class);
|
|
$this->cronHelper = $this->diContainer->get(CronHelper::class);
|
|
$this->scheduledTasksRepository = $this->diContainer->get(ScheduledTasksRepository::class);
|
|
}
|
|
|
|
public function testItCanInitBeforeProcessing() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'init' => Expected::once(),
|
|
'scheduleAutomatically' => Expected::once(false),
|
|
]);
|
|
$this->cronWorkerRunner->run($worker);
|
|
}
|
|
|
|
public function testItPreparesTask() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'prepareTaskStrategy' => Expected::once(true),
|
|
'processTaskStrategy' => Expected::never(),
|
|
]);
|
|
|
|
$task = $this->createScheduledTask();
|
|
$result = $this->cronWorkerRunner->run($worker);
|
|
expect($result)->true();
|
|
$scheduledTask = $this->scheduledTasksRepository->findOneById($task->getId());
|
|
assert($scheduledTask instanceof ScheduledTaskEntity);
|
|
expect($scheduledTask->getStatus())->null();
|
|
}
|
|
|
|
public function testItProcessesTask() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'prepareTaskStrategy' => Expected::never(),
|
|
'processTaskStrategy' => Expected::once(true),
|
|
]);
|
|
|
|
$task = $this->createRunningTask();
|
|
$result = $this->cronWorkerRunner->run($worker);
|
|
expect($result)->true();
|
|
$scheduledTask = $this->scheduledTasksRepository->findOneById($task->getId());
|
|
assert($scheduledTask instanceof ScheduledTaskEntity);
|
|
expect($scheduledTask->getStatus())->same(ScheduledTaskEntity::STATUS_COMPLETED);
|
|
}
|
|
|
|
public function testItFailsToProcessWithoutTasks() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'scheduleAutomatically' => Expected::once(false),
|
|
'prepareTaskStrategy' => Expected::never(),
|
|
'processTaskStrategy' => Expected::never(),
|
|
]);
|
|
|
|
$result = $this->cronWorkerRunner->run($worker);
|
|
expect($result)->false();
|
|
}
|
|
|
|
public function testItFailsToProcessWithoutProcessingRequirementsMet() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'checkProcessingRequirements' => Expected::once(false),
|
|
'prepareTaskStrategy' => Expected::never(),
|
|
'processTaskStrategy' => Expected::never(),
|
|
]);
|
|
|
|
$this->createScheduledTask();
|
|
$this->createRunningTask();
|
|
|
|
$result = $this->cronWorkerRunner->run($worker);
|
|
expect($result)->false();
|
|
}
|
|
|
|
public function testItCanScheduleTaskAutomatically() {
|
|
$inOneWeek = Carbon::now()->addWeek()->startOfDay();
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'scheduleAutomatically' => Expected::once(true),
|
|
'getTaskType' => Expected::atLeastOnce(SimpleWorkerMockImplementation::TASK_TYPE),
|
|
'getNextRunDate' => Expected::once($inOneWeek),
|
|
]);
|
|
|
|
$result = $this->cronWorkerRunner->run($worker);
|
|
expect($result)->false();
|
|
$scheduledTask = $this->scheduledTasksRepository->findAll()[0];
|
|
assert($scheduledTask instanceof ScheduledTaskEntity);
|
|
expect($scheduledTask->getScheduledAt())->same($inOneWeek);
|
|
}
|
|
|
|
public function testItWillRescheduleTaskIfItIsRunningForTooLong() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'processTaskStrategy' => Expected::once(false),
|
|
]);
|
|
$worker->__construct();
|
|
|
|
$task = $this->createRunningTask();
|
|
$task = $this->scheduledTasksRepository->findOneById($task->getId()); // make sure `updated_at` is set by the DB
|
|
assert($task instanceof ScheduledTaskEntity);
|
|
|
|
$result = $this->cronWorkerRunner->run($worker);
|
|
expect($result)->true();
|
|
|
|
$scheduledAt = $task->getScheduledAt();
|
|
$newUpdatedAt = $task->getUpdatedAt();
|
|
$this->assertInstanceOf(Carbon::class, $newUpdatedAt);
|
|
$newUpdatedAt->subMinutes(CronWorkerRunner::TASK_RUN_TIMEOUT + 1);
|
|
$task->setUpdatedAt($newUpdatedAt);
|
|
$this->scheduledTasksRepository->persist($task);
|
|
$this->scheduledTasksRepository->flush();
|
|
|
|
$result = $this->cronWorkerRunner->run($worker);
|
|
expect($result)->true();
|
|
|
|
$task = $this->scheduledTasksRepository->findOneById($task->getId());
|
|
assert($task instanceof ScheduledTaskEntity);
|
|
expect($task->getScheduledAt())->greaterThan($scheduledAt);
|
|
expect($task->getStatus())->same(ScheduledTaskEntity::STATUS_SCHEDULED);
|
|
expect($task->getInProgress())->isEmpty();
|
|
|
|
// reset the state of the updatedAt field. this is needed to reset the state of TimestampListener::now otherwise it will impact other tests.
|
|
// this code can be removed once https://mailpoet.atlassian.net/browse/MAILPOET-3870 is fixed.
|
|
$updatedAt = $task->getUpdatedAt();
|
|
$this->assertInstanceOf(Carbon::class, $updatedAt);
|
|
$updatedAt->addMinutes(CronWorkerRunner::TASK_RUN_TIMEOUT + 1);
|
|
}
|
|
|
|
public function testItWillRescheduleATaskIfItFails() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'processTaskStrategy' => Expected::once(function () {
|
|
throw new \Exception('test error');
|
|
}),
|
|
]);
|
|
|
|
$task = $this->createRunningTask();
|
|
$scheduledAt = $task->getScheduledAt();
|
|
try {
|
|
$this->cronWorkerRunner->run($worker);
|
|
$this->fail('An exception should be thrown');
|
|
} catch (\Exception $e) {
|
|
expect($e->getMessage())->equals('test error');
|
|
$task = $this->scheduledTasksRepository->findOneById($task->getId());
|
|
assert($task instanceof ScheduledTaskEntity);
|
|
expect($task->getScheduledAt())->greaterThan($scheduledAt);
|
|
expect($task->getStatus())->same(ScheduledTaskEntity::STATUS_SCHEDULED);
|
|
expect($task->getRescheduleCount())->equals(1);
|
|
expect($task->getInProgress())->isEmpty();
|
|
}
|
|
}
|
|
|
|
public function testWillNotRescheduleATaskOnCronTimeout() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'processTaskStrategy' => Expected::once(function () {
|
|
$this->cronHelper->enforceExecutionLimit(microtime(true) - CronHelper::DAEMON_EXECUTION_LIMIT - 1);
|
|
}),
|
|
]);
|
|
|
|
$task = $this->createRunningTask();
|
|
$scheduledAt = $task->getScheduledAt();
|
|
try {
|
|
$this->cronWorkerRunner->run($worker);
|
|
$this->fail('An exception should be thrown');
|
|
} catch (\Exception $e) {
|
|
expect($e->getCode())->same(CronHelper::DAEMON_EXECUTION_LIMIT_REACHED);
|
|
$task = $this->scheduledTasksRepository->findOneById($task->getId());
|
|
assert($task instanceof ScheduledTaskEntity);
|
|
expect($scheduledAt)->equals($task->getScheduledAt());
|
|
expect($task->getStatus())->null();
|
|
expect($task->getRescheduleCount())->equals(0);
|
|
}
|
|
}
|
|
|
|
public function testItWillNotRunInMultipleInstances() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'supportsMultipleInstances' => Expected::once(false),
|
|
'processTaskStrategy' => Expected::never(),
|
|
]);
|
|
|
|
$task = $this->createRunningTask();
|
|
$task->setInProgress(true);
|
|
|
|
$this->cronWorkerRunner->run($worker);
|
|
}
|
|
|
|
public function testItThrowsExceptionWhenExecutionLimitIsReached() {
|
|
$worker = $this->make(SimpleWorkerMockImplementation::class, [
|
|
'processTaskStrategy' => Expected::never(),
|
|
]);
|
|
|
|
$cronWorkerRunner = Stub::copy($this->cronWorkerRunner, [
|
|
'timer' => microtime(true) - $this->diContainer->get(CronHelper::class)->getDaemonExecutionLimit(),
|
|
]);
|
|
try {
|
|
$cronWorkerRunner->run($worker);
|
|
self::fail('Maximum execution time limit exception was not thrown.');
|
|
} catch (\Exception $e) {
|
|
expect($e->getMessage())->same('Maximum execution time has been reached.');
|
|
}
|
|
}
|
|
|
|
private function createScheduledTask(): ScheduledTaskEntity {
|
|
return $this->createTask(ScheduledTaskEntity::STATUS_SCHEDULED);
|
|
}
|
|
|
|
private function createRunningTask(): ScheduledTaskEntity {
|
|
return $this->createTask(null);
|
|
}
|
|
|
|
private function createTask($status): ScheduledTaskEntity {
|
|
$factory = new ScheduledTaskFactory();
|
|
|
|
return $factory->create(
|
|
SimpleWorkerMockImplementation::TASK_TYPE,
|
|
$status,
|
|
Carbon::createFromTimestamp(WPFunctions::get()->currentTime('timestamp'))
|
|
);
|
|
}
|
|
|
|
public function _after() {
|
|
$this->truncateEntity(ScheduledTaskEntity::class);
|
|
}
|
|
}
|