Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/Foundation/Providers/ArtisanServiceProvider.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php namespace Winter\Storm\Foundation\Providers;
<?php

namespace Winter\Storm\Foundation\Providers;

use Winter\Storm\Foundation\Console\KeyGenerateCommand;
use Winter\Storm\Foundation\Console\ClearCompiledCommand;
Expand Down Expand Up @@ -41,7 +43,10 @@ class ArtisanServiceProvider extends ArtisanServiceProviderBase
'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,

Expand All @@ -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:
Expand Down
135 changes: 135 additions & 0 deletions tests/Scheduling/ScheduleListCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

namespace Winter\Storm\Tests\Scheduling;

use Illuminate\Console\Command;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Console\Scheduling\ScheduleListCommand;
use Illuminate\Support\Carbon;
use Illuminate\Support\ProcessUtils;

use Winter\Storm\Tests\TestCase;

class ScheduleListCommandTest extends TestCase
{
public $schedule;

protected function setUp(): void
{
parent::setUp();

Carbon::setTestNow('2023-01-01');
ScheduleListCommand::resolveTerminalWidthUsing(fn () => 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();
}
Comment on lines +100 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reset Carbon test time to avoid cross-test leakage.

setUp() freezes time; without clearing it, other tests can inherit the mocked clock.

✅ Add cleanup in tearDown()
 protected function tearDown(): void
 {
+    Carbon::setTestNow(null);
     putenv('SHELL_VERBOSITY');

     parent::tearDown();
 }
🤖 Prompt for AI Agents
In `@tests/Scheduling/ScheduleListCommandTest.php` around lines 100 - 105, In
ScheduleListCommandTest::tearDown() add cleanup to reset Carbon's mocked time by
calling Carbon::setTestNow(null) (or Carbon::setTestNow()) before calling
parent::tearDown(), so the frozen time set in setUp() doesn't leak into other
tests; keep the existing putenv('SHELL_VERBOSITY') call and ensure you
import/use Carbon\Carbon or reference it fully when adding the reset.

}

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)
{
}
Comment on lines +119 to +123
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Suppress PHPMD unused-parameter warning for the stub.

PHPMD flags $param as unused; this can fail code analysis if tests are included.

🔧 Suppress the unused-parameter warning
 class FooParamJob
 {
+    /**
+     * `@SuppressWarnings`(PHPMD.UnusedFormalParameter)
+     */
     public function __construct($param)
     {
     }
 }
🧰 Tools
🪛 PHPMD (2.15.0)

121-121: Avoid unused parameters such as '$param'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In `@tests/Scheduling/ScheduleListCommandTest.php` around lines 119 - 123, PHPMD
flags the constructor parameter in class FooParamJob as unused; add a PHPMD
suppression docblock to the constructor to silence the warning. Locate the
FooParamJob class and its __construct($param) method and add a docblock like /**
`@SuppressWarnings`(PHPMD.UnusedFormalParameter) */ immediately above the
__construct definition so the unused-parameter warning is suppressed for this
test stub.

}

class FooCall
{
public function __invoke(): void
{
}

public function fooFunction(): void
{
}
}
102 changes: 102 additions & 0 deletions tests/Scheduling/ScheduleTestCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace Winter\Storm\Tests\Scheduling;

use Illuminate\Console\Application;
use Illuminate\Console\Command;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Console\Scheduling\ScheduleTestCommand;
use Illuminate\Support\Carbon;
use Winter\Storm\Tests\TestCase;

class ScheduleTestCommandTest extends TestCase
{
public $schedule;

protected function setUp(): void
{
parent::setUp();

Carbon::setTestNow(now()->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()
{
// ..
}
}