From ba14a1a5bcabab792617785e81486ee1b0df822e Mon Sep 17 00:00:00 2001 From: DamsFX Date: Thu, 28 Aug 2025 20:36:15 +0200 Subject: [PATCH 1/2] Register schedule commands --- .../Providers/ArtisanServiceProvider.php | 72 +++++----- tests/Scheduling/ScheduleListCommandTest.php | 136 ++++++++++++++++++ tests/Scheduling/ScheduleTestCommandTest.php | 102 +++++++++++++ 3 files changed, 275 insertions(+), 35 deletions(-) create mode 100644 tests/Scheduling/ScheduleListCommandTest.php create mode 100644 tests/Scheduling/ScheduleTestCommandTest.php diff --git a/src/Foundation/Providers/ArtisanServiceProvider.php b/src/Foundation/Providers/ArtisanServiceProvider.php index 0cf920083..d5780c979 100644 --- a/src/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Foundation/Providers/ArtisanServiceProvider.php @@ -1,4 +1,6 @@ - \Illuminate\Cache\Console\ClearCommand::class, - 'CacheForget' => \Illuminate\Cache\Console\ForgetCommand::class, - 'ClearCompiled' => \Winter\Storm\Foundation\Console\ClearCompiledCommand::class, - 'ConfigCache' => \Winter\Storm\Foundation\Console\ConfigCacheCommand::class, - 'ConfigClear' => \Winter\Storm\Foundation\Console\ConfigClearCommand::class, - 'Down' => \Illuminate\Foundation\Console\DownCommand::class, - 'Environment' => \Illuminate\Foundation\Console\EnvironmentCommand::class, - 'EventCache' => \Illuminate\Foundation\Console\EventCacheCommand::class, - 'EventClear' => \Illuminate\Foundation\Console\EventClearCommand::class, - 'EventList' => \Winter\Storm\Foundation\Console\EventListCommand::class, - 'KeyGenerate' => \Winter\Storm\Foundation\Console\KeyGenerateCommand::class, - 'Optimize' => \Illuminate\Foundation\Console\OptimizeCommand::class, - 'PackageDiscover' => \Illuminate\Foundation\Console\PackageDiscoverCommand::class, - 'QueueFailed' => \Illuminate\Queue\Console\ListFailedCommand::class, - 'QueueFlush' => \Illuminate\Queue\Console\FlushFailedCommand::class, - 'QueueForget' => \Illuminate\Queue\Console\ForgetFailedCommand::class, - 'QueueListen' => \Illuminate\Queue\Console\ListenCommand::class, - 'QueueMonitor' => \Illuminate\Queue\Console\MonitorCommand::class, - 'QueuePruneBatches' => \Illuminate\Queue\Console\PruneBatchesCommand::class, - 'QueuePruneFailedJobs' => \Illuminate\Queue\Console\PruneFailedJobsCommand::class, - 'QueueRestart' => \Illuminate\Queue\Console\RestartCommand::class, - 'QueueRetry' => \Illuminate\Queue\Console\RetryCommand::class, - 'QueueRetryBatch' => \Illuminate\Queue\Console\RetryBatchCommand::class, - 'QueueWork' => \Illuminate\Queue\Console\WorkCommand::class, - 'RouteCache' => \Illuminate\Foundation\Console\RouteCacheCommand::class, - 'RouteClear' => \Illuminate\Foundation\Console\RouteClearCommand::class, - 'RouteList' => \Illuminate\Foundation\Console\RouteListCommand::class, - 'ScheduleFinish' => \Illuminate\Console\Scheduling\ScheduleFinishCommand::class, - 'ScheduleRun' => \Illuminate\Console\Scheduling\ScheduleRunCommand::class, - 'Up' => \Illuminate\Foundation\Console\UpCommand::class, - 'ViewClear' => \Illuminate\Foundation\Console\ViewClearCommand::class, + 'CacheClear' => \Illuminate\Cache\Console\ClearCommand::class, + 'CacheForget' => \Illuminate\Cache\Console\ForgetCommand::class, + 'ClearCompiled' => \Winter\Storm\Foundation\Console\ClearCompiledCommand::class, + 'ConfigCache' => \Winter\Storm\Foundation\Console\ConfigCacheCommand::class, + 'ConfigClear' => \Winter\Storm\Foundation\Console\ConfigClearCommand::class, + 'Down' => \Illuminate\Foundation\Console\DownCommand::class, + 'Environment' => \Illuminate\Foundation\Console\EnvironmentCommand::class, + 'EventCache' => \Illuminate\Foundation\Console\EventCacheCommand::class, + 'EventClear' => \Illuminate\Foundation\Console\EventClearCommand::class, + 'EventList' => \Winter\Storm\Foundation\Console\EventListCommand::class, + 'KeyGenerate' => \Winter\Storm\Foundation\Console\KeyGenerateCommand::class, + 'Optimize' => \Illuminate\Foundation\Console\OptimizeCommand::class, + 'PackageDiscover' => \Illuminate\Foundation\Console\PackageDiscoverCommand::class, + 'QueueFailed' => \Illuminate\Queue\Console\ListFailedCommand::class, + 'QueueFlush' => \Illuminate\Queue\Console\FlushFailedCommand::class, + 'QueueForget' => \Illuminate\Queue\Console\ForgetFailedCommand::class, + 'QueueListen' => \Illuminate\Queue\Console\ListenCommand::class, + 'QueueMonitor' => \Illuminate\Queue\Console\MonitorCommand::class, + 'QueuePruneBatches' => \Illuminate\Queue\Console\PruneBatchesCommand::class, + 'QueuePruneFailedJobs' => \Illuminate\Queue\Console\PruneFailedJobsCommand::class, + 'QueueRestart' => \Illuminate\Queue\Console\RestartCommand::class, + 'QueueRetry' => \Illuminate\Queue\Console\RetryCommand::class, + 'QueueRetryBatch' => \Illuminate\Queue\Console\RetryBatchCommand::class, + 'QueueWork' => \Illuminate\Queue\Console\WorkCommand::class, + 'RouteCache' => \Illuminate\Foundation\Console\RouteCacheCommand::class, + 'RouteClear' => \Illuminate\Foundation\Console\RouteClearCommand::class, + 'RouteList' => \Illuminate\Foundation\Console\RouteListCommand::class, + 'ScheduleFinish' => \Illuminate\Console\Scheduling\ScheduleFinishCommand::class, + 'ScheduleList' => \Illuminate\Console\Scheduling\ScheduleListCommand::class, + 'ScheduleRun' => \Illuminate\Console\Scheduling\ScheduleRunCommand::class, + 'ScheduleTest' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class, + 'ScheduleWork' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class, + 'Up' => \Illuminate\Foundation\Console\UpCommand::class, + 'ViewClear' => \Illuminate\Foundation\Console\ViewClearCommand::class, // Currently unsupported in Winter: // @TODO: Assess for inclusion @@ -54,10 +59,7 @@ class ArtisanServiceProvider extends ArtisanServiceProviderBase // 'OptimizeClear' => OptimizeClearCommand::class, // 'QueueClear' => QueueClearCommand::class, // 'SchemaDump' => DumpCommand::class, - // 'ScheduleList' => \Illuminate\Console\Scheduling\ScheduleListCommand::class, // 'ScheduleClearCache' => ScheduleClearCacheCommand::class, - // 'ScheduleTest' => ScheduleTestCommand::class, - // 'ScheduleWork' => ScheduleWorkCommand::class, // 'ViewCache' => ViewCacheCommand::class, // Explicitly unsupported in Winter: diff --git a/tests/Scheduling/ScheduleListCommandTest.php b/tests/Scheduling/ScheduleListCommandTest.php new file mode 100644 index 000000000..ee81b47c5 --- /dev/null +++ b/tests/Scheduling/ScheduleListCommandTest.php @@ -0,0 +1,136 @@ + 80); + + $this->schedule = $this->app->make(Schedule::class); + } + + public function testDisplayEmptySchedule() + { + $this->artisan(ScheduleListCommand::class) + ->assertSuccessful() + ->expectsOutputToContain('No scheduled tasks have been defined.'); + } + + public function testDisplaySchedule() + { + $this->schedule->command(FooCommand::class)->quarterly(); + $this->schedule->command('inspire')->twiceDaily(14, 18); + $this->schedule->command('foobar', ['a' => 'b'])->everyMinute(); + $this->schedule->job(FooJob::class)->everyMinute(); + $this->schedule->command('inspire')->cron('0 9,17 * * *'); + $this->schedule->command('inspire')->cron("0 10\t* * *"); + $this->schedule->call(FooCall::class)->everyMinute(); + $this->schedule->call([FooCall::class, 'fooFunction'])->everyMinute(); + + $this->schedule->call(fn () => '')->everyMinute(); + $closureLineNumber = __LINE__ - 1; + $closureFilePath = __FILE__; + + $this->artisan(ScheduleListCommand::class) + ->assertSuccessful() + ->expectsOutput(' 0 0 1 1-12/3 * php artisan foo:command .... Next Due: 3 months from now') + ->expectsOutput(' 0 14,18 * * * php artisan inspire ........ Next Due: 14 hours from now') + ->expectsOutput(' * * * * * php artisan foobar a='.ProcessUtils::escapeArgument('b').' ... Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Winter\Storm\Tests\Scheduling\FooJob Next Due: 1 minute from now') + ->expectsOutput(' 0 9,17 * * * php artisan inspire ......... Next Due: 9 hours from now') + ->expectsOutput(' 0 10 * * * php artisan inspire ........ Next Due: 10 hours from now') + ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall::fooFunction Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Closure at: '.$closureFilePath.':'.$closureLineNumber.' Next Due: 1 minute from now'); + } + + public function testDisplayScheduleWithSort() + { + $this->schedule->command(FooCommand::class)->quarterly(); + $this->schedule->command('inspire')->twiceDaily(14, 18); + $this->schedule->command('foobar', ['a' => 'b'])->everyMinute(); + $this->schedule->job(FooJob::class)->everyMinute(); + $this->schedule->command('inspire')->cron('0 9,17 * * *'); + $this->schedule->command('inspire')->cron("0 10\t* * *"); + $this->schedule->call(FooCall::class)->everyMinute(); + $this->schedule->call([FooCall::class, 'fooFunction'])->everyMinute(); + + $this->schedule->call(fn () => '')->everyMinute(); + $closureLineNumber = __LINE__ - 1; + $closureFilePath = __FILE__; + + $this->artisan(ScheduleListCommand::class, ['--next' => true]) + ->assertSuccessful() + ->expectsOutput(' * * * * * php artisan foobar a='.ProcessUtils::escapeArgument('b').' ... Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Winter\Storm\Tests\Scheduling\FooJob Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Closure at: Winter\Storm\Tests\Scheduling\FooCall::fooFunction Next Due: 1 minute from now') + ->expectsOutput(' * * * * * Closure at: '.$closureFilePath.':'.$closureLineNumber.' Next Due: 1 minute from now') + ->expectsOutput(' 0 9,17 * * * php artisan inspire ......... Next Due: 9 hours from now') + ->expectsOutput(' 0 10 * * * php artisan inspire ........ Next Due: 10 hours from now') + ->expectsOutput(' 0 14,18 * * * php artisan inspire ........ Next Due: 14 hours from now') + ->expectsOutput(' 0 0 1 1-12/3 * php artisan foo:command .... Next Due: 3 months from now'); + } + + public function testDisplayScheduleInVerboseMode() + { + $this->schedule->command(FooCommand::class)->everyMinute(); + + $this->artisan(ScheduleListCommand::class, ['-v' => true]) + ->assertSuccessful() + ->expectsOutputToContain('Next Due: '.now()->setMinutes(1)->format('Y-m-d H:i:s P')) + ->expectsOutput(' ⇁ This is the description of the command.'); + } + + protected function tearDown(): void + { + putenv('SHELL_VERBOSITY'); + + parent::tearDown(); + } +} + +class FooCommand extends Command +{ + protected $signature = 'foo:command'; + + protected $description = 'This is the description of the command.'; +} + +class FooJob +{ +} + +class FooParamJob +{ + public function __construct($param) + { + } +} + +class FooCall +{ + public function __invoke(): void + { + } + + public function fooFunction(): void + { + } +} + diff --git a/tests/Scheduling/ScheduleTestCommandTest.php b/tests/Scheduling/ScheduleTestCommandTest.php new file mode 100644 index 000000000..f1c82f16a --- /dev/null +++ b/tests/Scheduling/ScheduleTestCommandTest.php @@ -0,0 +1,102 @@ +startOfYear()); + + $this->schedule = $this->app->make(Schedule::class); + } + + public function testRunNoDefinedCommands() + { + $this->artisan(ScheduleTestCommand::class) + ->assertSuccessful() + ->expectsOutputToContain('No scheduled commands have been defined.'); + } + + public function testRunNoMatchingCommand() + { + $this->schedule->command(BarCommandStub::class); + + $this->artisan(ScheduleTestCommand::class, ['--name' => 'missing:command']) + ->assertSuccessful() + ->expectsOutputToContain('No matching scheduled command found.'); + } + + public function testRunUsingNameOption() + { + $this->schedule->command(BarCommandStub::class)->name('bar-command'); + $this->schedule->job(BarJobStub::class); + $this->schedule->call(fn () => true)->name('callback'); + + $expectedOutput = windows_os() + ? 'Running ["artisan" bar:command]' + : "Running ['artisan' bar:command]"; + + $this->artisan(ScheduleTestCommand::class, ['--name' => 'bar:command']) + ->assertSuccessful() + ->expectsOutputToContain($expectedOutput); + + $this->artisan(ScheduleTestCommand::class, ['--name' => BarJobStub::class]) + ->assertSuccessful() + ->expectsOutputToContain(sprintf('Running [%s]', BarJobStub::class)); + + $this->artisan(ScheduleTestCommand::class, ['--name' => 'callback']) + ->assertSuccessful() + ->expectsOutputToContain('Running [callback]'); + } + + public function testRunUsingChoices() + { + $this->schedule->command(BarCommandStub::class)->name('bar-command'); + $this->schedule->job(BarJobStub::class); + $this->schedule->call(fn () => true)->name('callback'); + + $this->artisan(ScheduleTestCommand::class) + ->assertSuccessful() + ->expectsChoice( + 'Which command would you like to run?', + 'callback', + [Application::formatCommandString('bar:command'), BarJobStub::class, 'callback'], + true + ) + ->expectsOutputToContain('Running [callback]'); + } + + protected function tearDown(): void + { + parent::tearDown(); + + Carbon::setTestNow(null); + } +} + +class BarCommandStub extends Command +{ + protected $signature = 'bar:command'; + + protected $description = 'This is the description of the command.'; +} + +class BarJobStub +{ + public function __invoke() + { + // .. + } +} From 44fa1b15e3dd0d015a7c9b9101461ad074e4c148 Mon Sep 17 00:00:00 2001 From: DamsFX Date: Fri, 29 Aug 2025 11:03:16 +0200 Subject: [PATCH 2/2] Fix code quality checks --- .../Providers/ArtisanServiceProvider.php | 68 +++++++++---------- tests/Scheduling/ScheduleListCommandTest.php | 1 - 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/Foundation/Providers/ArtisanServiceProvider.php b/src/Foundation/Providers/ArtisanServiceProvider.php index d5780c979..75d161c32 100644 --- a/src/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Foundation/Providers/ArtisanServiceProvider.php @@ -15,40 +15,40 @@ class ArtisanServiceProvider extends ArtisanServiceProviderBase */ protected $commands = [ // Currently included in Winter - 'CacheClear' => \Illuminate\Cache\Console\ClearCommand::class, - 'CacheForget' => \Illuminate\Cache\Console\ForgetCommand::class, - 'ClearCompiled' => \Winter\Storm\Foundation\Console\ClearCompiledCommand::class, - 'ConfigCache' => \Winter\Storm\Foundation\Console\ConfigCacheCommand::class, - 'ConfigClear' => \Winter\Storm\Foundation\Console\ConfigClearCommand::class, - 'Down' => \Illuminate\Foundation\Console\DownCommand::class, - 'Environment' => \Illuminate\Foundation\Console\EnvironmentCommand::class, - 'EventCache' => \Illuminate\Foundation\Console\EventCacheCommand::class, - 'EventClear' => \Illuminate\Foundation\Console\EventClearCommand::class, - 'EventList' => \Winter\Storm\Foundation\Console\EventListCommand::class, - 'KeyGenerate' => \Winter\Storm\Foundation\Console\KeyGenerateCommand::class, - 'Optimize' => \Illuminate\Foundation\Console\OptimizeCommand::class, - 'PackageDiscover' => \Illuminate\Foundation\Console\PackageDiscoverCommand::class, - 'QueueFailed' => \Illuminate\Queue\Console\ListFailedCommand::class, - 'QueueFlush' => \Illuminate\Queue\Console\FlushFailedCommand::class, - 'QueueForget' => \Illuminate\Queue\Console\ForgetFailedCommand::class, - 'QueueListen' => \Illuminate\Queue\Console\ListenCommand::class, - 'QueueMonitor' => \Illuminate\Queue\Console\MonitorCommand::class, - 'QueuePruneBatches' => \Illuminate\Queue\Console\PruneBatchesCommand::class, - 'QueuePruneFailedJobs' => \Illuminate\Queue\Console\PruneFailedJobsCommand::class, - 'QueueRestart' => \Illuminate\Queue\Console\RestartCommand::class, - 'QueueRetry' => \Illuminate\Queue\Console\RetryCommand::class, - 'QueueRetryBatch' => \Illuminate\Queue\Console\RetryBatchCommand::class, - 'QueueWork' => \Illuminate\Queue\Console\WorkCommand::class, - 'RouteCache' => \Illuminate\Foundation\Console\RouteCacheCommand::class, - 'RouteClear' => \Illuminate\Foundation\Console\RouteClearCommand::class, - 'RouteList' => \Illuminate\Foundation\Console\RouteListCommand::class, - 'ScheduleFinish' => \Illuminate\Console\Scheduling\ScheduleFinishCommand::class, - 'ScheduleList' => \Illuminate\Console\Scheduling\ScheduleListCommand::class, - 'ScheduleRun' => \Illuminate\Console\Scheduling\ScheduleRunCommand::class, - 'ScheduleTest' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class, - 'ScheduleWork' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class, - 'Up' => \Illuminate\Foundation\Console\UpCommand::class, - 'ViewClear' => \Illuminate\Foundation\Console\ViewClearCommand::class, + 'CacheClear' => \Illuminate\Cache\Console\ClearCommand::class, + 'CacheForget' => \Illuminate\Cache\Console\ForgetCommand::class, + 'ClearCompiled' => \Winter\Storm\Foundation\Console\ClearCompiledCommand::class, + 'ConfigCache' => \Winter\Storm\Foundation\Console\ConfigCacheCommand::class, + 'ConfigClear' => \Winter\Storm\Foundation\Console\ConfigClearCommand::class, + 'Down' => \Illuminate\Foundation\Console\DownCommand::class, + 'Environment' => \Illuminate\Foundation\Console\EnvironmentCommand::class, + 'EventCache' => \Illuminate\Foundation\Console\EventCacheCommand::class, + 'EventClear' => \Illuminate\Foundation\Console\EventClearCommand::class, + 'EventList' => \Winter\Storm\Foundation\Console\EventListCommand::class, + 'KeyGenerate' => \Winter\Storm\Foundation\Console\KeyGenerateCommand::class, + 'Optimize' => \Illuminate\Foundation\Console\OptimizeCommand::class, + 'PackageDiscover' => \Illuminate\Foundation\Console\PackageDiscoverCommand::class, + 'QueueFailed' => \Illuminate\Queue\Console\ListFailedCommand::class, + 'QueueFlush' => \Illuminate\Queue\Console\FlushFailedCommand::class, + 'QueueForget' => \Illuminate\Queue\Console\ForgetFailedCommand::class, + 'QueueListen' => \Illuminate\Queue\Console\ListenCommand::class, + 'QueueMonitor' => \Illuminate\Queue\Console\MonitorCommand::class, + 'QueuePruneBatches' => \Illuminate\Queue\Console\PruneBatchesCommand::class, + 'QueuePruneFailedJobs' => \Illuminate\Queue\Console\PruneFailedJobsCommand::class, + 'QueueRestart' => \Illuminate\Queue\Console\RestartCommand::class, + 'QueueRetry' => \Illuminate\Queue\Console\RetryCommand::class, + 'QueueRetryBatch' => \Illuminate\Queue\Console\RetryBatchCommand::class, + 'QueueWork' => \Illuminate\Queue\Console\WorkCommand::class, + 'RouteCache' => \Illuminate\Foundation\Console\RouteCacheCommand::class, + 'RouteClear' => \Illuminate\Foundation\Console\RouteClearCommand::class, + 'RouteList' => \Illuminate\Foundation\Console\RouteListCommand::class, + 'ScheduleFinish' => \Illuminate\Console\Scheduling\ScheduleFinishCommand::class, + 'ScheduleList' => \Illuminate\Console\Scheduling\ScheduleListCommand::class, + 'ScheduleRun' => \Illuminate\Console\Scheduling\ScheduleRunCommand::class, + 'ScheduleTest' => \Illuminate\Console\Scheduling\ScheduleTestCommand::class, + 'ScheduleWork' => \Illuminate\Console\Scheduling\ScheduleWorkCommand::class, + 'Up' => \Illuminate\Foundation\Console\UpCommand::class, + 'ViewClear' => \Illuminate\Foundation\Console\ViewClearCommand::class, // Currently unsupported in Winter: // @TODO: Assess for inclusion diff --git a/tests/Scheduling/ScheduleListCommandTest.php b/tests/Scheduling/ScheduleListCommandTest.php index ee81b47c5..7e5e3b04c 100644 --- a/tests/Scheduling/ScheduleListCommandTest.php +++ b/tests/Scheduling/ScheduleListCommandTest.php @@ -133,4 +133,3 @@ public function fooFunction(): void { } } -