diff --git a/composer.json b/composer.json index 858e90593..ed652c512 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "Hypervel\\Bus\\": "src/bus/src/", "Hypervel\\Cache\\": "src/cache/src/", "Hypervel\\Config\\": "src/config/src/", + "Hypervel\\Console\\": "src/console/src/", "Hypervel\\Container\\": "src/container/src/", "Hypervel\\Cookie\\": "src/cookie/src/", "Hypervel\\Coroutine\\": "src/coroutine/src/", @@ -51,7 +52,6 @@ "Hypervel\\Prompts\\": "src/prompts/src/", "Hypervel\\Queue\\": "src/queue/src/", "Hypervel\\Router\\": "src/router/src/", - "Hypervel\\Scheduling\\": "src/scheduling/src/", "Hypervel\\Session\\": "src/session/src/", "Hypervel\\Support\\": "src/support/src/", "Hypervel\\Telescope\\": "src/telescope/src/" @@ -127,6 +127,7 @@ "hypervel/bus": "self.version", "hypervel/cache": "self.version", "hypervel/config": "self.version", + "hypervel/console": "self.version", "hypervel/container": "self.version", "hypervel/cookie": "self.version", "hypervel/core": "self.version", @@ -149,7 +150,6 @@ "hypervel/prompts": "self.version", "hypervel/queue": "self.version", "hypervel/router": "self.version", - "hypervel/scheduling": "self.version", "hypervel/session": "self.version", "hypervel/support": "self.version", "hypervel/telescope": "self.version" @@ -201,6 +201,7 @@ "Hypervel\\Cache\\ConfigProvider", "Hypervel\\Cookie\\ConfigProvider", "Hypervel\\Config\\ConfigProvider", + "Hypervel\\Console\\ConfigProvider", "Hypervel\\Devtool\\ConfigProvider", "Hypervel\\Dispatcher\\ConfigProvider", "Hypervel\\Encryption\\ConfigProvider", @@ -215,7 +216,6 @@ "Hypervel\\Notifications\\ConfigProvider", "Hypervel\\Queue\\ConfigProvider", "Hypervel\\Router\\ConfigProvider", - "Hypervel\\Scheduling\\ConfigProvider", "Hypervel\\Session\\ConfigProvider", "Hypervel\\Telescope\\ConfigProvider" ] diff --git a/src/scheduling/LICENSE.md b/src/console/LICENSE.md similarity index 100% rename from src/scheduling/LICENSE.md rename to src/console/LICENSE.md diff --git a/src/console/README.md b/src/console/README.md new file mode 100644 index 000000000..8cb6760cc --- /dev/null +++ b/src/console/README.md @@ -0,0 +1,2 @@ +Console for Hypervel +=== \ No newline at end of file diff --git a/src/console/composer.json b/src/console/composer.json new file mode 100644 index 000000000..6c31a094b --- /dev/null +++ b/src/console/composer.json @@ -0,0 +1,52 @@ +{ + "name": "hypervel/console", + "type": "library", + "description": "The console package for Hypervel.", + "license": "MIT", + "keywords": [ + "php", + "hyperf", + "console", + "swoole", + "hypervel" + ], + "authors": [ + { + "name": "Albert Chen", + "email": "albert@hypervel.org" + } + ], + "support": { + "issues": "https://github.com/hypervel/components/issues", + "source": "https://github.com/hypervel/components" + }, + "autoload": { + "psr-4": { + "Hypervel\\Console\\": "src/" + } + }, + "require": { + "php": "^8.2", + "hyperf/command": "~3.1.0", + "hyperf/context": "~3.1.0", + "hypervel/foundation": "^0.1", + "dragonmantank/cron-expression": "^3.3.2", + "symfony/console": "^5.4|^6.4|^7.0", + "friendsofhyperf/command-signals": "~3.1.0" + }, + "suggest": { + "hypervel/prompt": "Required to run schedule:test command.", + "friendsofhyperf/pretty-console": "Required to run schedule:run command. (~3.1.0)" + }, + "config": { + "sort-packages": true + }, + "extra": { + "hyperf": { + "config": "Hypervel\\Console\\ConfigProvider" + }, + "branch-alias": { + "dev-main": "0.1-dev" + } + } +} \ No newline at end of file diff --git a/src/foundation/src/Console/Application.php b/src/console/src/Application.php similarity index 98% rename from src/foundation/src/Console/Application.php rename to src/console/src/Application.php index 1fc765f4b..fdf30e150 100644 --- a/src/foundation/src/Console/Application.php +++ b/src/console/src/Application.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use Closure; use Hyperf\Command\Command; use Hyperf\Context\Context; +use Hypervel\Console\Contracts\Application as ApplicationContract; use Hypervel\Container\Contracts\Container as ContainerContract; -use Hypervel\Foundation\Console\Contracts\Application as ApplicationContract; use Hypervel\Support\ProcessUtils; use Override; use Psr\EventDispatcher\EventDispatcherInterface; diff --git a/src/foundation/src/Console/ApplicationFactory.php b/src/console/src/ApplicationFactory.php similarity index 92% rename from src/foundation/src/Console/ApplicationFactory.php rename to src/console/src/ApplicationFactory.php index 818d61895..8da5d1ba9 100644 --- a/src/foundation/src/Console/ApplicationFactory.php +++ b/src/console/src/ApplicationFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use Hypervel\Foundation\Console\Contracts\Kernel as KernelContract; use Psr\Container\ContainerInterface; diff --git a/src/console/src/ClosureCommand.php b/src/console/src/ClosureCommand.php new file mode 100644 index 000000000..6120379c0 --- /dev/null +++ b/src/console/src/ClosureCommand.php @@ -0,0 +1,91 @@ +signature = $signature; + + parent::__construct(); + } + + /** + * Execute the console command. + */ + public function handle(): int + { + $inputs = array_merge($this->input->getArguments(), $this->input->getOptions()); + + $parameters = []; + + foreach ((new ReflectionFunction($this->callback))->getParameters() as $parameter) { + if (isset($inputs[$parameter->getName()])) { + $parameters[$parameter->getName()] = $inputs[$parameter->getName()]; + } + } + + return (int) $this->container->call( + $this->callback->bindTo($this, $this), + $parameters + ); + } + + /** + * Set the description for the command. + */ + public function purpose(string $description): static + { + return $this->describe($description); + } + + /** + * Set the description for the command. + */ + public function describe(string $description): static + { + $this->setDescription($description); + + return $this; + } + + /** + * Create a new scheduled event for the command. + */ + public function schedule(array $parameters = []): Event + { + return Schedule::command($this->name, $parameters); + } + + /** + * Dynamically proxy calls to a new scheduled event. + * + * @throws BadMethodCallException + */ + public function __call(string $method, array $parameters) + { + return $this->forwardCallTo($this->schedule(), $method, $parameters); + } +} diff --git a/src/foundation/src/Console/Command.php b/src/console/src/Command.php similarity index 85% rename from src/foundation/src/Console/Command.php rename to src/console/src/Command.php index 622dbcbab..b9fdef83b 100644 --- a/src/foundation/src/Console/Command.php +++ b/src/console/src/Command.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use FriendsOfHyperf\CommandSignals\Traits\InteractsWithSignals; use FriendsOfHyperf\PrettyConsole\Traits\Prettyable; @@ -12,7 +12,6 @@ use Hyperf\Command\Event\BeforeHandle; use Hyperf\Command\Event\FailToHandle; use Hyperf\Coroutine\Coroutine; -use Hypervel\Context\ApplicationContext; use Hypervel\Support\Traits\HasLaravelStyleCommand; use Swoole\ExitException; use Symfony\Component\Console\Input\InputInterface; @@ -30,13 +29,14 @@ abstract class Command extends HyperfCommand protected function execute(InputInterface $input, OutputInterface $output): int { $this->disableDispatcher($input); + $this->replaceOutput(); $method = method_exists($this, 'handle') ? 'handle' : '__invoke'; $callback = function () use ($method): int { try { $this->eventDispatcher?->dispatch(new BeforeHandle($this)); - $statusCode = ApplicationContext::getContainer() - ->call([$this, $method]); + /* @phpstan-ignore-next-line */ + $statusCode = $this->app->call([$this, $method]); if (is_int($statusCode)) { $this->exitCode = $statusCode; } @@ -71,4 +71,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->exitCode >= 0 && $this->exitCode <= 255 ? $this->exitCode : self::INVALID; } + + protected function replaceOutput(): void + { + /* @phpstan-ignore-next-line */ + if ($this->app->bound(OutputInterface::class)) { + $this->output = $this->app->get(OutputInterface::class); + } + } } diff --git a/src/foundation/src/Console/CommandReplacer.php b/src/console/src/CommandReplacer.php similarity index 98% rename from src/foundation/src/Console/CommandReplacer.php rename to src/console/src/CommandReplacer.php index b66a6fb43..98ad57d08 100644 --- a/src/foundation/src/Console/CommandReplacer.php +++ b/src/console/src/CommandReplacer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use Symfony\Component\Console\Command\Command; diff --git a/src/scheduling/src/Console/ScheduleClearCacheCommand.php b/src/console/src/Commands/ScheduleClearCacheCommand.php similarity index 82% rename from src/scheduling/src/Console/ScheduleClearCacheCommand.php rename to src/console/src/Commands/ScheduleClearCacheCommand.php index cc5fde074..f6b813bf6 100644 --- a/src/scheduling/src/Console/ScheduleClearCacheCommand.php +++ b/src/console/src/Commands/ScheduleClearCacheCommand.php @@ -2,16 +2,13 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Console; +namespace Hypervel\Console\Commands; -use Hyperf\Command\Command; -use Hypervel\Scheduling\Schedule; -use Hypervel\Support\Traits\HasLaravelStyleCommand; +use Hypervel\Console\Command; +use Hypervel\Console\Scheduling\Schedule; class ScheduleClearCacheCommand extends Command { - use HasLaravelStyleCommand; - /** * The console command name. */ diff --git a/src/scheduling/src/Console/ScheduleListCommand.php b/src/console/src/Commands/ScheduleListCommand.php similarity index 96% rename from src/scheduling/src/Console/ScheduleListCommand.php rename to src/console/src/Commands/ScheduleListCommand.php index e6ab983d7..6c13f3a0d 100644 --- a/src/scheduling/src/Console/ScheduleListCommand.php +++ b/src/console/src/Commands/ScheduleListCommand.php @@ -2,27 +2,24 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Console; +namespace Hypervel\Console\Commands; use Closure; use Cron\CronExpression; use DateTimeZone; use Exception; use Hyperf\Collection\Collection; -use Hyperf\Command\Command; -use Hypervel\Scheduling\CallbackEvent; -use Hypervel\Scheduling\Event; -use Hypervel\Scheduling\Schedule; +use Hypervel\Console\Command; +use Hypervel\Console\Scheduling\CallbackEvent; +use Hypervel\Console\Scheduling\Event; +use Hypervel\Console\Scheduling\Schedule; use Hypervel\Support\Carbon; -use Hypervel\Support\Traits\HasLaravelStyleCommand; use ReflectionClass; use ReflectionFunction; use Symfony\Component\Console\Terminal; class ScheduleListCommand extends Command { - use HasLaravelStyleCommand; - /** * The console command signature. */ diff --git a/src/scheduling/src/Console/ScheduleRunCommand.php b/src/console/src/Commands/ScheduleRunCommand.php similarity index 91% rename from src/scheduling/src/Console/ScheduleRunCommand.php rename to src/console/src/Commands/ScheduleRunCommand.php index 78966601c..4d88e8978 100644 --- a/src/scheduling/src/Console/ScheduleRunCommand.php +++ b/src/console/src/Commands/ScheduleRunCommand.php @@ -2,23 +2,21 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Console; +namespace Hypervel\Console\Commands; use Hyperf\Collection\Collection; -use Hyperf\Command\Command; use Hyperf\Coroutine\Concurrent; use Hyperf\Coroutine\Waiter; use Hypervel\Cache\Contracts\Factory as CacheFactory; -use Hypervel\Container\Contracts\Container; -use Hypervel\Context\ApplicationContext; +use Hypervel\Console\Command; +use Hypervel\Console\Events\ScheduledTaskFailed; +use Hypervel\Console\Events\ScheduledTaskFinished; +use Hypervel\Console\Events\ScheduledTaskSkipped; +use Hypervel\Console\Events\ScheduledTaskStarting; +use Hypervel\Console\Scheduling\CallbackEvent; +use Hypervel\Console\Scheduling\Event; +use Hypervel\Console\Scheduling\Schedule; use Hypervel\Foundation\Exceptions\Contracts\ExceptionHandler; -use Hypervel\Scheduling\CallbackEvent; -use Hypervel\Scheduling\Event; -use Hypervel\Scheduling\Events\ScheduledTaskFailed; -use Hypervel\Scheduling\Events\ScheduledTaskFinished; -use Hypervel\Scheduling\Events\ScheduledTaskSkipped; -use Hypervel\Scheduling\Events\ScheduledTaskStarting; -use Hypervel\Scheduling\Schedule; use Hypervel\Support\Carbon; use Hypervel\Support\Facades\Date; use Hypervel\Support\Sleep; @@ -27,8 +25,6 @@ class ScheduleRunCommand extends Command { - protected Container $app; - /** * The console command signature. */ @@ -72,8 +68,6 @@ public function __construct( protected ExceptionHandler $handler, ) { parent::__construct(); - - $this->app = ApplicationContext::getContainer(); } /** diff --git a/src/scheduling/src/Console/ScheduleStopCommand.php b/src/console/src/Commands/ScheduleStopCommand.php similarity index 87% rename from src/scheduling/src/Console/ScheduleStopCommand.php rename to src/console/src/Commands/ScheduleStopCommand.php index 1354d1108..67bf2a406 100644 --- a/src/scheduling/src/Console/ScheduleStopCommand.php +++ b/src/console/src/Commands/ScheduleStopCommand.php @@ -2,17 +2,14 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Console; +namespace Hypervel\Console\Commands; -use Hyperf\Command\Command; use Hypervel\Cache\Contracts\Factory as CacheFactory; +use Hypervel\Console\Command; use Hypervel\Support\Facades\Date; -use Hypervel\Support\Traits\HasLaravelStyleCommand; class ScheduleStopCommand extends Command { - use HasLaravelStyleCommand; - /** * The console signature name. */ diff --git a/src/scheduling/src/Console/ScheduleTestCommand.php b/src/console/src/Commands/ScheduleTestCommand.php similarity index 90% rename from src/scheduling/src/Console/ScheduleTestCommand.php rename to src/console/src/Commands/ScheduleTestCommand.php index be97e422b..5f9cb40bb 100644 --- a/src/scheduling/src/Console/ScheduleTestCommand.php +++ b/src/console/src/Commands/ScheduleTestCommand.php @@ -2,21 +2,16 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Console; +namespace Hypervel\Console\Commands; -use FriendsOfHyperf\PrettyConsole\Traits\Prettyable; -use Hyperf\Command\Command; -use Hypervel\Scheduling\CallbackEvent; -use Hypervel\Scheduling\Schedule; -use Hypervel\Support\Traits\HasLaravelStyleCommand; +use Hypervel\Console\Command; +use Hypervel\Console\Scheduling\CallbackEvent; +use Hypervel\Console\Scheduling\Schedule; use function Hypervel\Prompts\select; class ScheduleTestCommand extends Command { - use HasLaravelStyleCommand; - use Prettyable; - /** * The console command signature. */ diff --git a/src/scheduling/src/ConfigProvider.php b/src/console/src/ConfigProvider.php similarity index 57% rename from src/scheduling/src/ConfigProvider.php rename to src/console/src/ConfigProvider.php index 8298297e4..454763d54 100644 --- a/src/scheduling/src/ConfigProvider.php +++ b/src/console/src/ConfigProvider.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console; -use Hypervel\Scheduling\Console\ScheduleClearCacheCommand; -use Hypervel\Scheduling\Console\ScheduleListCommand; -use Hypervel\Scheduling\Console\ScheduleRunCommand; -use Hypervel\Scheduling\Console\ScheduleStopCommand; -use Hypervel\Scheduling\Console\ScheduleTestCommand; +use Hypervel\Console\Commands\ScheduleClearCacheCommand; +use Hypervel\Console\Commands\ScheduleListCommand; +use Hypervel\Console\Commands\ScheduleRunCommand; +use Hypervel\Console\Commands\ScheduleStopCommand; +use Hypervel\Console\Commands\ScheduleTestCommand; class ConfigProvider { diff --git a/src/foundation/src/Console/ConfirmableTrait.php b/src/console/src/ConfirmableTrait.php similarity index 96% rename from src/foundation/src/Console/ConfirmableTrait.php rename to src/console/src/ConfirmableTrait.php index eb09b9b3c..f3e7b16c9 100644 --- a/src/foundation/src/Console/ConfirmableTrait.php +++ b/src/console/src/ConfirmableTrait.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use Closure; use Hypervel\Context\ApplicationContext; diff --git a/src/foundation/src/Console/ContainerCommandLoader.php b/src/console/src/ContainerCommandLoader.php similarity index 97% rename from src/foundation/src/Console/ContainerCommandLoader.php rename to src/console/src/ContainerCommandLoader.php index fafc667c8..bd091f07d 100644 --- a/src/foundation/src/Console/ContainerCommandLoader.php +++ b/src/console/src/ContainerCommandLoader.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Command\Command; diff --git a/src/foundation/src/Console/Contracts/Application.php b/src/console/src/Contracts/Application.php similarity index 97% rename from src/foundation/src/Console/Contracts/Application.php rename to src/console/src/Contracts/Application.php index 05339d375..655569133 100644 --- a/src/foundation/src/Console/Contracts/Application.php +++ b/src/console/src/Contracts/Application.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console\Contracts; +namespace Hypervel\Console\Contracts; use Closure; use Hyperf\Command\Command; diff --git a/src/scheduling/src/Contracts/CacheAware.php b/src/console/src/Contracts/CacheAware.php similarity index 81% rename from src/scheduling/src/Contracts/CacheAware.php rename to src/console/src/Contracts/CacheAware.php index 956b975a4..abc06a1c6 100644 --- a/src/scheduling/src/Contracts/CacheAware.php +++ b/src/console/src/Contracts/CacheAware.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Contracts; +namespace Hypervel\Console\Contracts; interface CacheAware { diff --git a/src/scheduling/src/Contracts/EventMutex.php b/src/console/src/Contracts/EventMutex.php similarity index 84% rename from src/scheduling/src/Contracts/EventMutex.php rename to src/console/src/Contracts/EventMutex.php index 00b793d97..dc2e1315c 100644 --- a/src/scheduling/src/Contracts/EventMutex.php +++ b/src/console/src/Contracts/EventMutex.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Contracts; +namespace Hypervel\Console\Contracts; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\Event; interface EventMutex { diff --git a/src/scheduling/src/Contracts/SchedulingMutex.php b/src/console/src/Contracts/SchedulingMutex.php similarity index 83% rename from src/scheduling/src/Contracts/SchedulingMutex.php rename to src/console/src/Contracts/SchedulingMutex.php index b068eea66..f37f08673 100644 --- a/src/scheduling/src/Contracts/SchedulingMutex.php +++ b/src/console/src/Contracts/SchedulingMutex.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Contracts; +namespace Hypervel\Console\Contracts; use DateTimeInterface; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\Event; interface SchedulingMutex { diff --git a/src/foundation/src/Console/ErrorRenderer.php b/src/console/src/ErrorRenderer.php similarity index 98% rename from src/foundation/src/Console/ErrorRenderer.php rename to src/console/src/ErrorRenderer.php index 1814a56f2..9f1f6a945 100644 --- a/src/foundation/src/Console/ErrorRenderer.php +++ b/src/console/src/ErrorRenderer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use NunoMaduro\Collision\Adapters\Laravel\Inspector; use NunoMaduro\Collision\Provider; diff --git a/src/scheduling/src/Events/ScheduledTaskFailed.php b/src/console/src/Events/ScheduledTaskFailed.php similarity index 76% rename from src/scheduling/src/Events/ScheduledTaskFailed.php rename to src/console/src/Events/ScheduledTaskFailed.php index 71763cb2c..62d1b390b 100644 --- a/src/scheduling/src/Events/ScheduledTaskFailed.php +++ b/src/console/src/Events/ScheduledTaskFailed.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Events; +namespace Hypervel\Console\Events; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\Event; use Throwable; class ScheduledTaskFailed diff --git a/src/scheduling/src/Events/ScheduledTaskFinished.php b/src/console/src/Events/ScheduledTaskFinished.php similarity index 75% rename from src/scheduling/src/Events/ScheduledTaskFinished.php rename to src/console/src/Events/ScheduledTaskFinished.php index 4bb8c0ab1..3e19f4f77 100644 --- a/src/scheduling/src/Events/ScheduledTaskFinished.php +++ b/src/console/src/Events/ScheduledTaskFinished.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Events; +namespace Hypervel\Console\Events; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\Event; class ScheduledTaskFinished { diff --git a/src/scheduling/src/Events/ScheduledTaskSkipped.php b/src/console/src/Events/ScheduledTaskSkipped.php similarity index 72% rename from src/scheduling/src/Events/ScheduledTaskSkipped.php rename to src/console/src/Events/ScheduledTaskSkipped.php index 0386ed800..4e37fb050 100644 --- a/src/scheduling/src/Events/ScheduledTaskSkipped.php +++ b/src/console/src/Events/ScheduledTaskSkipped.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Events; +namespace Hypervel\Console\Events; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\Event; class ScheduledTaskSkipped { diff --git a/src/scheduling/src/Events/ScheduledTaskStarting.php b/src/console/src/Events/ScheduledTaskStarting.php similarity index 72% rename from src/scheduling/src/Events/ScheduledTaskStarting.php rename to src/console/src/Events/ScheduledTaskStarting.php index a42aa1124..04a079403 100644 --- a/src/scheduling/src/Events/ScheduledTaskStarting.php +++ b/src/console/src/Events/ScheduledTaskStarting.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Hypervel\Scheduling\Events; +namespace Hypervel\Console\Events; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\Event; class ScheduledTaskStarting { diff --git a/src/foundation/src/Console/HasPendingCommand.php b/src/console/src/HasPendingCommand.php similarity index 98% rename from src/foundation/src/Console/HasPendingCommand.php rename to src/console/src/HasPendingCommand.php index 47f358345..66fc6c173 100644 --- a/src/foundation/src/Console/HasPendingCommand.php +++ b/src/console/src/HasPendingCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Foundation\Console; +namespace Hypervel\Console; use Hyperf\Command\Annotation\Command as AnnotationCommand; use Hyperf\Command\Parser; diff --git a/src/scheduling/src/CacheEventMutex.php b/src/console/src/Scheduling/CacheEventMutex.php similarity index 95% rename from src/scheduling/src/CacheEventMutex.php rename to src/console/src/Scheduling/CacheEventMutex.php index 81d834966..8dda537ee 100644 --- a/src/scheduling/src/CacheEventMutex.php +++ b/src/console/src/Scheduling/CacheEventMutex.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; use Hypervel\Cache\Contracts\Factory as CacheFactory; use Hypervel\Cache\Contracts\LockProvider; use Hypervel\Cache\Contracts\Store; -use Hypervel\Scheduling\Contracts\CacheAware; -use Hypervel\Scheduling\Contracts\EventMutex; +use Hypervel\Console\Contracts\CacheAware; +use Hypervel\Console\Contracts\EventMutex; class CacheEventMutex implements EventMutex, CacheAware { diff --git a/src/scheduling/src/CacheSchedulingMutex.php b/src/console/src/Scheduling/CacheSchedulingMutex.php similarity index 90% rename from src/scheduling/src/CacheSchedulingMutex.php rename to src/console/src/Scheduling/CacheSchedulingMutex.php index 7fd828e91..d2a852559 100644 --- a/src/scheduling/src/CacheSchedulingMutex.php +++ b/src/console/src/Scheduling/CacheSchedulingMutex.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; use DateTimeInterface; use Hypervel\Cache\Contracts\Factory as CacheFactory; -use Hypervel\Scheduling\Contracts\CacheAware; -use Hypervel\Scheduling\Contracts\SchedulingMutex; +use Hypervel\Console\Contracts\CacheAware; +use Hypervel\Console\Contracts\SchedulingMutex; class CacheSchedulingMutex implements SchedulingMutex, CacheAware { diff --git a/src/scheduling/src/CallbackEvent.php b/src/console/src/Scheduling/CallbackEvent.php similarity index 98% rename from src/scheduling/src/CallbackEvent.php rename to src/console/src/Scheduling/CallbackEvent.php index 9479df3d7..146c9f281 100644 --- a/src/scheduling/src/CallbackEvent.php +++ b/src/console/src/Scheduling/CallbackEvent.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; use DateTimeZone; +use Hypervel\Console\Contracts\EventMutex; use Hypervel\Container\Contracts\Container; -use Hypervel\Scheduling\Contracts\EventMutex; use Hypervel\Support\Reflector; use InvalidArgumentException; use LogicException; diff --git a/src/scheduling/src/Event.php b/src/console/src/Scheduling/Event.php similarity index 99% rename from src/scheduling/src/Event.php rename to src/console/src/Scheduling/Event.php index 2a2b9dc03..c701c741d 100644 --- a/src/scheduling/src/Event.php +++ b/src/console/src/Scheduling/Event.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; use Carbon\Carbon; use Closure; @@ -19,12 +19,12 @@ use Hyperf\Stringable\Stringable; use Hyperf\Support\Filesystem\Filesystem; use Hyperf\Tappable\Tappable; +use Hypervel\Console\Contracts\EventMutex; use Hypervel\Container\Contracts\Container; use Hypervel\Foundation\Console\Contracts\Kernel as KernelContract; use Hypervel\Foundation\Contracts\Application as ApplicationContract; use Hypervel\Foundation\Exceptions\Contracts\ExceptionHandler; use Hypervel\Mail\Contracts\Mailer; -use Hypervel\Scheduling\Contracts\EventMutex; use Hypervel\Support\Facades\Date; use Hypervel\Support\Traits\ReflectsClosures; use LogicException; diff --git a/src/scheduling/src/ManagesAttributes.php b/src/console/src/Scheduling/ManagesAttributes.php similarity index 99% rename from src/scheduling/src/ManagesAttributes.php rename to src/console/src/Scheduling/ManagesAttributes.php index 44eefc094..22000bd10 100644 --- a/src/scheduling/src/ManagesAttributes.php +++ b/src/console/src/Scheduling/ManagesAttributes.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; use Closure; use DateTimeZone; diff --git a/src/scheduling/src/ManagesFrequencies.php b/src/console/src/Scheduling/ManagesFrequencies.php similarity index 99% rename from src/scheduling/src/ManagesFrequencies.php rename to src/console/src/Scheduling/ManagesFrequencies.php index ccca4439d..8c3c3c34f 100644 --- a/src/scheduling/src/ManagesFrequencies.php +++ b/src/console/src/Scheduling/ManagesFrequencies.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; use Closure; use DateTimeZone; diff --git a/src/scheduling/src/PendingEventAttributes.php b/src/console/src/Scheduling/PendingEventAttributes.php similarity index 95% rename from src/scheduling/src/PendingEventAttributes.php rename to src/console/src/Scheduling/PendingEventAttributes.php index 5e1b24f8b..0a16ce262 100644 --- a/src/scheduling/src/PendingEventAttributes.php +++ b/src/console/src/Scheduling/PendingEventAttributes.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; /** - * @mixin \Hypervel\Scheduling\Schedule + * @mixin \Hypervel\Console\Scheduling\Schedule */ class PendingEventAttributes { diff --git a/src/scheduling/src/Schedule.php b/src/console/src/Scheduling/Schedule.php similarity index 98% rename from src/scheduling/src/Schedule.php rename to src/console/src/Scheduling/Schedule.php index fff7ed675..c4b3d7a1d 100644 --- a/src/scheduling/src/Schedule.php +++ b/src/console/src/Scheduling/Schedule.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Hypervel\Scheduling; +namespace Hypervel\Console\Scheduling; use BadMethodCallException; use Closure; @@ -13,6 +13,9 @@ use Hypervel\Bus\Contracts\Dispatcher; use Hypervel\Bus\UniqueLock; use Hypervel\Cache\Contracts\Factory as CacheFactory; +use Hypervel\Console\Contracts\CacheAware; +use Hypervel\Console\Contracts\EventMutex; +use Hypervel\Console\Contracts\SchedulingMutex; use Hypervel\Container\BindingResolutionException; use Hypervel\Container\Container; use Hypervel\Context\ApplicationContext; @@ -20,9 +23,6 @@ use Hypervel\Queue\CallQueuedClosure; use Hypervel\Queue\Contracts\ShouldBeUnique; use Hypervel\Queue\Contracts\ShouldQueue; -use Hypervel\Scheduling\Contracts\CacheAware; -use Hypervel\Scheduling\Contracts\EventMutex; -use Hypervel\Scheduling\Contracts\SchedulingMutex; use Hypervel\Support\ProcessUtils; use RuntimeException; diff --git a/src/devtool/src/Generator/stubs/console.stub b/src/devtool/src/Generator/stubs/console.stub index 9ed1fe269..2fd07ba53 100644 --- a/src/devtool/src/Generator/stubs/console.stub +++ b/src/devtool/src/Generator/stubs/console.stub @@ -4,7 +4,7 @@ declare(strict_types=1); namespace %NAMESPACE%; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; class %CLASS% extends Command { diff --git a/src/foundation/composer.json b/src/foundation/composer.json index c5c3599e8..7ef83ece8 100644 --- a/src/foundation/composer.json +++ b/src/foundation/composer.json @@ -1,6 +1,6 @@ { "name": "hypervel/foundation", - "description": "The Support package for Hypervel.", + "description": "The Foundation package for Hypervel.", "license": "MIT", "keywords": [ "php", diff --git a/src/foundation/src/ConfigProvider.php b/src/foundation/src/ConfigProvider.php index 45a3b1427..d3bf98086 100644 --- a/src/foundation/src/ConfigProvider.php +++ b/src/foundation/src/ConfigProvider.php @@ -7,7 +7,7 @@ use Hyperf\Contract\ApplicationInterface; use Hyperf\Coordinator\Listener\ResumeExitCoordinatorListener; use Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler; -use Hypervel\Foundation\Console\ApplicationFactory; +use Hypervel\Console\ApplicationFactory; use Hypervel\Foundation\Console\Commands\ServerReloadCommand; use Hypervel\Foundation\Console\Commands\VendorPublishCommand; use Hypervel\Foundation\Exceptions\Contracts\ExceptionHandler as ExceptionHandlerContract; diff --git a/src/foundation/src/Console/Commands/ServerReloadCommand.php b/src/foundation/src/Console/Commands/ServerReloadCommand.php index d6c05427c..fa0b54b31 100644 --- a/src/foundation/src/Console/Commands/ServerReloadCommand.php +++ b/src/foundation/src/Console/Commands/ServerReloadCommand.php @@ -7,7 +7,7 @@ use Hyperf\Contract\ConfigInterface; use Hyperf\Support\Filesystem\FileNotFoundException; use Hyperf\Support\Filesystem\Filesystem; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; use Psr\Container\ContainerInterface; use Throwable; diff --git a/src/foundation/src/Console/Commands/VendorPublishCommand.php b/src/foundation/src/Console/Commands/VendorPublishCommand.php index 4281fad16..12814f5d8 100644 --- a/src/foundation/src/Console/Commands/VendorPublishCommand.php +++ b/src/foundation/src/Console/Commands/VendorPublishCommand.php @@ -10,7 +10,7 @@ use Hyperf\Stringable\Str; use Hyperf\Support\Composer; use Hyperf\Support\Filesystem\Filesystem; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; use Hypervel\Support\ServiceProvider; class VendorPublishCommand extends Command diff --git a/src/foundation/src/Console/Contracts/Kernel.php b/src/foundation/src/Console/Contracts/Kernel.php index 21336aaab..15c70c1c6 100644 --- a/src/foundation/src/Console/Contracts/Kernel.php +++ b/src/foundation/src/Console/Contracts/Kernel.php @@ -5,9 +5,9 @@ namespace Hypervel\Foundation\Console\Contracts; use Closure; -use Hyperf\Command\ClosureCommand; -use Hypervel\Foundation\Console\Contracts\Application as ApplicationContract; -use Hypervel\Scheduling\Schedule; +use Hypervel\Console\ClosureCommand; +use Hypervel\Console\Contracts\Application as ApplicationContract; +use Hypervel\Console\Scheduling\Schedule; use Symfony\Component\Console\Output\OutputInterface; interface Kernel diff --git a/src/foundation/src/Console/Kernel.php b/src/foundation/src/Console/Kernel.php index 718afc71a..80edd8742 100644 --- a/src/foundation/src/Console/Kernel.php +++ b/src/foundation/src/Console/Kernel.php @@ -8,18 +8,19 @@ use Exception; use Hyperf\Collection\Arr; use Hyperf\Command\Annotation\Command as AnnotationCommand; -use Hyperf\Command\ClosureCommand; use Hyperf\Contract\ApplicationInterface; use Hyperf\Contract\ConfigInterface; use Hyperf\Di\Annotation\AnnotationCollector; use Hyperf\Di\ReflectionManager; use Hyperf\Framework\Event\BootApplication; use Hyperf\Stringable\Str; -use Hypervel\Foundation\Console\Application as ConsoleApplication; -use Hypervel\Foundation\Console\Contracts\Application as ApplicationContract; +use Hypervel\Console\Application as ConsoleApplication; +use Hypervel\Console\ClosureCommand; +use Hypervel\Console\Contracts\Application as ApplicationContract; +use Hypervel\Console\HasPendingCommand; +use Hypervel\Console\Scheduling\Schedule; use Hypervel\Foundation\Console\Contracts\Kernel as KernelContract; use Hypervel\Foundation\Contracts\Application as ContainerContract; -use Hypervel\Scheduling\Schedule; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Input\InputInterface; @@ -305,7 +306,16 @@ public function command(string $signature, Closure $callback): ClosureCommand { $command = new ClosureCommand($this->app, $signature, $callback); - $this->closureCommands[] = $command; + // If the commands have already been loaded, we will register it + // with the console right away. If not, we will defer the call + // to this registration by storing the commands closures. + if ($this->commandsLoaded) { + $closureId = spl_object_hash($command); + $this->app->set($commandId = "commands.{$closureId}", $command); + $this->registerCommand($commandId); + } else { + $this->closureCommands[] = $command; + } return $command; } @@ -340,8 +350,6 @@ public function getLoadedPaths(): array /** * Set the Artisan commands provided by the application. - * - * @return $this */ public function addCommands(array $commands): static { @@ -356,8 +364,6 @@ public function addCommands(array $commands): static /** * Set the paths that should have their Artisan commands automatically discovered. - * - * @return $this */ public function addCommandPaths(array $paths): static { @@ -368,8 +374,6 @@ public function addCommandPaths(array $paths): static /** * Set the paths that should have their Artisan "routes" automatically discovered. - * - * @return $this */ public function addCommandRoutePaths(array $paths): static { diff --git a/src/foundation/src/Testing/Concerns/InteractsWithConsole.php b/src/foundation/src/Testing/Concerns/InteractsWithConsole.php index 3f00b1401..279e0d183 100644 --- a/src/foundation/src/Testing/Concerns/InteractsWithConsole.php +++ b/src/foundation/src/Testing/Concerns/InteractsWithConsole.php @@ -5,6 +5,7 @@ namespace Hypervel\Foundation\Testing\Concerns; use Hypervel\Foundation\Console\Contracts\Kernel as KernelContract; +use Hypervel\Foundation\Testing\PendingCommand; trait InteractsWithConsole { @@ -13,27 +14,70 @@ trait InteractsWithConsole */ public bool $mockConsoleOutput = true; + /** + * Indicates if the command is expected to output anything. + */ + public ?bool $expectsOutput = null; + + /** + * All of the expected output lines. + */ + public array $expectedOutput = []; + + /** + * All of the expected text to be present in the output. + */ + public array $expectedOutputSubstrings = []; + + /** + * All of the output lines that aren't expected to be displayed. + */ + public array $unexpectedOutput = []; + + /** + * All of the text that is not expected to be present in the output. + */ + public array $unexpectedOutputSubstrings = []; + + /** + * All of the expected output tables. + */ + public array $expectedTables = []; + + /** + * All of the expected questions. + */ + public array $expectedQuestions = []; + + /** + * All of the expected choice questions. + */ + public array $expectedChoices = []; + /** * Alias of `command` method. */ - public function artisan(string $command, array $parameters = []): int + public function artisan(string $command, array $parameters = []): int|PendingCommand { return $this->command($command, $parameters); } /** - * Call hyperf command and return code. + * Call Hypervel command and return code. */ - public function command(string $command, array $parameters = []): int + public function command(string $command, array $parameters = []): int|PendingCommand { - return $this->app->get(KernelContract::class) - ->call($command, $parameters); + if (! $this->mockConsoleOutput) { + return $this->app + ->get(KernelContract::class) + ->call($command, $parameters); + } + + return new PendingCommand($this, $this->app, $command, $parameters); } /** * Disable mocking the console output. - * - * @return $this */ protected function withoutMockingConsoleOutput(): static { diff --git a/src/foundation/src/Testing/PendingCommand.php b/src/foundation/src/Testing/PendingCommand.php new file mode 100644 index 000000000..b063d5347 --- /dev/null +++ b/src/foundation/src/Testing/PendingCommand.php @@ -0,0 +1,466 @@ +test->expectedQuestions[] = [$question, $answer]; + + return $this; + } + + /** + * Specify an expected confirmation question that will be asked when the command runs. + */ + public function expectsConfirmation(string $question, string $answer = 'no'): static + { + return $this->expectsQuestion($question, strtolower($answer) === 'yes'); + } + + /** + * Specify an expected choice question with expected answers that will be asked/shown when the command runs. + */ + public function expectsChoice(string $question, array|string $answer, array $answers, bool $strict = false): static + { + $this->test->expectedChoices[$question] = [ + 'expected' => $answers, + 'strict' => $strict, + ]; + + return $this->expectsQuestion($question, $answer); + } + + /** + * Specify an expected search question with an expected search string, followed by an expected choice question with expected answers. + */ + public function expectsSearch(string $question, array|string $answer, string $search, array $answers): static + { + return $this + ->expectsQuestion($question, $search) + ->expectsChoice($question, $answer, $answers); + } + + /** + * Specify output that should be printed when the command runs. + */ + public function expectsOutput(?string $output = null): static + { + if ($output === null) { + $this->test->expectsOutput = true; + + return $this; + } + + $this->test->expectedOutput[] = $output; + + return $this; + } + + /** + * Specify output that should never be printed when the command runs. + */ + public function doesntExpectOutput(?string $output = null): static + { + if ($output === null) { + $this->test->expectsOutput = false; + + return $this; + } + + $this->test->unexpectedOutput[$output] = false; + + return $this; + } + + /** + * Specify that the given string should be contained in the command output. + */ + public function expectsOutputToContain(string $string): static + { + $this->test->expectedOutputSubstrings[] = $string; + + return $this; + } + + /** + * Specify that the given string shouldn't be contained in the command output. + */ + public function doesntExpectOutputToContain(string $string): static + { + $this->test->unexpectedOutputSubstrings[$string] = false; + + return $this; + } + + /** + * Specify a table that should be printed when the command runs. + */ + public function expectsTable(array $headers, array|Arrayable $rows, string $tableStyle = 'default', array $columnStyles = []): static + { + $table = (new Table($output = new BufferedOutput())) + ->setHeaders((array) $headers) + ->setRows($rows instanceof Arrayable ? $rows->toArray() : $rows) + ->setStyle($tableStyle); + + foreach ($columnStyles as $columnIndex => $columnStyle) { + $table->setColumnStyle($columnIndex, $columnStyle); + } + + $table->render(); + + $lines = array_filter( + explode(PHP_EOL, $output->fetch()) + ); + + foreach ($lines as $line) { + $this->expectsOutput($line); + } + + return $this; + } + + /** + * Assert that the command has the given exit code. + */ + public function assertExitCode(int $exitCode): static + { + $this->expectedExitCode = $exitCode; + + return $this; + } + + /** + * Assert that the command does not have the given exit code. + */ + public function assertNotExitCode(int $exitCode): static + { + $this->unexpectedExitCode = $exitCode; + + return $this; + } + + /** + * Assert that the command has the success exit code. + */ + public function assertSuccessful(): static + { + return $this->assertExitCode(Command::SUCCESS); + } + + /** + * Assert that the command has the success exit code. + */ + public function assertOk(): static + { + return $this->assertSuccessful(); + } + + /** + * Assert that the command does not have the success exit code. + */ + public function assertFailed(): static + { + return $this->assertNotExitCode(Command::SUCCESS); + } + + /** + * Execute the command. + */ + public function execute(): int + { + return $this->run(); + } + + /** + * Execute the command. + * + * @throws NoMatchingExpectationException + */ + public function run(): int + { + $this->hasExecuted = true; + + $mock = $this->mockConsoleOutput(); + + $exception = null; + $this->app->get(EventDispatcherInterface::class) + ->listen(FailToHandle::class, function ($event) use (&$exception) { + $exception = $event->getThrowable(); + }); + + try { + $exitCode = $this->app + ->get(KernelContract::class) + ->call($this->command, $this->parameters, $mock); + } catch (NoMatchingExpectationException $e) { + if ($e->getMethodName() === 'askQuestion') { + $this->test->fail('Unexpected question "' . $e->getActualArguments()[0]->getQuestion() . '" was asked.'); + } + + throw $e; + } + + if ($exception) { + throw $exception; + } + + if ($this->expectedExitCode !== null) { + $this->test->assertEquals( + $this->expectedExitCode, + $exitCode, + "Expected status code {$this->expectedExitCode} but received {$exitCode}." + ); + } elseif (! is_null($this->unexpectedExitCode)) { + $this->test->assertNotEquals( + $this->unexpectedExitCode, + $exitCode, + "Unexpected status code {$this->unexpectedExitCode} was received." + ); + } + + $this->verifyExpectations(); + $this->flushExpectations(); + + $this->app->unbind(OutputInterface::class); + + return $exitCode; + } + + /** + * Determine if expected questions / choices / outputs are fulfilled. + */ + protected function verifyExpectations(): void + { + if (count($this->test->expectedQuestions)) { + $this->test->fail('Question "' . Arr::first($this->test->expectedQuestions)[0] . '" was not asked.'); + } + + if (count($this->test->expectedChoices) > 0) { + foreach ($this->test->expectedChoices as $question => $answers) { + $assertion = $answers['strict'] ? 'assertEquals' : 'assertEqualsCanonicalizing'; + + $this->test->{$assertion}( + $answers['expected'], + $answers['actual'], + 'Question "' . $question . '" has different options.' + ); + } + } + + if (count($this->test->expectedOutput)) { + $this->test->fail('Output "' . Arr::first($this->test->expectedOutput) . '" was not printed.'); + } + + if (count($this->test->expectedOutputSubstrings)) { + $this->test->fail('Output does not contain "' . Arr::first($this->test->expectedOutputSubstrings) . '".'); + } + + if ($output = array_search(true, $this->test->unexpectedOutput)) { + $this->test->fail('Output "' . $output . '" was printed.'); + } + + if ($output = array_search(true, $this->test->unexpectedOutputSubstrings)) { + $this->test->fail('Output "' . $output . '" was printed.'); + } + } + + /** + * Mock the application's console output. + */ + protected function mockConsoleOutput() + { + // /** @var \Mockery\MockeryInterface&\Mockery\ExpectationInterface $mock */ + $mock = Mockery::mock(SymfonyStyle::class . '[askQuestion]', [ + new ArrayInput($this->parameters), + $this->createABufferedOutputMock(), + ]); + + foreach ($this->test->expectedQuestions as $i => $question) { + /** @var \Mockery\Expectation $expectation */ + $expectation = $mock->shouldReceive('askQuestion'); + $expectation->once() + ->ordered() + ->with(Mockery::on(function ($argument) use ($question) { + if (isset($this->test->expectedChoices[$question[0]])) { + $this->test->expectedChoices[$question[0]]['actual'] = $argument instanceof ChoiceQuestion && ! array_is_list($this->test->expectedChoices[$question[0]]['expected']) + ? $argument->getChoices() + : $argument->getAutocompleterValues(); + } + + return $argument->getQuestion() == $question[0]; + })) + ->andReturnUsing(function () use ($question, $i) { + unset($this->test->expectedQuestions[$i]); + + return $question[1]; + }); + } + + $this->app->instance(OutputInterface::class, $mock); + + return $mock; + } + + /** + * Create a mock for the buffered output. + * + * @return \Mockery\LegacyMockInterface|\Mockery\MockInterface + */ + private function createABufferedOutputMock() + { + $mock = Mockery::mock(BufferedOutput::class . '[doWrite]') + ->shouldAllowMockingProtectedMethods() + ->shouldIgnoreMissing(); + + if ($this->test->expectsOutput === false) { + /** @var \Mockery\Expectation $expectation */ + $expectation = $mock->shouldReceive('doWrite'); + $expectation->never(); + + return $mock; + } + + if ($this->test->expectsOutput === true + && count($this->test->expectedOutput) === 0 + && count($this->test->expectedOutputSubstrings) === 0 + ) { + /** @var \Mockery\Expectation $expectation */ + $expectation = $mock->shouldReceive('doWrite'); + $expectation->atLeast()->once(); + } + + foreach ($this->test->expectedOutput as $i => $output) { + /** @var \Mockery\Expectation $expectation */ + $expectation = $mock->shouldReceive('doWrite'); + $expectation->once() + ->ordered() + ->with($output, Mockery::any()) + ->andReturnUsing(function () use ($i) { + unset($this->test->expectedOutput[$i]); + }); + } + + foreach ($this->test->expectedOutputSubstrings as $i => $text) { + /** @var \Mockery\Expectation $expectation */ + $expectation = $mock->shouldReceive('doWrite'); + $expectation->atLeast() + ->times(0) + ->withArgs(fn ($output) => str_contains($output, $text)) + ->andReturnUsing(function () use ($i) { + unset($this->test->expectedOutputSubstrings[$i]); + }); + } + + foreach ($this->test->unexpectedOutput as $output => $displayed) { + /** @var \Mockery\Expectation $expectation */ + $expectation = $mock->shouldReceive('doWrite'); + $expectation->atLeast() + ->times(0) + ->ordered() + ->with($output, Mockery::any()) + ->andReturnUsing(function () use ($output) { + $this->test->unexpectedOutput[$output] = true; + }); + } + + foreach ($this->test->unexpectedOutputSubstrings as $text => $displayed) { + /** @var \Mockery\Expectation $expectation */ + $expectation = $mock->shouldReceive('doWrite'); + $expectation->atLeast() + ->times(0) + ->withArgs(fn ($output) => str_contains($output, $text)) + ->andReturnUsing(function () use ($text) { + $this->test->unexpectedOutputSubstrings[$text] = true; + }); + } + + return $mock; + } + + /** + * Flush the expectations from the test case. + */ + protected function flushExpectations(): void + { + $this->test->expectedOutput = []; + $this->test->expectedOutputSubstrings = []; + $this->test->unexpectedOutput = []; + $this->test->unexpectedOutputSubstrings = []; + $this->test->expectedTables = []; + $this->test->expectedQuestions = []; + $this->test->expectedChoices = []; + } + + /** + * Handle the object's destruction. + */ + public function __destruct() + { + if ($this->hasExecuted) { + return; + } + + $this->run(); + } +} diff --git a/src/foundation/src/Testing/RefreshDatabase.php b/src/foundation/src/Testing/RefreshDatabase.php index 42078968f..415fbd94a 100644 --- a/src/foundation/src/Testing/RefreshDatabase.php +++ b/src/foundation/src/Testing/RefreshDatabase.php @@ -72,6 +72,13 @@ protected function usingInMemoryDatabase(): bool */ protected function refreshTestDatabase(): void { + $shouldMockOutput = true; + if ($hasMockConsoleOutput = property_exists($this, 'mockConsoleOutput')) { + $shouldMockOutput = $this->mockConsoleOutput; + + $this->mockConsoleOutput = false; + } + $migrateRefresh = property_exists($this, 'migrateRefresh') && (bool) $this->migrateRefresh; if ($migrateRefresh || ! RefreshDatabaseState::$migrated) { $this->command('migrate:fresh', $this->migrateFreshUsing()); @@ -81,6 +88,10 @@ protected function refreshTestDatabase(): void } } + if ($hasMockConsoleOutput) { + $this->mockConsoleOutput = $shouldMockOutput; + } + $this->beginDatabaseTransaction(); } diff --git a/src/queue/src/Console/ClearCommand.php b/src/queue/src/Console/ClearCommand.php index 5c5ea3067..08f4d0d3b 100644 --- a/src/queue/src/Console/ClearCommand.php +++ b/src/queue/src/Console/ClearCommand.php @@ -7,7 +7,7 @@ use Hyperf\Command\Command; use Hyperf\Contract\ConfigInterface; use Hyperf\Stringable\Str; -use Hypervel\Foundation\Console\ConfirmableTrait; +use Hypervel\Console\ConfirmableTrait; use Hypervel\Queue\Contracts\ClearableQueue; use Hypervel\Queue\Contracts\Factory as FactoryContract; use Hypervel\Support\Traits\HasLaravelStyleCommand; diff --git a/src/scheduling/README.md b/src/scheduling/README.md deleted file mode 100644 index 7341dc085..000000000 --- a/src/scheduling/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Scheduling for Hypervel -=== \ No newline at end of file diff --git a/src/scheduling/composer.json b/src/scheduling/composer.json deleted file mode 100644 index 664f64484..000000000 --- a/src/scheduling/composer.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "hypervel/scheduling", - "type": "library", - "description": "The scheduling package for Hypervel.", - "license": "MIT", - "keywords": [ - "php", - "hyperf", - "crontab", - "scheduling", - "swoole", - "hypervel" - ], - "autoload": { - "psr-4": { - "Hypervel\\Scheduling\\": "src/" - } - }, - "require": { - "php": "^8.2", - "ext-mbstring": "*", - "hypervel/support": "^0.1", - "dragonmantank/cron-expression": "^3.3.2", - "symfony/console": "^5.4|^6.4|^7.0" - }, - "require-dev": { - "mockery/mockery": "^1.5" - }, - "suggest": { - "hypervel/prompt": "Required to run schedule:test command.", - "friendsofhyperf/pretty-console": "Required to run schedule:run command. (~3.1.0)" - }, - "extra": { - "hyperf": { - "config": "Hypervel\\Scheduling\\ConfigProvider" - }, - "branch-alias": { - "dev-main": "0.1-dev" - } - }, - "prefer-stable": true, - "minimum-stability": "dev" -} \ No newline at end of file diff --git a/src/support/src/Facades/Artisan.php b/src/support/src/Facades/Artisan.php index a6841aed8..6d2c95eb2 100644 --- a/src/support/src/Facades/Artisan.php +++ b/src/support/src/Facades/Artisan.php @@ -5,13 +5,12 @@ namespace Hypervel\Support\Facades; use Hypervel\Foundation\Console\Contracts\Kernel as KernelContract; -use Hypervel\Scheduling\Schedule; /** * @method static void bootstrap() - * @method static void schedule(\Hypervel\Scheduling\Schedule $schedule) + * @method static void schedule(\Hypervel\Console\Scheduling\Schedule $schedule) * @method static void commands() - * @method static \Hyperf\Command\ClosureCommand command(string $signature, \Closure $callback) + * @method static \Hypervel\Console\ClosureCommand command(string $signature, \Closure $callback) * @method static void load(array|string $paths) * @method static \Hypervel\Foundation\Console\Contracts\Kernel addCommands(array $commands) * @method static \Hypervel\Foundation\Console\Contracts\Kernel addCommandPaths(array $paths) @@ -21,8 +20,8 @@ * @method static void call(string $command, array $parameters = [], \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer = null) * @method static array all() * @method static string output() - * @method static void setArtisan(\Hypervel\Foundation\Console\Contracts\Application $artisan) - * @method static \Hypervel\Foundation\Console\Contracts\Application getArtisan() + * @method static void setArtisan(\Hypervel\Console\Contracts\Application $artisan) + * @method static \Hypervel\Console\Contracts\Application getArtisan() * * @see \Hypervel\Foundation\Console\Contracts\Kernel */ diff --git a/src/support/src/Facades/Schedule.php b/src/support/src/Facades/Schedule.php index fc6d33cc6..ce59849a5 100644 --- a/src/support/src/Facades/Schedule.php +++ b/src/support/src/Facades/Schedule.php @@ -4,88 +4,88 @@ namespace Hypervel\Support\Facades; -use Hypervel\Scheduling\Schedule as ScheduleAccessor; +use Hypervel\Console\Scheduling\Schedule as ScheduleAccessor; /** - * @method static \Hypervel\Scheduling\CallbackEvent call(string|callable $callback, array $parameters = []) - * @method static \Hypervel\Scheduling\Event command(string $command, array $parameters = []) - * @method static \Hypervel\Scheduling\CallbackEvent job(object|string $job, string|null $queue = null, string|null $connection = null) - * @method static \Hypervel\Scheduling\Event exec(string $command, array $parameters = []) + * @method static \Hypervel\Console\Scheduling\CallbackEvent call(string|callable $callback, array $parameters = []) + * @method static \Hypervel\Console\Scheduling\Event command(string $command, array $parameters = []) + * @method static \Hypervel\Console\Scheduling\CallbackEvent job(object|string $job, string|null $queue = null, string|null $connection = null) + * @method static \Hypervel\Console\Scheduling\Event exec(string $command, array $parameters = []) * @method static void group(\Closure $events) * @method static string compileArrayInput(string|int $key, array $value) - * @method static bool serverShouldRun(\Hypervel\Scheduling\Event $event, \DateTimeInterface $time) + * @method static bool serverShouldRun(\Hypervel\Console\Scheduling\Event $event, \DateTimeInterface $time) * @method static \Hyperf\Collection\Collection dueEvents(\Hypervel\Contracts\Foundation\Application $app) - * @method static \Hypervel\Scheduling\Event[] events() - * @method static \Hypervel\Scheduling\Schedule useCache(string $store) + * @method static \Hypervel\Console\Scheduling\Event[] events() + * @method static \Hypervel\Console\Scheduling\Schedule useCache(string $store) * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() * @method static mixed macroCall(string $method, array $parameters) - * @method static \Hypervel\Scheduling\PendingEventAttributes withoutOverlapping(int $expiresAt = 1440) - * @method static void mergeAttributes(\Hypervel\Scheduling\Event $event) - * @method static \Hypervel\Scheduling\PendingEventAttributes user(string $user) - * @method static \Hypervel\Scheduling\PendingEventAttributes environments(array|mixed $environments) - * @method static \Hypervel\Scheduling\PendingEventAttributes evenInMaintenanceMode() - * @method static \Hypervel\Scheduling\PendingEventAttributes onOneServer() - * @method static \Hypervel\Scheduling\PendingEventAttributes runInBackground() - * @method static \Hypervel\Scheduling\PendingEventAttributes when(\Closure|bool $callback) - * @method static \Hypervel\Scheduling\PendingEventAttributes skip(\Closure|bool $callback) - * @method static \Hypervel\Scheduling\PendingEventAttributes name(string $description) - * @method static \Hypervel\Scheduling\PendingEventAttributes description(string $description) - * @method static \Hypervel\Scheduling\PendingEventAttributes cron(string $expression) - * @method static \Hypervel\Scheduling\PendingEventAttributes between(string $startTime, string $endTime) - * @method static \Hypervel\Scheduling\PendingEventAttributes unlessBetween(string $startTime, string $endTime) - * @method static \Hypervel\Scheduling\PendingEventAttributes everySecond() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyTwoSeconds() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyFiveSeconds() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyTenSeconds() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyFifteenSeconds() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyTwentySeconds() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyThirtySeconds() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyMinute() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyTwoMinutes() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyThreeMinutes() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyFourMinutes() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyFiveMinutes() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyTenMinutes() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyFifteenMinutes() - * @method static \Hypervel\Scheduling\PendingEventAttributes everyThirtyMinutes() - * @method static \Hypervel\Scheduling\PendingEventAttributes hourly() - * @method static \Hypervel\Scheduling\PendingEventAttributes hourlyAt(array|string|int|int[] $offset) - * @method static \Hypervel\Scheduling\PendingEventAttributes everyOddHour(array|string|int $offset = 0) - * @method static \Hypervel\Scheduling\PendingEventAttributes everyTwoHours(array|string|int $offset = 0) - * @method static \Hypervel\Scheduling\PendingEventAttributes everyThreeHours(array|string|int $offset = 0) - * @method static \Hypervel\Scheduling\PendingEventAttributes everyFourHours(array|string|int $offset = 0) - * @method static \Hypervel\Scheduling\PendingEventAttributes everySixHours(array|string|int $offset = 0) - * @method static \Hypervel\Scheduling\PendingEventAttributes daily() - * @method static \Hypervel\Scheduling\PendingEventAttributes at(string $time) - * @method static \Hypervel\Scheduling\PendingEventAttributes dailyAt(string $time) - * @method static \Hypervel\Scheduling\PendingEventAttributes twiceDaily(int $first = 1, int $second = 13) - * @method static \Hypervel\Scheduling\PendingEventAttributes twiceDailyAt(int $first = 1, int $second = 13, int $offset = 0) - * @method static \Hypervel\Scheduling\PendingEventAttributes weekdays() - * @method static \Hypervel\Scheduling\PendingEventAttributes weekends() - * @method static \Hypervel\Scheduling\PendingEventAttributes mondays() - * @method static \Hypervel\Scheduling\PendingEventAttributes tuesdays() - * @method static \Hypervel\Scheduling\PendingEventAttributes wednesdays() - * @method static \Hypervel\Scheduling\PendingEventAttributes thursdays() - * @method static \Hypervel\Scheduling\PendingEventAttributes fridays() - * @method static \Hypervel\Scheduling\PendingEventAttributes saturdays() - * @method static \Hypervel\Scheduling\PendingEventAttributes sundays() - * @method static \Hypervel\Scheduling\PendingEventAttributes weekly() - * @method static \Hypervel\Scheduling\PendingEventAttributes weeklyOn(array|mixed $dayOfWeek, string $time = '0:0') - * @method static \Hypervel\Scheduling\PendingEventAttributes monthly() - * @method static \Hypervel\Scheduling\PendingEventAttributes monthlyOn(int $dayOfMonth = 1, string $time = '0:0') - * @method static \Hypervel\Scheduling\PendingEventAttributes twiceMonthly(int $first = 1, int $second = 16, string $time = '0:0') - * @method static \Hypervel\Scheduling\PendingEventAttributes lastDayOfMonth(string $time = '0:0') - * @method static \Hypervel\Scheduling\PendingEventAttributes quarterly() - * @method static \Hypervel\Scheduling\PendingEventAttributes quarterlyOn(int $dayOfQuarter = 1, string $time = '0:0') - * @method static \Hypervel\Scheduling\PendingEventAttributes yearly() - * @method static \Hypervel\Scheduling\PendingEventAttributes yearlyOn(int $month = 1, int|string $dayOfMonth = 1, string $time = '0:0') - * @method static \Hypervel\Scheduling\PendingEventAttributes days(array|mixed $days) - * @method static \Hypervel\Scheduling\PendingEventAttributes timezone(\DateTimeZone|string $timezone) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes withoutOverlapping(int $expiresAt = 1440) + * @method static void mergeAttributes(\Hypervel\Console\Scheduling\Event $event) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes user(string $user) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes environments(array|mixed $environments) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes evenInMaintenanceMode() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes onOneServer() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes runInBackground() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes when(\Closure|bool $callback) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes skip(\Closure|bool $callback) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes name(string $description) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes description(string $description) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes cron(string $expression) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes between(string $startTime, string $endTime) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes unlessBetween(string $startTime, string $endTime) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everySecond() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyTwoSeconds() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyFiveSeconds() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyTenSeconds() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyFifteenSeconds() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyTwentySeconds() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyThirtySeconds() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyMinute() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyTwoMinutes() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyThreeMinutes() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyFourMinutes() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyFiveMinutes() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyTenMinutes() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyFifteenMinutes() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyThirtyMinutes() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes hourly() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes hourlyAt(array|string|int|int[] $offset) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyOddHour(array|string|int $offset = 0) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyTwoHours(array|string|int $offset = 0) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyThreeHours(array|string|int $offset = 0) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everyFourHours(array|string|int $offset = 0) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes everySixHours(array|string|int $offset = 0) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes daily() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes at(string $time) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes dailyAt(string $time) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes twiceDaily(int $first = 1, int $second = 13) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes twiceDailyAt(int $first = 1, int $second = 13, int $offset = 0) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes weekdays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes weekends() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes mondays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes tuesdays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes wednesdays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes thursdays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes fridays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes saturdays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes sundays() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes weekly() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes weeklyOn(array|mixed $dayOfWeek, string $time = '0:0') + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes monthly() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes monthlyOn(int $dayOfMonth = 1, string $time = '0:0') + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes twiceMonthly(int $first = 1, int $second = 16, string $time = '0:0') + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes lastDayOfMonth(string $time = '0:0') + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes quarterly() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes quarterlyOn(int $dayOfQuarter = 1, string $time = '0:0') + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes yearly() + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes yearlyOn(int $month = 1, int|string $dayOfMonth = 1, string $time = '0:0') + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes days(array|mixed $days) + * @method static \Hypervel\Console\Scheduling\PendingEventAttributes timezone(\DateTimeZone|string $timezone) * - * @see \Hypervel\Scheduling\Schedule + * @see \Hypervel\Console\Scheduling\Schedule */ class Schedule extends Facade { diff --git a/src/telescope/src/Console/ClearCommand.php b/src/telescope/src/Console/ClearCommand.php index 37c4b9ee3..040d5709a 100644 --- a/src/telescope/src/Console/ClearCommand.php +++ b/src/telescope/src/Console/ClearCommand.php @@ -4,7 +4,7 @@ namespace Hypervel\Telescope\Console; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; use Hypervel\Telescope\Contracts\ClearableRepository; class ClearCommand extends Command diff --git a/src/telescope/src/Console/PauseCommand.php b/src/telescope/src/Console/PauseCommand.php index 479398f90..b62fd6478 100644 --- a/src/telescope/src/Console/PauseCommand.php +++ b/src/telescope/src/Console/PauseCommand.php @@ -5,7 +5,7 @@ namespace Hypervel\Telescope\Console; use Hypervel\Cache\Contracts\Factory as CacheFactory; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; class PauseCommand extends Command { diff --git a/src/telescope/src/Console/PruneCommand.php b/src/telescope/src/Console/PruneCommand.php index f59fcfd2b..8f6d9049f 100644 --- a/src/telescope/src/Console/PruneCommand.php +++ b/src/telescope/src/Console/PruneCommand.php @@ -4,7 +4,7 @@ namespace Hypervel\Telescope\Console; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; use Hypervel\Support\Carbon; use Hypervel\Telescope\Contracts\PrunableRepository; diff --git a/src/telescope/src/Console/PublishCommand.php b/src/telescope/src/Console/PublishCommand.php index 532d545f9..b74211283 100644 --- a/src/telescope/src/Console/PublishCommand.php +++ b/src/telescope/src/Console/PublishCommand.php @@ -4,7 +4,7 @@ namespace Hypervel\Telescope\Console; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; class PublishCommand extends Command { diff --git a/src/telescope/src/Console/ResumeCommand.php b/src/telescope/src/Console/ResumeCommand.php index d1793061b..b8392391a 100644 --- a/src/telescope/src/Console/ResumeCommand.php +++ b/src/telescope/src/Console/ResumeCommand.php @@ -5,7 +5,7 @@ namespace Hypervel\Telescope\Console; use Hypervel\Cache\Contracts\Factory as CacheFactory; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; class ResumeCommand extends Command { diff --git a/src/telescope/src/Watchers/ScheduleWatcher.php b/src/telescope/src/Watchers/ScheduleWatcher.php index bce3d71e7..7f4293f25 100644 --- a/src/telescope/src/Watchers/ScheduleWatcher.php +++ b/src/telescope/src/Watchers/ScheduleWatcher.php @@ -4,8 +4,8 @@ namespace Hypervel\Telescope\Watchers; -use Hypervel\Scheduling\CallbackEvent; -use Hypervel\Scheduling\Events; +use Hypervel\Console\Events; +use Hypervel\Console\Scheduling\CallbackEvent; use Hypervel\Telescope\Contracts\EntriesRepository; use Hypervel\Telescope\IncomingEntry; use Hypervel\Telescope\Telescope; diff --git a/tests/Console/ArtisanCommandTest.php b/tests/Console/ArtisanCommandTest.php new file mode 100644 index 000000000..3e9c0b3ef --- /dev/null +++ b/tests/Console/ArtisanCommandTest.php @@ -0,0 +1,374 @@ + 0); + + $this->artisan('exit') + ->assertOk(); + } + + public function testConsoleCommandFails() + { + Artisan::command('exit', fn () => 1); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Expected status code 0 but received 1.'); + + $this->artisan('exit') + ->assertOk(); + } + + public function testConsoleCommandPassesWithOutput() + { + $this->registerSurveyCommand(); + + $this->artisan('survey') + ->expectsQuestion('What is your name?', 'Albert Chen') + ->expectsQuestion('Which language do you prefer?', 'PHP') + ->expectsOutput('Your name is Albert Chen and you prefer PHP.') + ->doesntExpectOutput('Your name is Albert Chen and you prefer Ruby.') + ->assertExitCode(0); + } + + public function testConsoleCommandPassesWithRepeatingOutput() + { + $this->registerSlimCommand(); + + $this->artisan('slim') + ->expectsQuestion('Who?', 'Albert') + ->expectsQuestion('What?', 'Albert') + ->expectsQuestion('Huh?', 'Albert') + ->expectsOutput('Albert') + ->doesntExpectOutput('Chen') + ->expectsOutput('Albert') + ->expectsOutput('Albert') + ->assertExitCode(0); + } + + public function testConsoleCommandFailsFromUnexpectedOutput() + { + $this->registerSurveyCommand(); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Output "Your name is Albert and you prefer PHP." was printed.'); + + $this->artisan('survey') + ->expectsQuestion('What is your name?', 'Albert') + ->expectsQuestion('Which language do you prefer?', 'PHP') + ->doesntExpectOutput('Your name is Albert and you prefer PHP.') + ->assertExitCode(0); + } + + public function testConsoleCommandFailsFromUnexpectedOutputSubstring() + { + $this->registerContainsCommand(); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Output "Albert Chen" was printed.'); + + $this->artisan('contains') + ->doesntExpectOutputToContain('Albert Chen') + ->assertExitCode(0); + } + + public function testConsoleCommandFailsFromMissingOutput() + { + $this->registerSurveyCommand(); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Output "Your name is Albert Chen and you prefer PHP." was not printed.'); + + $this->ignoringMockOnceExceptions(function () { + $this->artisan('survey') + ->expectsQuestion('What is your name?', 'Albert Chen') + ->expectsQuestion('Which language do you prefer?', 'Ruby') + ->expectsOutput('Your name is Albert Chen and you prefer PHP.') + ->assertExitCode(0); + }); + } + + public function testConsoleCommandFailsFromExitCodeMismatch() + { + $this->registerSurveyCommand(); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Expected status code 1 but received 0.'); + + $this->artisan('survey') + ->expectsQuestion('What is your name?', 'Albert Chen') + ->expectsQuestion('Which language do you prefer?', 'PHP') + ->assertExitCode(1); + } + + public function testConsoleCommandFailsFromUnOrderedOutput() + { + $this->registerSlimCommand(); + + $this->expectException(InvalidOrderException::class); + + $this->ignoringMockOnceExceptions(function () { + $this->artisan('slim') + ->expectsQuestion('Who?', 'Albert') + ->expectsQuestion('What?', 'Danger') + ->expectsQuestion('Huh?', 'Chen') + ->expectsOutput('Albert') + ->expectsOutput('Chen') + ->expectsOutput('Danger') + ->assertExitCode(0); + }); + } + + public function testConsoleCommandPassesIfTheOutputContains() + { + $this->registerContainsCommand(); + + $this->artisan('contains') + ->expectsOutputToContain('Albert Chen') + ->assertExitCode(0); + } + + public function testConsoleCommandPassesIfOutputsSomething() + { + $this->registerContainsCommand(); + + $this->artisan('contains') + ->expectsOutput() + ->assertExitCode(0); + } + + public function testConsoleCommandPassesIfoutputsIsSomethingAndIsTheExpectedOutput() + { + $this->registerContainsCommand(); + + $this->artisan('contains') + ->expectsOutput() + ->expectsOutput('My name is Albert Chen') + ->assertExitCode(0); + } + + public function testConsoleCommandFailIfDoesntOutputSomething() + { + Artisan::command('exit', fn () => 0); + + $this->expectException(InvalidCountException::class); + + $this->artisan('exit') + ->expectsOutput() + ->assertExitCode(0); + + m::close(); + } + + public function testConsoleCommandFailIfDoesntOutputSomethingAndIsNotTheExpectedOutput() + { + Artisan::command('exit', fn () => 0); + + $this->expectException(AssertionFailedError::class); + + $this->ignoringMockOnceExceptions(function () { + $this->artisan('exit') + ->expectsOutput() + ->expectsOutput('My name is Albert Chen') + ->assertExitCode(0); + }); + } + + public function testConsoleCommandPassesIfDoesNotOutputAnything() + { + Artisan::command('exit', fn () => 0); + + $this->artisan('exit') + ->doesntExpectOutput() + ->assertExitCode(0); + } + + public function testConsoleCommandPassesIfDoesNotOutputAnythingAndIsNotTheExpectedOutput() + { + Artisan::command('exit', fn () => 0); + + $this->artisan('exit') + ->doesntExpectOutput() + ->doesntExpectOutput('My name is Albert Chen') + ->assertExitCode(0); + } + + public function testConsoleCommandPassesIfExpectsOutputAndThereIsInteractions() + { + $this->registerInteractionsCommand(); + + $this->artisan('interactions', ['--no-interaction' => true]) + ->expectsOutput() + ->expectsQuestion('What is your name?', 'Albert Chen') + ->expectsChoice('Which language do you prefer?', 'PHP', ['PHP', 'PHP', 'PHP']) + ->expectsConfirmation('Do you want to continue?', 'no') + ->assertExitCode(0); + } + + public function testConsoleCommandFailsIfDoesntExpectOutputButThereIsInteractions() + { + $this->registerInteractionsCommand(); + + $this->expectException(InvalidCountException::class); + + $this->artisan('interactions', ['--no-interaction' => true]) + ->doesntExpectOutput() + ->expectsQuestion('What is your name?', 'Albert Chen') + ->expectsChoice('Which language do you prefer?', 'PHP', ['PHP', 'PHP', 'PHP']) + ->expectsConfirmation('Do you want to continue?', 'no') + ->assertExitCode(0); + + m::close(); + } + + public function testConsoleCommandFailsIfDoesntExpectOutputButOutputsSomething() + { + $this->registerContainsCommand(); + + $this->expectException(InvalidCountException::class); + + $this->artisan('contains') + ->doesntExpectOutput() + ->assertExitCode(0); + + m::close(); + } + + public function testConsoleCommandFailsIfDoesntExpectOutputSomethingAndIsNotExpectOutput() + { + $this->registerContainsCommand(); + + $this->expectException(InvalidCountException::class); + + $this->artisan('contains') + ->doesntExpectOutput() + ->doesntExpectOutput('My name is Albert Chen') + ->assertExitCode(0); + + m::close(); + } + + public function testConsoleCommandFailsIfTheOutputDoesNotContain() + { + $this->registerContainsCommand(); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Output does not contain "Chen Albert".'); + + $this->ignoringMockOnceExceptions(function () { + $this->artisan('contains') + ->expectsOutputToContain('Chen Albert') + ->assertExitCode(0); + }); + } + + public function testPendingCommandCanBeRapped() + { + Artisan::command('new-england', function () { + $this->line('The region of New England consists of the following states:'); + $this->info('Connecticut'); + $this->info('Maine'); + $this->info('Massachusetts'); + $this->info('New Hampshire'); + $this->info('Rhode Island'); + $this->info('Vermont'); + }); + + $newEngland = [ + 'Connecticut', + 'Maine', + 'Massachusetts', + 'New Hampshire', + 'Rhode Island', + 'Vermont', + ]; + + $this->artisan('new-england') + ->expectsOutput('The region of New England consists of the following states:') + ->tap(function ($command) use ($newEngland) { + foreach ($newEngland as $state) { + $command->expectsOutput($state); + } + }) + ->assertExitCode(0); + } + + protected function registerSurveyCommand(): void + { + Artisan::command('survey', function () { + $name = $this->ask('What is your name?'); + + $language = $this->choice('Which language do you prefer?', [ + 'PHP', + 'Ruby', + 'Python', + ]); + + $this->line("Your name is {$name} and you prefer {$language}."); + }); + } + + protected function registerContainsCommand(): void + { + Artisan::command('contains', function () { + $this->line('My name is Albert Chen'); + }); + } + + protected function registerInteractionsCommand(): void + { + Artisan::command('interactions', function () { + $this->ask('What is your name?'); + $this->choice('Which language do you prefer?', [ + 'PHP', + 'PHP', + 'PHP', + ]); + + $this->table(['Name', 'Email'], [ + ['Albert Chen', 'albert@hypervel.org'], + ]); + + $this->confirm('Do you want to continue?', true); + }); + } + + protected function registerSlimCommand(): void + { + Artisan::command('slim', function () { + $this->line($this->ask('Who?')); + $this->line($this->ask('What?')); + $this->line($this->ask('Huh?')); + }); + } + + protected function ignoringMockOnceExceptions(callable $callback): void + { + try { + $callback(); + } finally { + try { + m::close(); + } catch (InvalidCountException) { + // Ignore mock exception from PendingCommand::expectsOutput(). + } + } + } +} diff --git a/tests/Scheduling/CacheEventMutexTest.php b/tests/Console/Scheduling/CacheEventMutexTest.php similarity index 96% rename from tests/Scheduling/CacheEventMutexTest.php rename to tests/Console/Scheduling/CacheEventMutexTest.php index 939b2aa3c..b124ff5f2 100644 --- a/tests/Scheduling/CacheEventMutexTest.php +++ b/tests/Console/Scheduling/CacheEventMutexTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Hypervel\Tests\Scheduling; +namespace Hypervel\Tests\Console\Scheduling; use Hypervel\Cache\ArrayStore; use Hypervel\Cache\Contracts\Factory as CacheFactory; use Hypervel\Cache\Contracts\Repository; use Hypervel\Cache\Contracts\Store; -use Hypervel\Scheduling\CacheEventMutex; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\CacheEventMutex; +use Hypervel\Console\Scheduling\Event; use Mockery as m; use PHPUnit\Framework\TestCase; diff --git a/tests/Scheduling/CacheSchedulingMutexTest.php b/tests/Console/Scheduling/CacheSchedulingMutexTest.php similarity index 93% rename from tests/Scheduling/CacheSchedulingMutexTest.php rename to tests/Console/Scheduling/CacheSchedulingMutexTest.php index ce7ad2d58..2825c956e 100644 --- a/tests/Scheduling/CacheSchedulingMutexTest.php +++ b/tests/Console/Scheduling/CacheSchedulingMutexTest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Hypervel\Tests\Scheduling; +namespace Hypervel\Tests\Console\Scheduling; use Hypervel\Cache\Contracts\Factory as CacheFactory; use Hypervel\Cache\Contracts\Repository; -use Hypervel\Scheduling\CacheEventMutex; -use Hypervel\Scheduling\CacheSchedulingMutex; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Scheduling\CacheEventMutex; +use Hypervel\Console\Scheduling\CacheSchedulingMutex; +use Hypervel\Console\Scheduling\Event; use Hypervel\Support\Carbon; use Mockery as m; use PHPUnit\Framework\TestCase; diff --git a/tests/Scheduling/EventTest.php b/tests/Console/Scheduling/EventTest.php similarity index 97% rename from tests/Scheduling/EventTest.php rename to tests/Console/Scheduling/EventTest.php index 8ee186cb7..6e354ebcc 100644 --- a/tests/Scheduling/EventTest.php +++ b/tests/Console/Scheduling/EventTest.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace Hypervel\Tests\Scheduling; +namespace Hypervel\Tests\Console\Scheduling; use Hyperf\Context\ApplicationContext; use Hyperf\Context\Context; use Hyperf\Stringable\Str; use Hyperf\Support\Filesystem\Filesystem; +use Hypervel\Console\Contracts\EventMutex; +use Hypervel\Console\Scheduling\Event; use Hypervel\Container\Contracts\Container; use Hypervel\Foundation\Console\Contracts\Kernel as KernelContract; -use Hypervel\Scheduling\Contracts\EventMutex; -use Hypervel\Scheduling\Event; use Hypervel\Tests\Foundation\Concerns\HasMockedApplication; use Mockery as m; use PHPUnit\Framework\TestCase; diff --git a/tests/Scheduling/FrequencyTest.php b/tests/Console/Scheduling/FrequencyTest.php similarity index 98% rename from tests/Scheduling/FrequencyTest.php rename to tests/Console/Scheduling/FrequencyTest.php index b71ae8f49..1dc7131bf 100644 --- a/tests/Scheduling/FrequencyTest.php +++ b/tests/Console/Scheduling/FrequencyTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Hypervel\Tests\Scheduling; +namespace Hypervel\Tests\Console\Scheduling; -use Hypervel\Scheduling\Contracts\EventMutex; -use Hypervel\Scheduling\Event; +use Hypervel\Console\Contracts\EventMutex; +use Hypervel\Console\Scheduling\Event; use Hypervel\Support\Carbon; use Mockery as m; use PHPUnit\Framework\TestCase; diff --git a/tests/Scheduling/ScheduleTest.php b/tests/Console/Scheduling/ScheduleTest.php similarity index 92% rename from tests/Scheduling/ScheduleTest.php rename to tests/Console/Scheduling/ScheduleTest.php index 15a5af57a..143d98c0b 100644 --- a/tests/Scheduling/ScheduleTest.php +++ b/tests/Console/Scheduling/ScheduleTest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Hypervel\Tests\Scheduling; +namespace Hypervel\Tests\Console\Scheduling; +use Hypervel\Console\Contracts\EventMutex; +use Hypervel\Console\Contracts\SchedulingMutex; +use Hypervel\Console\Scheduling\Schedule; use Hypervel\Container\Container; use Hypervel\Queue\Contracts\ShouldQueue; -use Hypervel\Scheduling\Contracts\EventMutex; -use Hypervel\Scheduling\Contracts\SchedulingMutex; -use Hypervel\Scheduling\Schedule; use Hypervel\Tests\Foundation\Concerns\HasMockedApplication; use Mockery as m; use Mockery\MockInterface; diff --git a/tests/Foundation/Testing/BootstrapConfigProvider.php b/tests/Foundation/Testing/BootstrapConfigProvider.php index a53f25eae..aa028e5e4 100644 --- a/tests/Foundation/Testing/BootstrapConfigProvider.php +++ b/tests/Foundation/Testing/BootstrapConfigProvider.php @@ -36,6 +36,7 @@ class BootstrapConfigProvider \Hypervel\Cache\ConfigProvider::class, \Hypervel\Cookie\ConfigProvider::class, \Hypervel\Config\ConfigProvider::class, + \Hypervel\Console\ConfigProvider::class, \Hypervel\Dispatcher\ConfigProvider::class, \Hypervel\Encryption\ConfigProvider::class, \Hypervel\Event\ConfigProvider::class, @@ -48,7 +49,6 @@ class BootstrapConfigProvider \Hypervel\Notifications\ConfigProvider::class, \Hypervel\Queue\ConfigProvider::class, \Hypervel\Router\ConfigProvider::class, - \Hypervel\Scheduling\ConfigProvider::class, \Hypervel\Session\ConfigProvider::class, ]; diff --git a/tests/Foundation/Testing/DatabaseMigrationsTest.php b/tests/Foundation/Testing/DatabaseMigrationsTest.php index f7a7514ca..0f9c75ff4 100644 --- a/tests/Foundation/Testing/DatabaseMigrationsTest.php +++ b/tests/Foundation/Testing/DatabaseMigrationsTest.php @@ -28,6 +28,13 @@ class DatabaseMigrationsTest extends ApplicationTestCase protected ?string $seeder = null; + public function setUp(): void + { + parent::setUp(); + + $this->withoutMockingConsoleOutput(); + } + public function tearDown(): void { $this->dropViews = false; diff --git a/tests/Telescope/Watchers/CommandWatcherTest.php b/tests/Telescope/Watchers/CommandWatcherTest.php index 5612d68ff..e097488b6 100644 --- a/tests/Telescope/Watchers/CommandWatcherTest.php +++ b/tests/Telescope/Watchers/CommandWatcherTest.php @@ -5,7 +5,7 @@ namespace Hypervel\Tests\Telescope\Watchers; use Hyperf\Contract\ConfigInterface; -use Hypervel\Foundation\Console\Command; +use Hypervel\Console\Command; use Hypervel\Foundation\Console\Contracts\Kernel as KernelContract; use Hypervel\Telescope\EntryType; use Hypervel\Telescope\Watchers\CommandWatcher; diff --git a/tests/Telescope/Watchers/ScheduleWatcherTest.php b/tests/Telescope/Watchers/ScheduleWatcherTest.php index 081728286..5959cac8c 100644 --- a/tests/Telescope/Watchers/ScheduleWatcherTest.php +++ b/tests/Telescope/Watchers/ScheduleWatcherTest.php @@ -5,9 +5,9 @@ namespace Hypervel\Tests\Telescope\Watchers; use Hyperf\Contract\ConfigInterface; -use Hypervel\Scheduling\Event; -use Hypervel\Scheduling\Events\ScheduledTaskFinished; -use Hypervel\Scheduling\Events\ScheduledTaskStarting; +use Hypervel\Console\Events\ScheduledTaskFinished; +use Hypervel\Console\Events\ScheduledTaskStarting; +use Hypervel\Console\Scheduling\Event; use Hypervel\Telescope\EntryType; use Hypervel\Telescope\Watchers\ScheduleWatcher; use Hypervel\Tests\Telescope\FeatureTestCase;