From d5bf8734efa08fdbba1988f66834f37ca8b6003f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 30 Sep 2017 12:40:07 -0500 Subject: [PATCH] Add unit tests for RunTaskJob. --- .../Controllers/Admin/NodesController.php | 1 + app/Jobs/Schedule/RunTaskJob.php | 3 +- app/Jobs/SendScheduledTask.php | 98 -------- tests/Unit/Jobs/Schedule/RunTaskJobTest.php | 216 ++++++++++++++++++ 4 files changed, 219 insertions(+), 99 deletions(-) delete mode 100644 app/Jobs/SendScheduledTask.php create mode 100644 tests/Unit/Jobs/Schedule/RunTaskJobTest.php diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 5bb070cff..d68a47556 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -248,6 +248,7 @@ class NodesController extends Controller * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function updateSettings(NodeFormRequest $request, Node $node) { diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 7bedf6c7a..0e025740a 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -61,7 +61,8 @@ class RunTaskJob extends Job implements ShouldQueue */ public function __construct($task, $schedule) { - Assert::integerish($task, 'First argument passed to constructor must be numeric, received %s.'); + Assert::integerish($task, 'First argument passed to constructor must be integer, received %s.'); + Assert::integerish($schedule, 'Second argument passed to constructor must be integer, received %s.'); $this->queue = app()->make('config')->get('pterodactyl.queues.standard'); $this->task = $task; diff --git a/app/Jobs/SendScheduledTask.php b/app/Jobs/SendScheduledTask.php deleted file mode 100644 index b66ecf41b..000000000 --- a/app/Jobs/SendScheduledTask.php +++ /dev/null @@ -1,98 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Jobs; - -use Cron; -use Carbon; -use Pterodactyl\Models\Task; -use Pterodactyl\Models\TaskLog; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Contracts\Queue\ShouldQueue; -use Pterodactyl\Repositories\old_Daemon\PowerRepository; -use Pterodactyl\Repositories\old_Daemon\CommandRepository; - -class SendScheduledTask extends Job implements ShouldQueue -{ - use InteractsWithQueue, SerializesModels; - - /** - * @var \Pterodactyl\Models\Task - */ - protected $task; - - /** - * Create a new job instance. - */ - public function __construct(Task $task) - { - $this->task = $task; - - $this->task->queued = true; - $this->task->save(); - } - - /** - * Execute the job. - */ - public function handle() - { - $time = Carbon::now(); - $log = new TaskLog; - - if ($this->attempts() >= 1) { - // Just delete the job, we will attempt it again later anyways. - $this->delete(); - } - - try { - if ($this->task->action === 'command') { - $repo = new CommandRepository($this->task->server, $this->task->user); - $response = $repo->send($this->task->data); - } elseif ($this->task->action === 'power') { - $repo = new PowerRepository($this->task->server, $this->task->user); - $response = $repo->do($this->task->data); - } else { - throw new \Exception('Task type provided was not valid.'); - } - - $log->fill([ - 'task_id' => $this->task->id, - 'run_time' => $time, - 'run_status' => 0, - 'response' => $response, - ]); - } catch (\Exception $ex) { - $log->fill([ - 'task_id' => $this->task->id, - 'run_time' => $time, - 'run_status' => 1, - 'response' => $ex->getMessage(), - ]); - } finally { - $cron = Cron::factory(sprintf( - '%s %s %s %s %s %s', - $this->task->minute, - $this->task->hour, - $this->task->day_of_month, - $this->task->month, - $this->task->day_of_week, - $this->task->year - )); - $this->task->fill([ - 'last_run' => $time, - 'next_run' => $cron->getNextRunDate(), - 'queued' => false, - ]); - $this->task->save(); - $log->save(); - } - } -} diff --git a/tests/Unit/Jobs/Schedule/RunTaskJobTest.php b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php new file mode 100644 index 000000000..9a27c59c1 --- /dev/null +++ b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php @@ -0,0 +1,216 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Jobs\Schedule; + +use Mockery as m; +use Carbon\Carbon; +use Tests\TestCase; +use Pterodactyl\Models\Task; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Schedule; +use Illuminate\Support\Facades\Bus; +use Pterodactyl\Jobs\Schedule\RunTaskJob; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; +use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; + +class RunTaskJobTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface|\Mockery\Mock + */ + protected $commandRepository; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService|\Mockery\Mock + */ + protected $keyProviderService; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface|\Mockery\Mock + */ + protected $powerRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface|\Mockery\Mock + */ + protected $scheduleRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface|\Mockery\Mock + */ + protected $taskRepository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + Bus::fake(); + + $this->commandRepository = m::mock(CommandRepositoryInterface::class); + $this->config = m::mock(Repository::class); + $this->keyProviderService = m::mock(DaemonKeyProviderService::class); + $this->powerRepository = m::mock(PowerRepositoryInterface::class); + $this->scheduleRepository = m::mock(ScheduleRepositoryInterface::class); + $this->taskRepository = m::mock(TaskRepositoryInterface::class); + + $this->app->instance(Repository::class, $this->config); + $this->app->instance(TaskRepositoryInterface::class, $this->taskRepository); + $this->app->instance(ScheduleRepositoryInterface::class, $this->scheduleRepository); + } + + /** + * Test power option passed to job. + */ + public function testPowerAction() + { + Carbon::setTestNow(); + + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(['action' => 'power', 'sequence_id' => 1]); + $server = factory(Server::class)->make(); + $task->server = $server; + + $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + $this->keyProviderService->shouldReceive('handle')->with($server->id, $server->owner_id)->once()->andReturn('123456'); + $this->powerRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() + ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() + ->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf() + ->shouldReceive('sendSignal')->with($task->payload)->once()->andReturnNull(); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull(); + $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturnNull(); + + $this->scheduleRepository->shouldReceive('withoutFresh->update')->with($schedule->id, [ + 'is_processing' => false, + 'last_run_at' => Carbon::now()->toDateTimeString(), + ])->once()->andReturnNull(); + + $this->getJobInstance($task->id, $schedule->id); + + Bus::assertNotDispatched(RunTaskJob::class); + } + + /** + * Test commmand action passed to job. + */ + public function testCommandAction() + { + Carbon::setTestNow(); + + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); + $server = factory(Server::class)->make(); + $task->server = $server; + + $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + $this->keyProviderService->shouldReceive('handle')->with($server->id, $server->owner_id)->once()->andReturn('123456'); + $this->commandRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() + ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() + ->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf() + ->shouldReceive('send')->with($task->payload)->once()->andReturnNull(); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull(); + $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturnNull(); + + $this->scheduleRepository->shouldReceive('withoutFresh->update')->with($schedule->id, [ + 'is_processing' => false, + 'last_run_at' => Carbon::now()->toDateTimeString(), + ])->once()->andReturnNull(); + + $this->getJobInstance($task->id, $schedule->id); + + Bus::assertNotDispatched(RunTaskJob::class); + } + + /** + * Test that the next task in the list is queued if the current one is not the last. + */ + public function testNextTaskQueuedIfExists() + { + Carbon::setTestNow(); + + $schedule = factory(Schedule::class)->make(); + $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); + $server = factory(Server::class)->make(); + $task->server = $server; + + $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + $this->keyProviderService->shouldReceive('handle')->with($server->id, $server->owner_id)->once()->andReturn('123456'); + $this->commandRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() + ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() + ->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf() + ->shouldReceive('send')->with($task->payload)->once()->andReturnNull(); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull(); + + $nextTask = factory(Task::class)->make(); + $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturn($nextTask); + $this->taskRepository->shouldReceive('update')->with($nextTask->id, [ + 'is_queued' => true, + ])->once()->andReturnNull(); + + $this->getJobInstance($task->id, $schedule->id); + + Bus::assertDispatched(RunTaskJob::class, function ($job) use ($nextTask, $schedule) { + $this->assertEquals($nextTask->id, $job->task, 'Assert correct task ID is passed to job.'); + $this->assertEquals($schedule->id, $job->schedule, 'Assert correct schedule ID is passed to job.'); + $this->assertEquals($nextTask->time_offset, $job->delay, 'Assert correct job delay time is set.'); + + return true; + }); + } + + /** + * Test that an exception is thrown if an invalid task action is supplied. + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cannot run a task that points to a non-existant action. + */ + public function testInvalidActionPassedToJob() + { + $task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]); + $task->server = []; + + $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + + $this->getJobInstance($task->id, 1234); + } + + /** + * Run the job using the mocks provided. + * + * @param int $task + * @param int $schedule + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function getJobInstance($task, $schedule) + { + return (new RunTaskJob($task, $schedule))->handle( + $this->commandRepository, + $this->keyProviderService, + $this->powerRepository, + $this->taskRepository + ); + } +}