diff --git a/composer.json b/composer.json index f4c27c4..9357667 100644 --- a/composer.json +++ b/composer.json @@ -24,12 +24,13 @@ "spatie/laravel-package-tools": "^1.9" }, "require-dev": { + "mockery/mockery": "^1.4", "ohdearapp/ohdear-php-sdk": "^3.0", "orchestra/testbench": "^7.0", - "phpunit/phpunit": "^9.4", + "pestphp/pest": "^1.20", + "pestphp/pest-plugin-laravel": "^1.2", "spatie/phpunit-snapshot-assertions": "^4.2", - "spatie/test-time": "^1.2", - "mockery/mockery": "^1.4" + "spatie/test-time": "^1.2" }, "suggest": { "ohdearapp/ohdear-php-sdk": "Needed to sync your schedule with Oh Dear" @@ -46,7 +47,7 @@ } }, "scripts": { - "test": "vendor/bin/phpunit", + "test": "vendor/bin/pest", "test-coverage": "vendor/bin/phpunit --coverage-html coverage", "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes" }, diff --git a/tests/Commands/ListCommandTest.php b/tests/Commands/ListCommandTest.php index 6748bde..078c766 100644 --- a/tests/Commands/ListCommandTest.php +++ b/tests/Commands/ListCommandTest.php @@ -6,6 +6,7 @@ use Spatie\ScheduleMonitor\Commands\ListCommand; use Spatie\ScheduleMonitor\Tests\TestClasses\TestJob; use Spatie\ScheduleMonitor\Tests\TestClasses\TestKernel; +use function Pest\Laravel\artisan; it('can list scheduled tasks', function () { TestKernel::registerScheduledTasks(function (Schedule $schedule) { @@ -16,5 +17,5 @@ $schedule->job(new TestJob())->daily(); }); - $this->artisan(ListCommand::class)->assertSuccessful(); + artisan(ListCommand::class)->assertSuccessful(); }); diff --git a/tests/Commands/SyncCommandTest.php b/tests/Commands/SyncCommandTest.php index 7fd40cd..698712c 100644 --- a/tests/Commands/SyncCommandTest.php +++ b/tests/Commands/SyncCommandTest.php @@ -1,7 +1,5 @@ command('dummy')->everyMinute(); - $schedule->exec('execute')->everyFifteenMinutes(); - $schedule->call(fn () => 1 + 1)->hourly()->monitorName('my-closure'); - $schedule->job(new TestJob())->daily()->timezone('Asia/Kolkata'); - }); - - $this->artisan(SyncCommand::class); - - $monitoredScheduledTasks = MonitoredScheduledTask::get(); - $this->assertCount(4, $monitoredScheduledTasks); - - $this->assertDatabaseHas('monitored_scheduled_tasks', [ - 'name' => 'dummy', - 'type' => 'command', - 'cron_expression' => '* * * * *', - 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy', - 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), - 'grace_time_in_minutes' => 5, - 'last_pinged_at' => null, - 'last_started_at' => null, - 'last_finished_at' => null, - 'timezone' => 'UTC', - ]); - - $this->assertDatabaseHas('monitored_scheduled_tasks', [ - 'name' => 'execute', - 'type' => 'shell', - 'cron_expression' => '*/15 * * * *', - 'ping_url' => 'https://ping.ohdear.app/test-ping-url-execute', - 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), - 'grace_time_in_minutes' => 5, - 'last_pinged_at' => null, - 'last_started_at' => null, - 'last_finished_at' => null, - 'timezone' => 'UTC', - ]); - - $this->assertDatabaseHas('monitored_scheduled_tasks', [ - 'name' => 'my-closure', - 'type' => 'closure', - 'cron_expression' => '0 * * * *', - 'ping_url' => 'https://ping.ohdear.app/test-ping-url-my-closure', - 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), - 'grace_time_in_minutes' => 5, - 'last_pinged_at' => null, - 'last_started_at' => null, - 'last_finished_at' => null, - 'timezone' => 'UTC', - ]); - - $this->assertDatabaseHas('monitored_scheduled_tasks', [ - 'name' => TestJob::class, - 'type' => 'job', - 'cron_expression' => '0 0 * * *', - 'ping_url' => 'https://ping.ohdear.app/test-ping-url-' . urlencode(TestJob::class), - 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), - 'grace_time_in_minutes' => 5, - 'last_pinged_at' => null, - 'last_started_at' => null, - 'last_finished_at' => null, - 'timezone' => 'Asia/Kolkata', - ]); - - $this->assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); - } - - /** @test */ - public function it_will_not_monitor_commands_without_a_name() - { - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->call(fn () => 'a closure has no name')->hourly(); - }); - - $this->artisan(SyncCommand::class); - - $monitoredScheduledTasks = MonitoredScheduledTask::get(); - $this->assertCount(0, $monitoredScheduledTasks); - - $this->assertEquals([], $this->ohDear->getSyncedCronCheckAttributes()); - } - - /** @test **/ - public function it_will_remove_old_tasks_from_the_database() - { - MonitoredScheduledTask::factory()->create(['name' => 'old-task']); - $this->assertCount(1, MonitoredScheduledTask::get()); - - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('new')->everyMinute(); - }); - - $this->artisan(SyncCommand::class); - - $this->assertCount(1, MonitoredScheduledTask::get()); - - $this->assertEquals('new', MonitoredScheduledTask::first()->name); - } - - /** @test */ - public function it_can_use_custom_grace_time() - { - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('dummy')->everyMinute()->graceTimeInMinutes(15); - }); - - $this->artisan(SyncCommand::class); - - $this->assertDatabaseHas('monitored_scheduled_tasks', [ - 'grace_time_in_minutes' => 15, - ]); - - $syncedCronChecks = $this->ohDear->getSyncedCronCheckAttributes(); - - $this->assertEquals(15, $syncedCronChecks[0]['grace_time_in_minutes']); - } - - /** @test */ - public function it_will_not_monitor_tasks_that_should_not_be_monitored() - { - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('dummy')->everyMinute()->doNotMonitor(); - }); - - $this->artisan(SyncCommand::class); - - $this->assertCount(0, MonitoredScheduledTask::get()); - - $this->assertEquals([], $this->ohDear->getSyncedCronCheckAttributes()); - } - - /** @test */ - public function it_will_remove_tasks_from_the_db_that_should_not_be_monitored_anymore() - { - MonitoredScheduledTask::factory()->create(['name' => 'not-monitored']); - $this->assertCount(1, MonitoredScheduledTask::get()); - - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('not-monitored')->everyMinute()->doNotMonitor(); - }); - $this->artisan(SyncCommand::class); - - $this->assertCount(0, MonitoredScheduledTask::get()); - } - - /** @test */ - public function it_will_update_tasks_that_have_their_schedule_updated() - { - $monitoredScheduledTask = MonitoredScheduledTask::factory()->create([ - 'name' => 'dummy', - 'cron_expression' => '* * * * *', - ]); - - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('dummy')->daily(); - }); - $this->artisan(SyncCommand::class); - - $this->assertCount(1, MonitoredScheduledTask::get()); - $this->assertEquals('0 0 * * *', $monitoredScheduledTask->refresh()->cron_expression); - } - - /** @test */ - public function it_will_not_sync_with_oh_dear_when_no_site_id_is_set() - { - config()->set('schedule-monitor.oh_dear.site_id', null); - - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('dummy')->daily(); - }); - $this->artisan(SyncCommand::class); - $this->assertCount(1, MonitoredScheduledTask::get()); - $this->assertEquals([], $this->ohDear->getSyncedCronCheckAttributes()); - } -} +beforeEach(function () { + TestTime::freeze('Y-m-d H:i:s', '2020-01-01 00:00:00'); +}); + +it('can sync the schedule with the db and oh dear', function () { + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy')->everyMinute(); + $schedule->exec('execute')->everyFifteenMinutes(); + $schedule->call(fn () => 1 + 1)->hourly()->monitorName('my-closure'); + $schedule->job(new TestJob())->daily()->timezone('Asia/Kolkata'); + }); + + $this->artisan(SyncCommand::class); + + $monitoredScheduledTasks = MonitoredScheduledTask::get(); + expect($monitoredScheduledTasks)->toHaveCount(4); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'dummy', + 'type' => 'command', + 'cron_expression' => '* * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'execute', + 'type' => 'shell', + 'cron_expression' => '*/15 * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-execute', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'my-closure', + 'type' => 'closure', + 'cron_expression' => '0 * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-my-closure', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => TestJob::class, + 'type' => 'job', + 'cron_expression' => '0 0 * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-' . urlencode(TestJob::class), + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'Asia/Kolkata', + ]); + + $this->assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); +}); + +it('will not monitor commands without a name', function () { + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->call(fn () => 'a closure has no name')->hourly(); + }); + + $this->artisan(SyncCommand::class); + + $monitoredScheduledTasks = MonitoredScheduledTask::get(); + expect($monitoredScheduledTasks)->toHaveCount(0); + + expect($this->ohDear->getSyncedCronCheckAttributes())->toEqual([]); +}); + +it('will remove old tasks from the database', function () { + MonitoredScheduledTask::factory()->create(['name' => 'old-task']); + expect(MonitoredScheduledTask::get())->toHaveCount(1); + + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('new')->everyMinute(); + }); + + $this->artisan(SyncCommand::class); + + expect(MonitoredScheduledTask::get())->toHaveCount(1); + + expect(MonitoredScheduledTask::first()->name)->toEqual('new'); +}); + +it('can use custom grace time', function () { + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy')->everyMinute()->graceTimeInMinutes(15); + }); + + $this->artisan(SyncCommand::class); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'grace_time_in_minutes' => 15, + ]); + + $syncedCronChecks = $this->ohDear->getSyncedCronCheckAttributes(); + + expect($syncedCronChecks[0]['grace_time_in_minutes'])->toEqual(15); +}); + +it('will not monitor tasks that should not be monitored', function () { + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy')->everyMinute()->doNotMonitor(); + }); + + $this->artisan(SyncCommand::class); + + expect(MonitoredScheduledTask::get())->toHaveCount(0); + + expect($this->ohDear->getSyncedCronCheckAttributes())->toEqual([]); +}); + +it('will remove tasks from the db that should not be monitored anymore', function () { + MonitoredScheduledTask::factory()->create(['name' => 'not-monitored']); + expect(MonitoredScheduledTask::get())->toHaveCount(1); + + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('not-monitored')->everyMinute()->doNotMonitor(); + }); + $this->artisan(SyncCommand::class); + + expect(MonitoredScheduledTask::get())->toHaveCount(0); +}); + +it('will update tasks that have their schedule updated', function () { + $monitoredScheduledTask = MonitoredScheduledTask::factory()->create([ + 'name' => 'dummy', + 'cron_expression' => '* * * * *', + ]); + + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy')->daily(); + }); + $this->artisan(SyncCommand::class); + + expect(MonitoredScheduledTask::get())->toHaveCount(1); + expect($monitoredScheduledTask->refresh()->cron_expression)->toEqual('0 0 * * *'); +}); + +it('will not sync with oh dear when no site id is set', function () { + config()->set('schedule-monitor.oh_dear.site_id', null); + + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy')->daily(); + }); + $this->artisan(SyncCommand::class); + expect(MonitoredScheduledTask::get())->toHaveCount(1); + expect($this->ohDear->getSyncedCronCheckAttributes())->toEqual([]); +}); diff --git a/tests/Commands/VerifyCommandTest.php b/tests/Commands/VerifyCommandTest.php index a2b92a7..4fa3c4b 100644 --- a/tests/Commands/VerifyCommandTest.php +++ b/tests/Commands/VerifyCommandTest.php @@ -1,36 +1,24 @@ artisan(VerifyCommand::class)->assertExitCode(0); - } +it('can verify the connection to oh dear', function () { + $this->artisan(VerifyCommand::class)->assertExitCode(0); +}); - /** @test */ - public function it_will_throw_an_exception_if_the_api_token_is_not_set() - { - config()->set('schedule-monitor.oh_dear.api_token', null); +it('will throw an exception if the api token is not set', function () { + config()->set('schedule-monitor.oh_dear.api_token', null); - $this->expectException(Exception::class); + $this->expectException(Exception::class); - $this->artisan(VerifyCommand::class)->assertExitCode(0); - } + $this->artisan(VerifyCommand::class)->assertExitCode(0); +}); - /** @test */ - public function it_will_throw_an_exception_if_the_site_id_is_not_set() - { - config()->set('schedule-monitor.oh_dear.site_id', null); +it('will throw an exception if the site id is not set', function () { + config()->set('schedule-monitor.oh_dear.site_id', null); - $this->expectException(Exception::class); + $this->expectException(Exception::class); - $this->artisan(VerifyCommand::class)->assertExitCode(0); - } -} + $this->artisan(VerifyCommand::class)->assertExitCode(0); +}); diff --git a/tests/Pest.php b/tests/Pest.php index fa36d22..fb762f2 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,6 @@ in(__DIR__); +uses(TestCase::class, MatchesSnapshots::class)->in(__DIR__); diff --git a/tests/ScheduledTaskSubscriber/PingFailedToOhDearTest.php b/tests/ScheduledTaskSubscriber/PingFailedToOhDearTest.php index a1fc593..f8e3606 100644 --- a/tests/ScheduledTaskSubscriber/PingFailedToOhDearTest.php +++ b/tests/ScheduledTaskSubscriber/PingFailedToOhDearTest.php @@ -1,59 +1,44 @@ Http::response('ok', 200), + ]); - TestTime::freeze('Y-m-d H:i:s', '2020-01-01 00:00:00'); + $this->meta = [ + 'failure_message' => 'failure', + ]; - Http::fake([ - 'ping.ohdear.app/*' => Http::response('ok', 200), - ]); + $this->monitoredScheduledTaskLogItem = MonitoredScheduledTaskLogItem::factory()->create([ + 'type' => MonitoredScheduledTaskLogItem::TYPE_FAILED, + 'meta' => $this->meta, + ]); +}); - $this->meta = [ - 'failure_message' => 'failure', - ]; +it('can ping oh dear when a scheduled task fails', function () { + dispatch(new PingOhDearJob($this->monitoredScheduledTaskLogItem)); - $this->monitoredScheduledTaskLogItem = MonitoredScheduledTaskLogItem::factory()->create([ - 'type' => MonitoredScheduledTaskLogItem::TYPE_FAILED, - 'meta' => $this->meta, - ]); - } - - /** @test */ - public function it_can_ping_oh_dear_when_a_scheduled_task_fails() - { - dispatch(new PingOhDearJob($this->monitoredScheduledTaskLogItem)); + $this->assertEquals( + $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->refresh()->last_pinged_at->format('Y-m-d H:i:s'), + now()->format('Y-m-d H:i:s'), + ); + Http::assertSent(function (Request $request) { $this->assertEquals( - $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->refresh()->last_pinged_at->format('Y-m-d H:i:s'), - now()->format('Y-m-d H:i:s'), + $request->url(), + $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->ping_url . '/failed' ); - Http::assertSent(function (Request $request) { - $this->assertEquals( - $request->url(), - $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->ping_url . '/failed' - ); - - $this->assertEquals($this->meta, $request->data()); + expect($request->data())->toEqual($this->meta); - return true; - }); - } -} + return true; + }); +}); diff --git a/tests/ScheduledTaskSubscriber/PingFinishedToOhDearTest.php b/tests/ScheduledTaskSubscriber/PingFinishedToOhDearTest.php index 0221cb4..4bc1a10 100644 --- a/tests/ScheduledTaskSubscriber/PingFinishedToOhDearTest.php +++ b/tests/ScheduledTaskSubscriber/PingFinishedToOhDearTest.php @@ -1,61 +1,46 @@ Http::response('ok', 200), + ]); - TestTime::freeze('Y-m-d H:i:s', '2020-01-01 00:00:00'); + $this->meta = [ + 'runtime' => 12.34, + 'exit_code' => 123, + 'memory' => 12345, + ]; - Http::fake([ - 'ping.ohdear.app/*' => Http::response('ok', 200), - ]); + $this->monitoredScheduledTaskLogItem = MonitoredScheduledTaskLogItem::factory()->create([ + 'type' => MonitoredScheduledTaskLogItem::TYPE_FINISHED, + 'meta' => $this->meta, + ]); +}); - $this->meta = [ - 'runtime' => 12.34, - 'exit_code' => 123, - 'memory' => 12345, - ]; +it('can ping oh dear when a scheduled task finishes', function () { + dispatch(new PingOhDearJob($this->monitoredScheduledTaskLogItem)); - $this->monitoredScheduledTaskLogItem = MonitoredScheduledTaskLogItem::factory()->create([ - 'type' => MonitoredScheduledTaskLogItem::TYPE_FINISHED, - 'meta' => $this->meta, - ]); - } - - /** @test */ - public function it_can_ping_oh_dear_when_a_scheduled_task_finishes() - { - dispatch(new PingOhDearJob($this->monitoredScheduledTaskLogItem)); + $this->assertEquals( + $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->refresh()->last_pinged_at->format('Y-m-d H:i:s'), + now()->format('Y-m-d H:i:s'), + ); + Http::assertSent(function (Request $request) { $this->assertEquals( - $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->refresh()->last_pinged_at->format('Y-m-d H:i:s'), - now()->format('Y-m-d H:i:s'), + $request->url(), + $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->ping_url . '/finished' ); - Http::assertSent(function (Request $request) { - $this->assertEquals( - $request->url(), - $this->monitoredScheduledTaskLogItem->monitoredScheduledTask->ping_url . '/finished' - ); - - $this->assertEquals($this->meta, $request->data()); + expect($request->data())->toEqual($this->meta); - return true; - }); - } -} + return true; + }); +}); diff --git a/tests/ScheduledTaskSubscriber/ScheduledTaskSubscriberTest.php b/tests/ScheduledTaskSubscriber/ScheduledTaskSubscriberTest.php index 9adf67e..c91ab06 100644 --- a/tests/ScheduledTaskSubscriber/ScheduledTaskSubscriberTest.php +++ b/tests/ScheduledTaskSubscriber/ScheduledTaskSubscriberTest.php @@ -1,8 +1,5 @@ call(fn () => 1 + 1)->everyMinute()->monitorName('dummy-task'); - }); - - File::copy(__DIR__.'/../stubs/artisan', base_path('artisan')); - } - - public function tearDown(): void - { - parent::tearDown(); - - File::delete(base_path('artisan')); - } - - /** @test */ - public function it_will_fire_a_job_and_create_a_log_item_when_a_monitored_scheduled_task_finished() - { - $this->artisan(SyncCommand::class)->assertExitCode(0); - $this->artisan('schedule:run')->assertExitCode(0); - - Bus::assertDispatched(function (PingOhDearJob $job) { - $monitoredScheduledTask = $job->logItem->monitoredScheduledTask; - - return $monitoredScheduledTask->name === 'dummy-task'; - }); - - $logTypes = MonitoredScheduledTask::findByName('dummy-task')->logItems->pluck('type')->toArray(); - - $this->assertEquals([ - MonitoredScheduledTaskLogItem::TYPE_FINISHED, - MonitoredScheduledTaskLogItem::TYPE_STARTING, - ], $logTypes); - } - - /** @test */ - public function it_will_log_skipped_scheduled_tasks() - { - TestKernel::replaceScheduledTasks(function (Schedule $schedule) { - $schedule - ->call(fn () => TestTime::addSecond()) - ->everyMinute()->skip(fn () => true) - ->monitorName('dummy-task'); - }); - $this->artisan(SyncCommand::class)->assertExitCode(0); - - $this->artisan('schedule:run')->assertExitCode(0); - - $logTypes = MonitoredScheduledTask::findByName('dummy-task')->logItems->pluck('type')->toArray(); - - $this->assertEquals([ - MonitoredScheduledTaskLogItem::TYPE_SKIPPED, - ], $logTypes); - } - - /** @test */ - public function it_will_log_failures_of_scheduled_tasks() - { - TestKernel::replaceScheduledTasks(function (Schedule $schedule) { - $schedule - ->call(function () { - throw new Exception("exception"); - }) - ->everyMinute() - ->monitorName('failing-task'); - }); - - $this->artisan(SyncCommand::class)->assertExitCode(0); - $this->artisan('schedule:run')->assertExitCode(0); - - $logTypes = MonitoredScheduledTask::findByName('failing-task') - ->logItems - ->pluck('type') - ->toArray(); - - $this->assertEquals([ - MonitoredScheduledTaskLogItem::TYPE_FAILED, - MonitoredScheduledTaskLogItem::TYPE_STARTING, - ], $logTypes); - } - - /** @test */ - public function it_will_mark_a_task_as_failed_when_it_throws_an_exception() - { - File::delete(base_path('artisan')); - - TestKernel::replaceScheduledTasks(function (Schedule $schedule) { - $schedule->command(FailingCommand::class)->everyMinute(); - }); - - $this->artisan(SyncCommand::class)->assertExitCode(0); - $this->artisan('schedule:run')->assertExitCode(0); - - $logTypes = MonitoredScheduledTask::findByName('failing-command') - ->logItems - ->pluck('type') - ->values() - ->toArray(); - - $this->assertEquals([ - MonitoredScheduledTaskLogItem::TYPE_FAILED, - MonitoredScheduledTaskLogItem::TYPE_STARTING, - ], $logTypes); - } - - /** @test */ - public function it_will_not_fire_a_job_when_a_scheduled_task_finished_that_is_not_monitored() - { - // running the schedule without syncing to oh dear - $this->artisan('schedule:run')->assertExitCode(0); - - Bus::assertNotDispatched(PingOhDearJob::class); - } - - /** @test */ - public function it_can_use_a_specific_queue_to_ping_oh_dear() - { - Bus::fake(); - - config()->set('schedule-monitor.oh_dear.queue', 'custom-queue'); - - $this->artisan(SyncCommand::class)->assertExitCode(0); - $this->artisan('schedule:run')->assertExitCode(0); - - Bus::assertDispatched(function (PingOhDearJob $job) { - return $job->queue === 'custom-queue'; - }); - } - - /** @test */ - public function it_stores_the_command_output_to_db() - { - TestKernel::replaceScheduledTasks(function (Schedule $schedule) { - $schedule - ->command('help') - ->everyMinute() - ->storeOutputInDb() - ->monitorName('dummy-task'); - }); - - $this->artisan(SyncCommand::class)->assertExitCode(0); - $this->artisan('schedule:run')->assertExitCode(0); - - $task = MonitoredScheduledTask::findByName('dummy-task'); - $logItem = $task->logItems()->where('type', MonitoredScheduledTaskLogItem::TYPE_FINISHED)->first(); - - $this->assertStringContainsString('help for a command', $logItem->meta['output'] ?? ''); - } - - /** @test */ - public function it_does_not_store_the_command_output_to_db() - { - TestKernel::replaceScheduledTasks(function (Schedule $schedule) { - $schedule - ->command('help') - ->everyMinute() - ->monitorName('dummy-task'); - }); - - $this->artisan(SyncCommand::class)->assertExitCode(0); - $this->artisan('schedule:run')->assertExitCode(0); - - $task = MonitoredScheduledTask::findByName('dummy-task'); - $logItem = $task->logItems()->where('type', MonitoredScheduledTaskLogItem::TYPE_FINISHED)->first(); - - $this->assertNull($logItem->meta['output']); - } -} +beforeEach(function () { + Bus::fake(); + + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->call(fn () => 1 + 1)->everyMinute()->monitorName('dummy-task'); + }); + + File::copy(__DIR__.'/../stubs/artisan', base_path('artisan')); +}); + +afterEach(function () { + File::delete(base_path('artisan')); +}); + +it('will fire a job and create a log item when a monitored scheduled task finished', function () { + $this->artisan(SyncCommand::class)->assertExitCode(0); + $this->artisan('schedule:run')->assertExitCode(0); + + Bus::assertDispatched(function (PingOhDearJob $job) { + $monitoredScheduledTask = $job->logItem->monitoredScheduledTask; + + return $monitoredScheduledTask->name === 'dummy-task'; + }); + + $logTypes = MonitoredScheduledTask::findByName('dummy-task')->logItems->pluck('type')->toArray(); + + $this->assertEquals([ + MonitoredScheduledTaskLogItem::TYPE_FINISHED, + MonitoredScheduledTaskLogItem::TYPE_STARTING, + ], $logTypes); +}); + +it('will log skipped scheduled tasks', function () { + TestKernel::replaceScheduledTasks(function (Schedule $schedule) { + $schedule + ->call(fn () => TestTime::addSecond()) + ->everyMinute()->skip(fn () => true) + ->monitorName('dummy-task'); + }); + $this->artisan(SyncCommand::class)->assertExitCode(0); + + $this->artisan('schedule:run')->assertExitCode(0); + + $logTypes = MonitoredScheduledTask::findByName('dummy-task')->logItems->pluck('type')->toArray(); + + $this->assertEquals([ + MonitoredScheduledTaskLogItem::TYPE_SKIPPED, + ], $logTypes); +}); + +it('will log failures of scheduled tasks', function () { + TestKernel::replaceScheduledTasks(function (Schedule $schedule) { + $schedule + ->call(function () { + throw new Exception("exception"); + }) + ->everyMinute() + ->monitorName('failing-task'); + }); + + $this->artisan(SyncCommand::class)->assertExitCode(0); + $this->artisan('schedule:run')->assertExitCode(0); + + $logTypes = MonitoredScheduledTask::findByName('failing-task') + ->logItems + ->pluck('type') + ->toArray(); + + $this->assertEquals([ + MonitoredScheduledTaskLogItem::TYPE_FAILED, + MonitoredScheduledTaskLogItem::TYPE_STARTING, + ], $logTypes); +}); + +it('will mark a task as failed when it throws an exception', function () { + File::delete(base_path('artisan')); + + TestKernel::replaceScheduledTasks(function (Schedule $schedule) { + $schedule->command(FailingCommand::class)->everyMinute(); + }); + + $this->artisan(SyncCommand::class)->assertExitCode(0); + $this->artisan('schedule:run')->assertExitCode(0); + + $logTypes = MonitoredScheduledTask::findByName('failing-command') + ->logItems + ->pluck('type') + ->values() + ->toArray(); + + $this->assertEquals([ + MonitoredScheduledTaskLogItem::TYPE_FAILED, + MonitoredScheduledTaskLogItem::TYPE_STARTING, + ], $logTypes); +}); + +it('will not fire a job when a scheduled task finished that is not monitored', function () { + // running the schedule without syncing to oh dear + $this->artisan('schedule:run')->assertExitCode(0); + + Bus::assertNotDispatched(PingOhDearJob::class); +}); + +it('can use a specific queue to ping oh dear', function () { + Bus::fake(); + + config()->set('schedule-monitor.oh_dear.queue', 'custom-queue'); + + $this->artisan(SyncCommand::class)->assertExitCode(0); + $this->artisan('schedule:run')->assertExitCode(0); + + Bus::assertDispatched(function (PingOhDearJob $job) { + return $job->queue === 'custom-queue'; + }); +}); + +it('stores the command output to db', function () { + TestKernel::replaceScheduledTasks(function (Schedule $schedule) { + $schedule + ->command('help') + ->everyMinute() + ->storeOutputInDb() + ->monitorName('dummy-task'); + }); + + $this->artisan(SyncCommand::class)->assertExitCode(0); + $this->artisan('schedule:run')->assertExitCode(0); + + $task = MonitoredScheduledTask::findByName('dummy-task'); + $logItem = $task->logItems()->where('type', MonitoredScheduledTaskLogItem::TYPE_FINISHED)->first(); + + expect($logItem->meta['output'] ?? '')->toContain('help for a command'); +}); + +it('does not store the command output to db', function () { + TestKernel::replaceScheduledTasks(function (Schedule $schedule) { + $schedule + ->command('help') + ->everyMinute() + ->monitorName('dummy-task'); + }); + + $this->artisan(SyncCommand::class)->assertExitCode(0); + $this->artisan('schedule:run')->assertExitCode(0); + + $task = MonitoredScheduledTask::findByName('dummy-task'); + $logItem = $task->logItems()->where('type', MonitoredScheduledTaskLogItem::TYPE_FINISHED)->first(); + + expect($logItem->meta['output'])->toBeNull(); +}); diff --git a/tests/Support/ScheduledTasksTest.php b/tests/Support/ScheduledTasksTest.php index bbb5977..9fe48d7 100644 --- a/tests/Support/ScheduledTasksTest.php +++ b/tests/Support/ScheduledTasksTest.php @@ -1,60 +1,50 @@ command('dummy')->everyMinute(); - $schedule->call(fn () => 1 + 1)->hourly()->monitorName('dummy'); - $schedule->command('other-dummy')->everyMinute(); - }); - - $scheduledTasks = ScheduledTasks::createForSchedule(); - - $uniqueTasks = $scheduledTasks->uniqueTasks() - ->map(fn (Task $task) => "{$task->name()}-{$task->type()}") - ->toArray(); - - $this->assertEquals([ - 'dummy-command', - 'other-dummy-command', - ], $uniqueTasks); - - $duplicateTasks = $scheduledTasks->duplicateTasks() - ->map(fn (Task $task) => "{$task->name()}-{$task->type()}") - ->toArray(); - - $this->assertEquals([ - 'dummy-closure', - ], $duplicateTasks); - } - - /** @test */ - public function it_can_get_only_the_tasks_that_run_in_the_current_environment_from_the_schedule() - { - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('dummy')->environments('testing'); // current - $schedule->command('other-dummy')->environments('production'); - }); - - $scheduledTasks = ScheduledTasks::createForSchedule(); - - $uniqueTasks = $scheduledTasks->uniqueTasks() - ->map(fn (Task $task) => "{$task->name()}-{$task->type()}") - ->toArray(); - - $this->assertEquals([ - 'dummy-command', - ], $uniqueTasks); - } -} +it('can get the unique and duplicate tasks from the schedule', function () { + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy')->everyMinute(); + $schedule->call(fn () => 1 + 1)->hourly()->monitorName('dummy'); + $schedule->command('other-dummy')->everyMinute(); + }); + + $scheduledTasks = ScheduledTasks::createForSchedule(); + + $uniqueTasks = $scheduledTasks->uniqueTasks() + ->map(fn (Task $task) => "{$task->name()}-{$task->type()}") + ->toArray(); + + $this->assertEquals([ + 'dummy-command', + 'other-dummy-command', + ], $uniqueTasks); + + $duplicateTasks = $scheduledTasks->duplicateTasks() + ->map(fn (Task $task) => "{$task->name()}-{$task->type()}") + ->toArray(); + + $this->assertEquals([ + 'dummy-closure', + ], $duplicateTasks); +}); + +it('can get only the tasks that run in the current environment from the schedule', function () { + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy')->environments('testing'); // current + $schedule->command('other-dummy')->environments('production'); + }); + + $scheduledTasks = ScheduledTasks::createForSchedule(); + + $uniqueTasks = $scheduledTasks->uniqueTasks() + ->map(fn (Task $task) => "{$task->name()}-{$task->type()}") + ->toArray(); + + $this->assertEquals([ + 'dummy-command', + ], $uniqueTasks); +}); diff --git a/tests/Support/ScheduledTestFactoryTest.php b/tests/Support/ScheduledTestFactoryTest.php index 6d2daba..84ebe7d 100644 --- a/tests/Support/ScheduledTestFactoryTest.php +++ b/tests/Support/ScheduledTestFactoryTest.php @@ -1,100 +1,80 @@ app->make(Schedule::class)->command('foo:bar'); +it('will return a command task for a scheduled command task', function () { + $event = app()->make(Schedule::class)->command('foo:bar'); - $task = ScheduledTaskFactory::createForEvent($event); + $task = ScheduledTaskFactory::createForEvent($event); - $this->assertInstanceOf(CommandTask::class, $task); - $this->assertEquals('foo:bar', $task->name()); - } + expect($task)->toBeInstanceOf(CommandTask::class); + expect($task->name())->toEqual('foo:bar'); +}); - /** @test */ - public function it_will_return_a_shell_task_for_a_scheduled_shell_task() - { - $event = $this->app->make(Schedule::class)->exec('a bash command'); +it('will return a shell task for a scheduled shell task', function () { + $event = app()->make(Schedule::class)->exec('a bash command'); - $task = ScheduledTaskFactory::createForEvent($event); + $task = ScheduledTaskFactory::createForEvent($event); - $this->assertInstanceOf(ShellTask::class, $task); - $this->assertEquals('a bash command', $task->name()); - } + expect($task)->toBeInstanceOf(ShellTask::class); + expect($task->name())->toEqual('a bash command'); +}); - /** @test */ - public function it_will_return_a_job_task_for_a_scheduled_job_task() - { - $event = $this->app->make(Schedule::class)->job(new TestJob()); +it('will return a job task for a scheduled job task', function () { + $event = app()->make(Schedule::class)->job(new TestJob()); - $task = ScheduledTaskFactory::createForEvent($event); + $task = ScheduledTaskFactory::createForEvent($event); - $this->assertInstanceOf(JobTask::class, $task); - $this->assertEquals(TestJob::class, $task->name()); - } + expect($task)->toBeInstanceOf(JobTask::class); + expect($task->name())->toEqual(TestJob::class); +}); - /** @test */ - public function it_will_return_a_closure_task_for_a_scheduled_closure_task() - { - $event = $this->app->make(Schedule::class)->call(function () { - $i = 1; - }); +it('will return a closure task for a scheduled closure task', function () { + $event = app()->make(Schedule::class)->call(function () { + $i = 1; + }); - $task = ScheduledTaskFactory::createForEvent($event); + $task = ScheduledTaskFactory::createForEvent($event); - $this->assertInstanceOf(ClosureTask::class, $task); - $this->assertNull($task->name()); - } + expect($task)->toBeInstanceOf(ClosureTask::class); + expect($task->name())->toBeNull(); +}); - /** @test */ - public function the_task_name_can_be_manually_set() - { - $event = $this->app->make(Schedule::class)->command('foo:bar')->monitorName('my-custom-name'); +test('the task name can be manually set', function () { + $event = app()->make(Schedule::class)->command('foo:bar')->monitorName('my-custom-name'); - $task = ScheduledTaskFactory::createForEvent($event); + $task = ScheduledTaskFactory::createForEvent($event); - $this->assertEquals('my-custom-name', $task->name()); - } + expect($task->name())->toEqual('my-custom-name'); +}); - /** @test */ - public function a_task_can_be_marked_as_not_to_be_monitored() - { - $event = $this->app->make(Schedule::class)->command('foo:bar'); - $this->assertTrue(ScheduledTaskFactory::createForEvent($event)->shouldMonitor()); +test('a task can be marked as not to be monitored', function () { + $event = app()->make(Schedule::class)->command('foo:bar'); + expect(ScheduledTaskFactory::createForEvent($event)->shouldMonitor())->toBeTrue(); - $event = $this->app->make(Schedule::class)->command('foo:bar')->doNotMonitor(); - $this->assertFalse(ScheduledTaskFactory::createForEvent($event)->shouldMonitor()); - } + $event = app()->make(Schedule::class)->command('foo:bar')->doNotMonitor(); + expect(ScheduledTaskFactory::createForEvent($event)->shouldMonitor())->toBeFalse(); +}); - /** @test */ - public function it_can_handle_timezones() - { - TestTime::freeze('Y-m-d H:i:s', '2020-02-01 00:00:00'); +it('can handle timezones', function () { + TestTime::freeze('Y-m-d H:i:s', '2020-02-01 00:00:00'); - $schedule = $this->app->make(Schedule::class); + $schedule = app()->make(Schedule::class); - $appTimezoneEvent = $schedule->command('foo:bar')->daily(); - $appTimezoneTask = ScheduledTaskFactory::createForEvent($appTimezoneEvent); - $this->assertEquals('UTC', $appTimezoneTask->timezone()); - $this->assertEquals('2020-02-02 00:00:00', $appTimezoneTask->nextRunAt()->format('Y-m-d H:i:s')); + $appTimezoneEvent = $schedule->command('foo:bar')->daily(); + $appTimezoneTask = ScheduledTaskFactory::createForEvent($appTimezoneEvent); + expect($appTimezoneTask->timezone())->toEqual('UTC'); + expect($appTimezoneTask->nextRunAt()->format('Y-m-d H:i:s'))->toEqual('2020-02-02 00:00:00'); - $otherTimezoneEvent = $schedule->command('foo:bar')->daily()->timezone('Asia/Kolkata'); - $otherTimezoneTask = ScheduledTaskFactory::createForEvent($otherTimezoneEvent); - $this->assertEquals('Asia/Kolkata', $otherTimezoneTask->timezone()); - $this->assertEquals('2020-02-01 18:30:00', $otherTimezoneTask->nextRunAt()->format('Y-m-d H:i:s')); - } -} + $otherTimezoneEvent = $schedule->command('foo:bar')->daily()->timezone('Asia/Kolkata'); + $otherTimezoneTask = ScheduledTaskFactory::createForEvent($otherTimezoneEvent); + expect($otherTimezoneTask->timezone())->toEqual('Asia/Kolkata'); + expect($otherTimezoneTask->nextRunAt()->format('Y-m-d H:i:s'))->toEqual('2020-02-01 18:30:00'); +}); diff --git a/tests/Support/Tasks/LastRunFailedTest.php b/tests/Support/Tasks/LastRunFailedTest.php index 3ec2fab..4921fd4 100644 --- a/tests/Support/Tasks/LastRunFailedTest.php +++ b/tests/Support/Tasks/LastRunFailedTest.php @@ -1,74 +1,52 @@ event = app()->make(Schedule::class)->command('foo:bar'); - TestTime::freeze(); + $this->monitoredScheduledTask = MonitoredScheduledTask::factory()->create([ + 'name' => 'foo:bar', + ]); +}); - $this->event = $this->app->make(Schedule::class)->command('foo:bar'); +it('will return false if it didnt start or fail yet', function () { + expect(task()->lastRunFailed())->toBeFalse(); +}); - $this->monitoredScheduledTask = MonitoredScheduledTask::factory()->create([ - 'name' => 'foo:bar', - ]); - } +it('will return false if it did start but not fail yet', function () { + $this->monitoredScheduledTask->update(['last_started_at' => now()]); - /** @test */ - public function it_will_return_false_if_it_didnt_start_or_fail_yet() - { - $this->assertFalse($this->task()->lastRunFailed()); - } + expect(task()->lastRunFailed())->toBeFalse(); +}); - /** @test */ - public function it_will_return_false_if_it_did_start_but_not_fail_yet() - { - $this->monitoredScheduledTask->update(['last_started_at' => now()]); +it('will return true if it failed after it started', function () { + $this->monitoredScheduledTask->update(['last_started_at' => now()]); - $this->assertFalse($this->task()->lastRunFailed()); - } + TestTime::addMinute(); - /** @test */ - public function it_will_return_true_if_it_failed_after_it_started() - { - $this->monitoredScheduledTask->update(['last_started_at' => now()]); + $this->monitoredScheduledTask->update(['last_failed_at' => now()]); - TestTime::addMinute(); + expect(task()->lastRunFailed())->toBeTrue(); +}); - $this->monitoredScheduledTask->update(['last_failed_at' => now()]); +it('will return false if it started after it failed', function () { + $this->monitoredScheduledTask->update(['last_failed_at' => now()]); - $this->assertTrue($this->task()->lastRunFailed()); - } + TestTime::addMinute(); - /** @test */ - public function it_will_return_false_if_it_started_after_it_failed() - { - $this->monitoredScheduledTask->update(['last_failed_at' => now()]); + $this->monitoredScheduledTask->update(['last_started_at' => now()]); - TestTime::addMinute(); + expect(task()->lastRunFailed())->toBeFalse(); +}); - $this->monitoredScheduledTask->update(['last_started_at' => now()]); - - $this->assertFalse($this->task()->lastRunFailed()); - } - - protected function task(): Task - { - return ScheduledTaskFactory::createForEvent($this->event); - } +function task(): Task +{ + return ScheduledTaskFactory::createForEvent(test()->event); } diff --git a/tests/Support/Tasks/LastRunFinishedTooLateTest.php b/tests/Support/Tasks/LastRunFinishedTooLateTest.php index 90a50a3..760d4ce 100644 --- a/tests/Support/Tasks/LastRunFinishedTooLateTest.php +++ b/tests/Support/Tasks/LastRunFinishedTooLateTest.php @@ -1,74 +1,56 @@ event = app()->make(Schedule::class) + ->command('foo:bar') + ->hourly() + ->graceTimeInMinutes(5); - TestTime::freeze('H:i:s', '00:00:00'); + $this->monitoredScheduledTask = MonitoredScheduledTask::factory()->create([ + 'name' => 'foo:bar', + ]); +}); - $this->event = $this->app->make(Schedule::class) - ->command('foo:bar') - ->hourly() - ->graceTimeInMinutes(5); +test('a task will be consider too late if does not finish within the grace period', function () { + expect(createTask()->lastRunFinishedTooLate())->toBeFalse(); - $this->monitoredScheduledTask = MonitoredScheduledTask::factory()->create([ - 'name' => 'foo:bar', - ]); - } + TestTime::addMinutes(5); + expect(createTask()->lastRunFinishedTooLate())->toBeFalse(); - /** @test */ - public function a_task_will_be_consider_too_late_if_does_not_finish_within_the_grace_period() - { - $this->assertFalse($this->task()->lastRunFinishedTooLate()); + TestTime::addSecond(); + expect(createTask()->lastRunFinishedTooLate())->toBeTrue(); +}); - TestTime::addMinutes(5); - $this->assertFalse($this->task()->lastRunFinishedTooLate()); +it('will reset the period', function () { + expect(createTask()->lastRunFinishedTooLate())->toBeFalse(); - TestTime::addSecond(); - $this->assertTrue($this->task()->lastRunFinishedTooLate()); - } + TestTime::addMinutes(4); + expect(createTask()->lastRunFinishedTooLate())->toBeFalse(); - /** @test */ - public function it_will_reset_the_period() - { - $this->assertFalse($this->task()->lastRunFinishedTooLate()); + $this->monitoredScheduledTask->update(['last_finished_at' => now()]); - TestTime::addMinutes(4); - $this->assertFalse($this->task()->lastRunFinishedTooLate()); + TestTime::addMinutes(10); + expect(createTask()->lastRunFinishedTooLate())->toBeFalse(); - $this->monitoredScheduledTask->update(['last_finished_at' => now()]); + TestTime::addMinutes(46); // now at 1:00; + expect(createTask()->lastRunFinishedTooLate())->toBeFalse(); - TestTime::addMinutes(10); - $this->assertFalse($this->task()->lastRunFinishedTooLate()); + TestTime::addMinutes(5); + expect(createTask()->lastRunFinishedTooLate())->toBeFalse(); - TestTime::addMinutes(46); // now at 1:00; - $this->assertFalse($this->task()->lastRunFinishedTooLate()); + TestTime::addMinute(); + expect(createTask()->lastRunFinishedTooLate())->toBeTrue(); +}); - TestTime::addMinutes(5); - $this->assertFalse($this->task()->lastRunFinishedTooLate()); - - TestTime::addMinute(); - $this->assertTrue($this->task()->lastRunFinishedTooLate()); - } - - protected function task(): Task - { - return ScheduledTaskFactory::createForEvent($this->event); - } +function createTask(): Task +{ + return ScheduledTaskFactory::createForEvent(test()->event); } diff --git a/tests/Traits/UsesScheduleMonitoringModelsTest.php b/tests/Traits/UsesScheduleMonitoringModelsTest.php index bb50a89..c94f981 100644 --- a/tests/Traits/UsesScheduleMonitoringModelsTest.php +++ b/tests/Traits/UsesScheduleMonitoringModelsTest.php @@ -1,25 +1,17 @@ getMonitoredScheduleTaskModel(); - $monitorScheduleTaskLogItem = $model->getMonitoredScheduleTaskLogItemModel(); + $monitorScheduleTask = $model->getMonitoredScheduleTaskModel(); + $monitorScheduleTaskLogItem = $model->getMonitoredScheduleTaskLogItemModel(); - $this->assertInstanceOf(MonitoredScheduledTask::class, $monitorScheduleTask); - $this->assertInstanceOf(MonitoredScheduledTaskLogItem::class, $monitorScheduleTaskLogItem); - } -} + expect($monitorScheduleTask)->toBeInstanceOf(MonitoredScheduledTask::class); + expect($monitorScheduleTaskLogItem)->toBeInstanceOf(MonitoredScheduledTaskLogItem::class); +});