diff --git a/src/Commands/MakeJobCommand.php b/src/Commands/MakeJobCommand.php index 0dbc6bf..aa3c054 100644 --- a/src/Commands/MakeJobCommand.php +++ b/src/Commands/MakeJobCommand.php @@ -28,9 +28,7 @@ class MakeJobCommand extends Command public function handle(): int { return $this->executeWithTiming(function () { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); // Ensure class name ends with Job if (!str_ends_with($className, 'Job')) { @@ -38,16 +36,13 @@ public function handle(): int } $namespace = 'App\\Jobs' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $parts[] = $className; - - $filePath = base_path( - 'app/Jobs/' . implode(DIRECTORY_SEPARATOR, $parts) . '.php' - ); + $fileName = count($parts) > 0 ? implode('/', $parts) . '/' . $className : $className; + $filePath = $this->generatedFilePath('app/Jobs', $fileName); // Check if Job already exists if (file_exists($filePath)) { $this->displayError('Job already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -62,7 +57,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Job created successfully'); - $this->line('📦 File: ' . str_replace(base_path(), '', $filePath) . ''); + $this->line('📦 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('⚙️ Class: ' . $className . ''); diff --git a/tests/Unit/MakeJobCommandTest.php b/tests/Unit/MakeJobCommandTest.php new file mode 100644 index 0000000..c55b2f1 --- /dev/null +++ b/tests/Unit/MakeJobCommandTest.php @@ -0,0 +1,125 @@ +tempRoot = sys_get_temp_dir() . '/doppar-queue-command-' . bin2hex(random_bytes(5)); + mkdir($this->tempRoot, 0755, true); + } + + protected function tearDown(): void + { + $this->deleteDirectory($this->tempRoot); + + parent::tearDown(); + } + + public function testMakeJobCommandSupportsBackslashesAndRelativeOutput(): void + { + $command = new class($this->tempRoot) extends MakeJobCommand + { + public array $capturedLines = []; + public array $capturedSuccesses = []; + + public function __construct(private string $tempRoot) + { + parent::__construct(); + } + + protected function argument($key = null) + { + return $key === 'name' ? 'Reports\\GenerateDaily' : null; + } + + protected function line(string $string, ?string $style = null): void + { + $this->capturedLines[] = $string; + } + + protected function newLine($count = 1): void + { + } + + protected function displaySuccess(string $message): void + { + $this->capturedSuccesses[] = $message; + } + + protected function executeWithTiming(callable $callback): int + { + $result = $callback(); + + return is_int($result) ? $result : 0; + } + + protected function generatedFilePath(string $baseDirectory, string $name, string $extension = '.php'): string + { + $normalizedName = $this->normalizeGeneratedName($name); + $relativePath = trim($baseDirectory, '/\\'); + + if ($normalizedName !== '') { + $relativePath .= '/' . $normalizedName; + } + + return $this->tempRoot . '/' . $relativePath . $extension; + } + + protected function relativePath(string $path, ?string $basePath = null): string + { + $normalizedPath = str_replace('\\', '/', $path); + $normalizedBase = rtrim(str_replace('\\', '/', $this->tempRoot), '/'); + + return substr($normalizedPath, strlen($normalizedBase) + 1); + } + }; + + $result = $command->handle(); + $file = $this->tempRoot . '/app/Jobs/Reports/GenerateDailyJob.php'; + $contents = (string) file_get_contents($file); + + $this->assertSame(0, $result); + $this->assertFileExists($file); + $this->assertStringContainsString('namespace App\\Jobs\\Reports;', $contents); + $this->assertStringContainsString('class GenerateDailyJob extends Job', $contents); + $this->assertContains('Job created successfully', $command->capturedSuccesses); + $this->assertContains( + '📦 File: app/Jobs/Reports/GenerateDailyJob.php', + $command->capturedLines + ); + } + + private function deleteDirectory(string $directory): void + { + if (!is_dir($directory)) { + return; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($iterator as $item) { + if ($item->isDir()) { + rmdir($item->getPathname()); + continue; + } + + unlink($item->getPathname()); + } + + rmdir($directory); + } +}