157 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
namespace App\Tests\Integration\Services\Schedules;
 | 
						|
 | 
						|
use Exception;
 | 
						|
use Carbon\CarbonImmutable;
 | 
						|
use App\Models\Task;
 | 
						|
use App\Models\Schedule;
 | 
						|
use Illuminate\Support\Facades\Bus;
 | 
						|
use Illuminate\Contracts\Bus\Dispatcher;
 | 
						|
use App\Jobs\Schedule\RunTaskJob;
 | 
						|
use App\Exceptions\DisplayException;
 | 
						|
use App\Tests\Integration\IntegrationTestCase;
 | 
						|
use App\Services\Schedules\ProcessScheduleService;
 | 
						|
 | 
						|
class ProcessScheduleServiceTest extends IntegrationTestCase
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * Test that a schedule with no tasks registered returns an error.
 | 
						|
     */
 | 
						|
    public function testScheduleWithNoTasksReturnsException(): void
 | 
						|
    {
 | 
						|
        $server = $this->createServerModel();
 | 
						|
        $schedule = Schedule::factory()->create(['server_id' => $server->id]);
 | 
						|
 | 
						|
        $this->expectException(DisplayException::class);
 | 
						|
        $this->expectExceptionMessage('Cannot process schedule for task execution: no tasks are registered.');
 | 
						|
 | 
						|
        $this->getService()->handle($schedule);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Test that an error during the schedule update is not persisted to the database.
 | 
						|
     */
 | 
						|
    public function testErrorDuringScheduleDataUpdateDoesNotPersistChanges(): void
 | 
						|
    {
 | 
						|
        $server = $this->createServerModel();
 | 
						|
 | 
						|
        /** @var \App\Models\Schedule $schedule */
 | 
						|
        $schedule = Schedule::factory()->create([
 | 
						|
            'server_id' => $server->id,
 | 
						|
            'cron_minute' => 'hodor', // this will break the getNextRunDate() function.
 | 
						|
        ]);
 | 
						|
 | 
						|
        /** @var \App\Models\Task $task */
 | 
						|
        $task = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 1]);
 | 
						|
 | 
						|
        $this->expectException(\InvalidArgumentException::class);
 | 
						|
 | 
						|
        $this->getService()->handle($schedule);
 | 
						|
 | 
						|
        $this->assertDatabaseMissing('schedules', ['id' => $schedule->id, 'is_processing' => true]);
 | 
						|
        $this->assertDatabaseMissing('tasks', ['id' => $task->id, 'is_queued' => true]);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Test that a job is dispatched as expected using the initial delay.
 | 
						|
     *
 | 
						|
     * @dataProvider dispatchNowDataProvider
 | 
						|
     */
 | 
						|
    public function testJobCanBeDispatchedWithExpectedInitialDelay(bool $now): void
 | 
						|
    {
 | 
						|
        Bus::fake();
 | 
						|
 | 
						|
        $server = $this->createServerModel();
 | 
						|
 | 
						|
        /** @var \App\Models\Schedule $schedule */
 | 
						|
        $schedule = Schedule::factory()->create(['server_id' => $server->id]);
 | 
						|
 | 
						|
        /** @var \App\Models\Task $task */
 | 
						|
        $task = Task::factory()->create(['schedule_id' => $schedule->id, 'time_offset' => 10, 'sequence_id' => 1]);
 | 
						|
 | 
						|
        $this->getService()->handle($schedule, $now);
 | 
						|
 | 
						|
        Bus::assertDispatched(RunTaskJob::class, function ($job) use ($now, $task) {
 | 
						|
            $this->assertInstanceOf(RunTaskJob::class, $job);
 | 
						|
            $this->assertSame($task->id, $job->task->id);
 | 
						|
            // Jobs using dispatchNow should not have a delay associated with them.
 | 
						|
            $this->assertSame($now ? null : 10, $job->delay);
 | 
						|
 | 
						|
            return true;
 | 
						|
        });
 | 
						|
 | 
						|
        $this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
 | 
						|
        $this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Test that even if a schedule's task sequence gets messed up the first task based on
 | 
						|
     * the ascending order of tasks is used.
 | 
						|
     */
 | 
						|
    public function testFirstSequenceTaskIsFound(): void
 | 
						|
    {
 | 
						|
        Bus::fake();
 | 
						|
 | 
						|
        $server = $this->createServerModel();
 | 
						|
        /** @var \App\Models\Schedule $schedule */
 | 
						|
        $schedule = Schedule::factory()->create(['server_id' => $server->id]);
 | 
						|
 | 
						|
        /** @var \App\Models\Task $task */
 | 
						|
        $task2 = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 4]);
 | 
						|
        $task = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 2]);
 | 
						|
        $task3 = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 3]);
 | 
						|
 | 
						|
        $this->getService()->handle($schedule);
 | 
						|
 | 
						|
        Bus::assertDispatched(RunTaskJob::class, function (RunTaskJob $job) use ($task) {
 | 
						|
            return $task->id === $job->task->id;
 | 
						|
        });
 | 
						|
 | 
						|
        $this->assertDatabaseHas('schedules', ['id' => $schedule->id, 'is_processing' => true]);
 | 
						|
        $this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => true]);
 | 
						|
        $this->assertDatabaseHas('tasks', ['id' => $task2->id, 'is_queued' => false]);
 | 
						|
        $this->assertDatabaseHas('tasks', ['id' => $task3->id, 'is_queued' => false]);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Tests that a task's processing state is reset correctly if using "dispatchNow" and there is
 | 
						|
     * an exception encountered while running it.
 | 
						|
     */
 | 
						|
    public function testTaskDispatchedNowIsResetProperlyIfErrorIsEncountered(): void
 | 
						|
    {
 | 
						|
        $this->swap(Dispatcher::class, $dispatcher = \Mockery::mock(Dispatcher::class));
 | 
						|
 | 
						|
        $server = $this->createServerModel();
 | 
						|
        /** @var \App\Models\Schedule $schedule */
 | 
						|
        $schedule = Schedule::factory()->create(['server_id' => $server->id, 'last_run_at' => null]);
 | 
						|
        /** @var \App\Models\Task $task */
 | 
						|
        $task = Task::factory()->create(['schedule_id' => $schedule->id, 'sequence_id' => 1]);
 | 
						|
 | 
						|
        $dispatcher->expects('dispatchNow')->andThrows(new \Exception('Test thrown exception'));
 | 
						|
 | 
						|
        $this->expectException(\Exception::class);
 | 
						|
        $this->expectExceptionMessage('Test thrown exception');
 | 
						|
 | 
						|
        $this->getService()->handle($schedule, true);
 | 
						|
 | 
						|
        $this->assertDatabaseHas('schedules', [
 | 
						|
            'id' => $schedule->id,
 | 
						|
            'is_processing' => false,
 | 
						|
            'last_run_at' => CarbonImmutable::now()->toAtomString(),
 | 
						|
        ]);
 | 
						|
 | 
						|
        $this->assertDatabaseHas('tasks', ['id' => $task->id, 'is_queued' => false]);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function dispatchNowDataProvider(): array
 | 
						|
    {
 | 
						|
        return [[true], [false]];
 | 
						|
    }
 | 
						|
 | 
						|
    private function getService(): ProcessScheduleService
 | 
						|
    {
 | 
						|
        return $this->app->make(ProcessScheduleService::class);
 | 
						|
    }
 | 
						|
}
 |