diff --git a/composer.json b/composer.json index 9fb1e7aa..746a3f38 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "guzzlehttp/psr7": "^2.0", "deviantintegral/jms-serializer-uri-handler": "^1.1", "deviantintegral/null-date-time": "^1.0", - "symfony/console": "^5.0||^6.0" + "symfony/console": "^7||^8" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.92.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4b22c2ea..825cae9a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,18 +1,13 @@ - - - - - - + ./tests/src/Unit + + ./tests/src/Functional + - - - ./src diff --git a/src/Command/SplitCommand.php b/src/Command/SplitCommand.php index be274ac7..17d1f7b0 100644 --- a/src/Command/SplitCommand.php +++ b/src/Command/SplitCommand.php @@ -17,7 +17,7 @@ */ class SplitCommand extends Command { - protected function configure() + protected function configure(): void { parent::configure(); $this->setName('har:split') @@ -29,7 +29,7 @@ protected function configure() ->addOption('force', 'f', InputOption::VALUE_NONE, 'Overwrite destination files that already exist.'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $source = $input->getArgument('har'); @@ -60,6 +60,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $io->progressAdvance(); } $io->progressFinish(); + + return Command::SUCCESS; } private function getSplitDestination( diff --git a/tests/src/Functional/SplitCommandTest.php b/tests/src/Functional/SplitCommandTest.php new file mode 100644 index 00000000..ee64ea3b --- /dev/null +++ b/tests/src/Functional/SplitCommandTest.php @@ -0,0 +1,264 @@ +tempDir = sys_get_temp_dir().'/har_test_'.uniqid(); + mkdir($this->tempDir, recursive: true); + + // Set up the command tester + $command = new SplitCommand(); + $this->commandTester = new CommandTester($command); + } + + protected function tearDown(): void + { + // Clean up temporary directory + if (is_dir($this->tempDir)) { + $this->recursiveRemoveDirectory($this->tempDir); + } + + parent::tearDown(); + } + + public function testSplitMultipleEntries(): void + { + $harFile = __DIR__.'/../../fixtures/www.softwareishard.com-multiple-entries.har'; + + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + ]); + + // Should succeed + $this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + + // Should create 11 files (one per entry) + $files = glob($this->tempDir.'/*.har'); + $this->assertCount(11, $files); + + // Verify file names are sequential + $expectedFiles = []; + for ($i = 1; $i <= 11; ++$i) { + $expectedFiles[] = $this->tempDir.'/'.$i.'.har'; + } + natsort($files); + $files = array_values($files); // Re-index array after sorting + $this->assertEquals($expectedFiles, $files); + + // Verify each file is valid HAR with single entry + $serializer = new Serializer(); + foreach ($files as $file) { + $contents = file_get_contents($file); + $har = $serializer->deserializeHar($contents); + $this->assertCount(1, $har->getLog()->getEntries(), 'Each split file should have exactly one entry'); + } + } + + public function testSplitSingleEntry(): void + { + $harFile = __DIR__.'/../../fixtures/www.softwareishard.com-single-entry.har'; + + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + ]); + + $this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + + // Should create 1 file + $files = glob($this->tempDir.'/*.har'); + $this->assertCount(1, $files); + $this->assertFileExists($this->tempDir.'/1.har'); + + // Verify the file is valid HAR + $serializer = new Serializer(); + $contents = file_get_contents($this->tempDir.'/1.har'); + $har = $serializer->deserializeHar($contents); + $this->assertCount(1, $har->getLog()->getEntries()); + } + + public function testSplitWithMd5Option(): void + { + $harFile = __DIR__.'/../../fixtures/www.softwareishard.com-multiple-entries.har'; + + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + '--md5' => true, + ]); + + $this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + + // Should create 11 files + $files = glob($this->tempDir.'/*.har'); + $this->assertCount(11, $files); + + // Verify file names are MD5 hashes + foreach ($files as $file) { + $basename = basename($file, '.har'); + $this->assertMatchesRegularExpression('/^[a-f0-9]{32}$/', $basename, 'Filename should be MD5 hash'); + } + + // Verify each file is valid HAR + $serializer = new Serializer(); + foreach ($files as $file) { + $contents = file_get_contents($file); + $har = $serializer->deserializeHar($contents); + $this->assertCount(1, $har->getLog()->getEntries()); + + // Verify the filename matches the MD5 of the request URL + $url = (string) $har->getLog()->getEntries()[0]->getRequest()->getUrl(); + $expectedHash = md5($url); + $basename = basename($file, '.har'); + $this->assertEquals($expectedHash, $basename); + } + } + + public function testSplitWithForceOption(): void + { + $harFile = __DIR__.'/../../fixtures/www.softwareishard.com-single-entry.har'; + + // First split + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + ]); + $this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + + $outputFile = $this->tempDir.'/1.har'; + $this->assertFileExists($outputFile); + $originalContent = file_get_contents($outputFile); + + // Modify the file to verify it gets overwritten + file_put_contents($outputFile, 'modified content'); + $this->assertEquals('modified content', file_get_contents($outputFile)); + + // Split again with --force + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + '--force' => true, + ]); + $this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + + // Verify the file was overwritten with original content + $newContent = file_get_contents($outputFile); + $this->assertEquals($originalContent, $newContent); + $this->assertNotEquals('modified content', $newContent); + } + + public function testSplitFailsWhenFileExistsWithoutForce(): void + { + $harFile = __DIR__.'/../../fixtures/www.softwareishard.com-single-entry.har'; + + // First split + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + ]); + $this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + + // Try to split again without --force + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches('/exists\. Use --force to overwrite/'); + + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + ]); + } + + public function testSplitToCurrentDirectoryByDefault(): void + { + $harFile = __DIR__.'/../../fixtures/www.softwareishard.com-single-entry.har'; + + // Save current directory + $originalDir = getcwd(); + + try { + // Change to temp directory + chdir($this->tempDir); + + // Execute without destination argument + $this->commandTester->execute([ + 'har' => $harFile, + ]); + + $this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + + // File should be created in current directory (tempDir) + $this->assertFileExists($this->tempDir.'/1.har'); + } finally { + // Restore original directory + chdir($originalDir); + } + } + + public function testSplitPreservesHarStructure(): void + { + $harFile = __DIR__.'/../../fixtures/www.softwareishard.com-multiple-entries.har'; + + // Load original HAR + $serializer = new Serializer(); + $originalContents = file_get_contents($harFile); + $originalHar = $serializer->deserializeHar($originalContents); + $originalEntries = $originalHar->getLog()->getEntries(); + + $this->commandTester->execute([ + 'har' => $harFile, + 'destination' => $this->tempDir, + ]); + + // Load split files and compare each entry + for ($i = 0; $i < \count($originalEntries); ++$i) { + $splitFile = $this->tempDir.'/'.($i + 1).'.har'; + $this->assertFileExists($splitFile); + + $splitContents = file_get_contents($splitFile); + $splitHar = $serializer->deserializeHar($splitContents); + $splitEntry = $splitHar->getLog()->getEntries()[0]; + + // Compare request URLs to verify correct entry + $this->assertEquals( + (string) $originalEntries[$i]->getRequest()->getUrl(), + (string) $splitEntry->getRequest()->getUrl() + ); + } + } + + private function recursiveRemoveDirectory(string $directory): void + { + if (!is_dir($directory)) { + return; + } + + $items = array_diff(scandir($directory), ['.', '..']); + foreach ($items as $item) { + $path = $directory.'/'.$item; + if (is_dir($path)) { + $this->recursiveRemoveDirectory($path); + } else { + unlink($path); + } + } + rmdir($directory); + } +}