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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 4 additions & 9 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<coverage>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false" failOnWarning="false">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/src/Unit</directory>
</testsuite>
<testsuite name="Functional">
<directory suffix="Test.php">./tests/src/Functional</directory>
</testsuite>
</testsuites>
<logging>
<junit outputFile="build/logs/results.xml"/>
</logging>
<source>
<include>
<directory suffix=".php">./src</directory>
Expand Down
6 changes: 4 additions & 2 deletions src/Command/SplitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
class SplitCommand extends Command
{
protected function configure()
protected function configure(): void
{
parent::configure();
$this->setName('har:split')
Expand All @@ -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');
Expand Down Expand Up @@ -60,6 +60,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
$io->progressAdvance();
}
$io->progressFinish();

return Command::SUCCESS;
}

private function getSplitDestination(
Expand Down
264 changes: 264 additions & 0 deletions tests/src/Functional/SplitCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
<?php

declare(strict_types=1);

namespace Deviantintegral\Har\Tests\Functional;

use Deviantintegral\Har\Command\SplitCommand;
use Deviantintegral\Har\Serializer;
use Deviantintegral\Har\Tests\Unit\HarTestBase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;

class SplitCommandTest extends HarTestBase
{
private string $tempDir;
private CommandTester $commandTester;

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

// Create a temporary directory for test outputs
$this->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);
}
}
Loading