From 370801dbda8bbb4f65211cab46f14d89e2c656a3 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 1 May 2026 14:48:36 +0400 Subject: [PATCH 1/5] [#488] Raise console command coverage and silence serve test logs --- src/Console/Commands/ServeCommand.php | 19 +- .../Console/Commands/DebugBarCommandTest.php | 33 ++- .../Commands/MigrationMigrateCommandTest.php | 10 + .../Console/Commands/OpenApiCommandTest.php | 78 ++++++ .../ResourceCacheClearCommandTest.php | 71 ++++- .../Console/Commands/ServeCommandTest.php | 255 ++++++++++++++++++ 6 files changed, 459 insertions(+), 7 deletions(-) diff --git a/src/Console/Commands/ServeCommand.php b/src/Console/Commands/ServeCommand.php index 61f5d3512..6027a158d 100644 --- a/src/Console/Commands/ServeCommand.php +++ b/src/Console/Commands/ServeCommand.php @@ -180,11 +180,7 @@ protected function startPhpServer(string $host, int $port) 'public', ]; - $descriptors = [ - 0 => STDIN, - 1 => STDOUT, - 2 => STDERR, - ]; + $descriptors = $this->serverProcessDescriptors(); $process = proc_open($cmd, $descriptors, $pipes); @@ -195,6 +191,19 @@ protected function startPhpServer(string $host, int $port) return $process; } + /** + * Descriptors used to start the PHP built-in server process. + * @return array|mixed> + */ + protected function serverProcessDescriptors(): array + { + return [ + 0 => STDIN, + 1 => STDOUT, + 2 => STDERR, + ]; + } + /** * Wait until the PHP server is ready to accept connections. * @param resource $process diff --git a/tests/Unit/Console/Commands/DebugBarCommandTest.php b/tests/Unit/Console/Commands/DebugBarCommandTest.php index 44f7a28eb..589a2ea94 100644 --- a/tests/Unit/Console/Commands/DebugBarCommandTest.php +++ b/tests/Unit/Console/Commands/DebugBarCommandTest.php @@ -4,8 +4,9 @@ use Symfony\Component\Console\Tester\CommandTester; use Quantum\Console\Commands\DebugBarCommand; -use Quantum\Storage\FileSystem; use Quantum\Tests\Unit\AppTestCase; +use Quantum\Storage\FileSystem; +use ReflectionMethod; class DebugBarCommandTest extends AppTestCase { @@ -50,4 +51,34 @@ public function testExecShowsErrorWhenAlreadyInstalled(): void @rmdir($assetsPath); @rmdir(assets_dir() . DS . 'DebugBar'); } + + public function testCopyResourcesRecursivelyCopiesFiles(): void + { + $sourceDir = base_dir() . DS . 'var' . DS . 'tmp_debugbar_src_' . uniqid(); + $targetDir = base_dir() . DS . 'var' . DS . 'tmp_debugbar_dst_' . uniqid(); + $nestedSourceDir = $sourceDir . DS . 'nested'; + + mkdir($nestedSourceDir, 0777, true); + mkdir($targetDir, 0777, true); + file_put_contents($sourceDir . DS . 'root.css', 'root'); + file_put_contents($nestedSourceDir . DS . 'nested.js', 'nested'); + + $this->setPrivateProperty($this->command, 'publicDebugBarFolderPath', $targetDir); + + $method = new ReflectionMethod($this->command, 'copyResources'); + $method->setAccessible(true); + $method->invoke($this->command, $sourceDir, $targetDir); + + $this->assertFileExists($targetDir . DS . 'root.css'); + $this->assertFileExists($targetDir . DS . 'nested' . DS . 'nested.js'); + + @unlink($targetDir . DS . 'nested' . DS . 'nested.js'); + @unlink($targetDir . DS . 'root.css'); + @rmdir($targetDir . DS . 'nested'); + @rmdir($targetDir); + @unlink($nestedSourceDir . DS . 'nested.js'); + @unlink($sourceDir . DS . 'root.css'); + @rmdir($nestedSourceDir); + @rmdir($sourceDir); + } } diff --git a/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php b/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php index bfc8b1352..c945c52fb 100644 --- a/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php +++ b/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php @@ -48,4 +48,14 @@ public function testExecCancelsDownDirectionWhenNotConfirmed(): void $output = $tester->getDisplay(); $this->assertStringContainsString('Operation was canceled!', $output); } + + public function testExecWithInvalidDirectionShowsMigrationMessage(): void + { + $tester = new CommandTester($this->command); + $tester->execute([ + 'direction' => 'invalid', + ]); + + $this->assertNotSame('', trim($tester->getDisplay())); + } } diff --git a/tests/Unit/Console/Commands/OpenApiCommandTest.php b/tests/Unit/Console/Commands/OpenApiCommandTest.php index 32756a2a4..25501fdc8 100644 --- a/tests/Unit/Console/Commands/OpenApiCommandTest.php +++ b/tests/Unit/Console/Commands/OpenApiCommandTest.php @@ -6,6 +6,7 @@ use Quantum\Console\Commands\OpenApiCommand; use Quantum\Tests\Unit\AppTestCase; use Quantum\Storage\FileSystem; +use ReflectionMethod; class OpenApiCommandTest extends AppTestCase { @@ -78,4 +79,81 @@ public function testExecShowsErrorWhenModuleIsMissing(): void } } } + + public function testOpenapiRoutesContainsModuleSpecPath(): void + { + $method = new ReflectionMethod($this->command, 'openapiRoutes'); + $method->setAccessible(true); + + $routes = $method->invoke($this->command, 'Blog'); + + $this->assertStringContainsString('"openapi"', $routes); + $this->assertStringContainsString('Blog', $routes); + $this->assertStringContainsString('resources\\openapi\\spec.json', $routes); + } + + public function testCopyResourcesSkipsExcludedFiles(): void + { + $sourceDir = base_dir() . DS . 'var' . DS . 'tmp_openapi_src_' . uniqid(); + $targetDir = base_dir() . DS . 'var' . DS . 'tmp_openapi_dst_' . uniqid(); + + mkdir($sourceDir, 0777, true); + mkdir($targetDir, 0777, true); + + file_put_contents($sourceDir . DS . 'swagger-ui.css', 'body{}'); + file_put_contents($sourceDir . DS . 'index.html', ''); + + $this->setPrivateProperty($this->command, 'vendorOpenApiFolderPath', $sourceDir); + $this->setPrivateProperty($this->command, 'publicOpenApiFolderPath', $targetDir); + + $method = new ReflectionMethod($this->command, 'copyResources'); + $method->setAccessible(true); + $method->invoke($this->command); + + $this->assertFileExists($targetDir . DS . 'swagger-ui.css'); + $this->assertFileDoesNotExist($targetDir . DS . 'index.html'); + + @unlink($targetDir . DS . 'swagger-ui.css'); + @rmdir($targetDir); + @unlink($sourceDir . DS . 'swagger-ui.css'); + @unlink($sourceDir . DS . 'index.html'); + @rmdir($sourceDir); + } + + public function testGenerateOpenapiSpecificationCreatesSpecFile(): void + { + $command = new class () extends OpenApiCommand { + public function info(string $message): void + { + } + + public function error(string $message): void + { + } + }; + + $moduleName = 'OpenApiSpec' . uniqid(); + $annotationDir = modules_dir() . DS . $moduleName . DS . 'Controllers' . DS . 'OpenApi'; + $specDir = modules_dir() . DS . $moduleName . DS . 'resources' . DS . 'openapi'; + $specPath = $specDir . DS . 'spec.json'; + + mkdir($annotationDir, 0777, true); + mkdir($specDir, 0777, true); + file_put_contents($annotationDir . DS . 'SpecController.php', "setAccessible(true); + $method->invoke($command, $moduleName); + + $this->assertFileExists($specPath); + $this->assertNotSame('', trim((string) file_get_contents($specPath))); + + @unlink($annotationDir . DS . 'SpecController.php'); + @rmdir($annotationDir); + @unlink($specPath); + @rmdir($specDir); + @rmdir(modules_dir() . DS . $moduleName . DS . 'Controllers'); + @rmdir(modules_dir() . DS . $moduleName . DS . 'resources'); + @rmdir(modules_dir() . DS . $moduleName); + } } diff --git a/tests/Unit/Console/Commands/ResourceCacheClearCommandTest.php b/tests/Unit/Console/Commands/ResourceCacheClearCommandTest.php index 1f672a2da..8fa6a4c3e 100644 --- a/tests/Unit/Console/Commands/ResourceCacheClearCommandTest.php +++ b/tests/Unit/Console/Commands/ResourceCacheClearCommandTest.php @@ -4,8 +4,9 @@ use Quantum\Console\Commands\ResourceCacheClearCommand; use Symfony\Component\Console\Tester\CommandTester; -use Quantum\Storage\FileSystem; use Quantum\Tests\Unit\AppTestCase; +use Quantum\Storage\FileSystem; +use ReflectionMethod; class ResourceCacheClearCommandTest extends AppTestCase { @@ -59,4 +60,72 @@ public function testExecShowsErrorWhenNoOptionsProvided(): void @rmdir($cacheDir); } + + public function testInitTypeAcceptsKnownType(): void + { + $method = new ReflectionMethod($this->command, 'initType'); + $method->setAccessible(true); + $method->invoke($this->command, 'views'); + + $this->assertSame('views', $this->getPrivateProperty($this->command, 'type')); + } + + public function testInitTypeThrowsForInvalidType(): void + { + $method = new ReflectionMethod($this->command, 'initType'); + $method->setAccessible(true); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Cache type {invalid} is invalid.'); + $method->invoke($this->command, 'invalid'); + } + + public function testInitModuleAcceptsKnownModule(): void + { + $this->setPrivateProperty($this->command, 'modules', ['blog', 'shop']); + + $method = new ReflectionMethod($this->command, 'initModule'); + $method->setAccessible(true); + $method->invoke($this->command, 'BLOG'); + + $this->assertSame('blog', $this->getPrivateProperty($this->command, 'module')); + } + + public function testClearResourceModuleAndTypeRemovesMatchedFiles(): void + { + config()->set('view_cache', ['cache_dir' => 'cache']); + + $cacheDir = base_dir() . DS . 'var' . DS . 'tmp_cache_' . uniqid(); + $moduleDir = $cacheDir . DS . 'views' . DS . 'blog'; + mkdir($moduleDir, 0777, true); + file_put_contents($moduleDir . DS . 'view.php', 'cached'); + + $this->setPrivateProperty($this->command, 'cacheDir', $cacheDir); + + $method = new ReflectionMethod($this->command, 'clearResourceModuleAndType'); + $method->setAccessible(true); + $method->invoke($this->command, 'blog', 'views'); + + $this->assertFileDoesNotExist($moduleDir . DS . 'view.php'); + + @rmdir($moduleDir); + @rmdir($cacheDir . DS . 'views'); + @rmdir($cacheDir); + } + + public function testExecClearsAllCacheWhenAllOptionProvided(): void + { + config()->set('view_cache', ['cache_dir' => 'tmp_cache_' . uniqid()]); + + $cacheDir = base_dir() . DS . config()->get('view_cache.cache_dir'); + mkdir($cacheDir, 0777, true); + file_put_contents($cacheDir . DS . 'a.cache', 'value'); + + $this->tester->execute(['--all' => true]); + + $this->assertFileDoesNotExist($cacheDir . DS . 'a.cache'); + $this->assertStringContainsString('Resource cache cleared successfully.', $this->tester->getDisplay()); + + @rmdir($cacheDir); + } } diff --git a/tests/Unit/Console/Commands/ServeCommandTest.php b/tests/Unit/Console/Commands/ServeCommandTest.php index 2820248be..6605a0ca3 100644 --- a/tests/Unit/Console/Commands/ServeCommandTest.php +++ b/tests/Unit/Console/Commands/ServeCommandTest.php @@ -5,6 +5,7 @@ use Symfony\Component\Console\Tester\CommandTester; use Quantum\Console\Commands\ServeCommand; use Quantum\Tests\Unit\AppTestCase; +use RuntimeException; class ServeCommandTest extends AppTestCase { @@ -85,4 +86,258 @@ protected function handleServerExecution(array $serverData): void $this->assertSame(8011, $command->receivedPort); $this->assertSame('http://127.0.0.1:8011', $command->receivedServerData['url']); } + + public function testPortThrowsForInvalidRange(): void + { + $command = new ServeCommand(); + + $tester = new CommandTester($command); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Port must be between 1 and 65535'); + $tester->execute(['--port' => '70000']); + } + + public function testStartServerOnAvailablePortSkipsBusyPort(): void + { + $command = new class () extends ServeCommand { + public int $cleanupCalls = 0; + protected int $maxPortScan = 3; + /** @var array */ + public array $checkedPorts = []; + + public function info(string $message): void + { + } + + protected function isPortInUse(string $host, int $port): bool + { + $this->checkedPorts[] = $port; + return $port === 8000; + } + + protected function startPhpServer(string $host, int $port) + { + return fopen('php://memory', 'r'); + } + + protected function waitUntilServerIsReady(string $host, int $port, $process): void + { + } + + protected function cleanupProcess($process): void + { + $this->cleanupCalls++; + parent::cleanupProcess($process); + } + + public function exposeStartServerOnAvailablePort(string $host, int $port): array + { + return $this->startServerOnAvailablePort($host, $port); + } + }; + + $serverData = $command->exposeStartServerOnAvailablePort('127.0.0.1', 8000); + + $this->assertSame([8000, 8001], $command->checkedPorts); + $this->assertSame(8001, $serverData['port']); + $this->assertSame('http://127.0.0.1:8001', $serverData['url']); + $this->assertSame(0, $command->cleanupCalls); + } + + public function testStartServerOnAvailablePortCleansUpWhenServerReadinessFails(): void + { + $command = new class () extends ServeCommand { + public int $cleanupCalls = 0; + protected int $maxPortScan = 2; + /** @var array */ + private array $attemptedPorts = []; + + public function info(string $message): void + { + } + + protected function isPortInUse(string $host, int $port): bool + { + return false; + } + + protected function startPhpServer(string $host, int $port) + { + $this->attemptedPorts[] = $port; + return fopen('php://memory', 'r'); + } + + protected function waitUntilServerIsReady(string $host, int $port, $process): void + { + throw new RuntimeException('not ready'); + } + + protected function cleanupProcess($process): void + { + $this->cleanupCalls++; + parent::cleanupProcess($process); + } + + public function exposeStartServerOnAvailablePort(string $host, int $port): array + { + return $this->startServerOnAvailablePort($host, $port); + } + }; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unable to start PHP server on any available port.'); + + try { + $command->exposeStartServerOnAvailablePort('127.0.0.1', 8000); + } finally { + $this->assertSame(2, $command->cleanupCalls); + } + } + + public function testHandleServerExecutionOpensBrowserWhenOptionEnabled(): void + { + $command = new class () extends ServeCommand { + public bool $openCalled = false; + public bool $waitCalled = false; + + protected function startServerOnAvailablePort(string $host, int $startPort): array + { + return [ + 'process' => fopen('php://memory', 'r'), + 'port' => $startPort, + 'url' => "http://{$host}:{$startPort}", + ]; + } + + protected function openBrowser(string $url): void + { + $this->openCalled = true; + } + + protected function waitForProcess($process): void + { + $this->waitCalled = true; + } + }; + + $tester = new CommandTester($command); + $tester->execute(['--open' => true]); + + $this->assertTrue($command->openCalled); + $this->assertTrue($command->waitCalled); + } + + public function testIsPortInUseDetectsListeningSocket(): void + { + $command = new class () extends ServeCommand { + public function exposeIsPortInUse(string $host, int $port): bool + { + return $this->isPortInUse($host, $port); + } + }; + + $server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr); + $this->assertIsResource($server); + $name = stream_socket_get_name($server, false); + $port = (int) substr((string) $name, strrpos((string) $name, ':') + 1); + + try { + $this->assertTrue($command->exposeIsPortInUse('127.0.0.1', $port)); + } finally { + fclose($server); + } + + $this->assertFalse($command->exposeIsPortInUse('127.0.0.1', $port)); + } + + public function testWaitForProcessClosesCompletedProcess(): void + { + $command = new class () extends ServeCommand { + public function exposeWaitForProcess($process): void + { + $this->waitForProcess($process); + } + }; + + $process = proc_open([PHP_BINARY, '-r', 'usleep(100000);'], [0 => STDIN, 1 => STDOUT, 2 => STDERR], $pipes); + $this->assertIsResource($process); + + $command->exposeWaitForProcess($process); + $this->assertFalse(is_resource($process)); + } + + public function testStartPhpServerAndWaitUntilReady(): void + { + $command = new class () extends ServeCommand { + public function exposeStartPhpServer(string $host, int $port) + { + return $this->startPhpServer($host, $port); + } + + public function exposeWaitUntilServerIsReady(string $host, int $port, $process): void + { + $this->waitUntilServerIsReady($host, $port, $process); + } + + public function exposeCleanupProcess($process): void + { + $this->cleanupProcess($process); + } + + protected function serverProcessDescriptors(): array + { + return [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + } + }; + + $publicDir = getcwd() . DS . 'public'; + $indexFile = $publicDir . DS . 'index.php'; + if (!is_dir($publicDir)) { + mkdir($publicDir, 0777, true); + } + file_put_contents($indexFile, "exposeStartPhpServer('127.0.0.1', $port); + $this->assertIsResource($process); + + try { + $command->exposeWaitUntilServerIsReady('127.0.0.1', $port, $process); + $this->assertTrue(true); + } finally { + if (is_resource($process)) { + proc_terminate($process); + } + $command->exposeCleanupProcess($process); + @unlink($indexFile); + @rmdir($publicDir); + } + } + + public function testOpenBrowserReturnsEarlyWhenCommandIsNotSupported(): void + { + $command = new class () extends ServeCommand { + protected function browserCommand(string $url): ?array + { + return null; + } + + public function exposeOpenBrowser(string $url): void + { + $this->openBrowser($url); + } + }; + + $command->exposeOpenBrowser('http://127.0.0.1:8000'); + $this->assertTrue(true); + } } From 142fea000e66d765a5c9ee15d664f9c72bdf0ec5 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 1 May 2026 14:57:09 +0400 Subject: [PATCH 2/5] [#488] Fix CI portability and cleanup handling in command tests --- tests/Unit/Console/Commands/OpenApiCommandTest.php | 5 ++++- tests/Unit/Console/Commands/ServeCommandTest.php | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Console/Commands/OpenApiCommandTest.php b/tests/Unit/Console/Commands/OpenApiCommandTest.php index 25501fdc8..55c304400 100644 --- a/tests/Unit/Console/Commands/OpenApiCommandTest.php +++ b/tests/Unit/Console/Commands/OpenApiCommandTest.php @@ -89,7 +89,10 @@ public function testOpenapiRoutesContainsModuleSpecPath(): void $this->assertStringContainsString('"openapi"', $routes); $this->assertStringContainsString('Blog', $routes); - $this->assertStringContainsString('resources\\openapi\\spec.json', $routes); + $this->assertTrue( + strpos($routes, 'resources/openapi/spec.json') !== false + || strpos($routes, 'resources\\openapi\\spec.json') !== false + ); } public function testCopyResourcesSkipsExcludedFiles(): void diff --git a/tests/Unit/Console/Commands/ServeCommandTest.php b/tests/Unit/Console/Commands/ServeCommandTest.php index 6605a0ca3..8b222bb55 100644 --- a/tests/Unit/Console/Commands/ServeCommandTest.php +++ b/tests/Unit/Console/Commands/ServeCommandTest.php @@ -128,7 +128,6 @@ protected function waitUntilServerIsReady(string $host, int $port, $process): vo protected function cleanupProcess($process): void { $this->cleanupCalls++; - parent::cleanupProcess($process); } public function exposeStartServerOnAvailablePort(string $host, int $port): array @@ -176,7 +175,6 @@ protected function waitUntilServerIsReady(string $host, int $port, $process): vo protected function cleanupProcess($process): void { $this->cleanupCalls++; - parent::cleanupProcess($process); } public function exposeStartServerOnAvailablePort(string $host, int $port): array From 0359a3697fc2ec3efcf66253338263962dd98c66 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 1 May 2026 15:03:27 +0400 Subject: [PATCH 3/5] [#488] Isolate serve command startup test workspace --- .../Console/Commands/ServeCommandTest.php | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/Unit/Console/Commands/ServeCommandTest.php b/tests/Unit/Console/Commands/ServeCommandTest.php index 8b222bb55..c9a3100a4 100644 --- a/tests/Unit/Console/Commands/ServeCommandTest.php +++ b/tests/Unit/Console/Commands/ServeCommandTest.php @@ -293,11 +293,12 @@ protected function serverProcessDescriptors(): array } }; - $publicDir = getcwd() . DS . 'public'; + $tempRoot = base_dir() . DS . 'var' . DS . 'tmp_serve_' . uniqid('', true); + $publicDir = $tempRoot . DS . 'public'; $indexFile = $publicDir . DS . 'index.php'; - if (!is_dir($publicDir)) { - mkdir($publicDir, 0777, true); - } + $originalCwd = getcwd(); + + mkdir($publicDir, 0777, true); file_put_contents($indexFile, "exposeStartPhpServer('127.0.0.1', $port); - $this->assertIsResource($process); - try { + $this->assertTrue(chdir($tempRoot)); + $process = $command->exposeStartPhpServer('127.0.0.1', $port); + $this->assertIsResource($process); $command->exposeWaitUntilServerIsReady('127.0.0.1', $port, $process); $this->assertTrue(true); } finally { - if (is_resource($process)) { + if (isset($process) && is_resource($process)) { proc_terminate($process); } - $command->exposeCleanupProcess($process); + if (isset($process)) { + $command->exposeCleanupProcess($process); + } + chdir($originalCwd); @unlink($indexFile); @rmdir($publicDir); + @rmdir($tempRoot); } } From 5d751881f867fd1eb3714a66065628709ccac54e Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 1 May 2026 15:05:34 +0400 Subject: [PATCH 4/5] [#488] Harden OpenApi spec test cleanup --- .../Console/Commands/OpenApiCommandTest.php | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/Unit/Console/Commands/OpenApiCommandTest.php b/tests/Unit/Console/Commands/OpenApiCommandTest.php index 55c304400..3283c4e67 100644 --- a/tests/Unit/Console/Commands/OpenApiCommandTest.php +++ b/tests/Unit/Console/Commands/OpenApiCommandTest.php @@ -144,19 +144,21 @@ public function error(string $message): void mkdir($specDir, 0777, true); file_put_contents($annotationDir . DS . 'SpecController.php', "setAccessible(true); - $method->invoke($command, $moduleName); - - $this->assertFileExists($specPath); - $this->assertNotSame('', trim((string) file_get_contents($specPath))); - - @unlink($annotationDir . DS . 'SpecController.php'); - @rmdir($annotationDir); - @unlink($specPath); - @rmdir($specDir); - @rmdir(modules_dir() . DS . $moduleName . DS . 'Controllers'); - @rmdir(modules_dir() . DS . $moduleName . DS . 'resources'); - @rmdir(modules_dir() . DS . $moduleName); + try { + $method = new ReflectionMethod($command, 'generateOpenapiSpecification'); + $method->setAccessible(true); + $method->invoke($command, $moduleName); + + $this->assertFileExists($specPath); + $this->assertNotSame('', trim((string) file_get_contents($specPath))); + } finally { + @unlink($annotationDir . DS . 'SpecController.php'); + @rmdir($annotationDir); + @unlink($specPath); + @rmdir($specDir); + @rmdir(modules_dir() . DS . $moduleName . DS . 'Controllers'); + @rmdir(modules_dir() . DS . $moduleName . DS . 'resources'); + @rmdir(modules_dir() . DS . $moduleName); + } } } From dab86e9f0697d11df67510113f4e23b588c2939b Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 1 May 2026 15:12:12 +0400 Subject: [PATCH 5/5] [#488] Assert exact invalid migration direction message --- tests/Unit/Console/Commands/MigrationMigrateCommandTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php b/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php index c945c52fb..430be19ad 100644 --- a/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php +++ b/tests/Unit/Console/Commands/MigrationMigrateCommandTest.php @@ -3,6 +3,7 @@ namespace Quantum\Tests\Unit\Console\Commands; use Quantum\Console\Commands\MigrationMigrateCommand; +use Quantum\Migration\Enums\ExceptionMessages; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Helper\HelperSet; @@ -56,6 +57,9 @@ public function testExecWithInvalidDirectionShowsMigrationMessage(): void 'direction' => 'invalid', ]); - $this->assertNotSame('', trim($tester->getDisplay())); + $this->assertStringContainsString( + ExceptionMessages::WRONG_MIGRATION_DIRECTION, + $tester->getDisplay() + ); } }