lms/vendor/maennchen/zipstream-php/test/CallbackOutputTest.php
2025-12-15 12:26:23 +01:00

203 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
namespace ZipStream\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use ZipArchive;
use ZipStream\Stream\CallbackStreamWrapper;
use ZipStream\ZipStream;
final class CallbackOutputTest extends TestCase
{
protected function tearDown(): void
{
// Clean up any registered callbacks to prevent memory leaks in tests
CallbackStreamWrapper::cleanup();
parent::tearDown();
}
public function testDataIsForwardedToCallback(): void
{
$buf = '';
$zip = new ZipStream(
outputStream: CallbackStreamWrapper::open(
static function (string $chunk) use (&$buf): void { $buf .= $chunk; }
),
sendHttpHeaders: false
);
$zip->addFile('hello.txt', 'Hello World');
$zip->finish();
$tmp = tmpfile();
fwrite($tmp, $buf);
rewind($tmp);
$meta = stream_get_meta_data($tmp);
$za = new ZipArchive();
$za->open($meta['uri']);
$content = $za->getFromName('hello.txt');
$za->close();
fclose($tmp);
$this->assertSame('Hello World', $content);
}
public function testMultipleSimultaneousStreams(): void
{
$buf1 = '';
$buf2 = '';
$stream1 = CallbackStreamWrapper::open(
static function (string $chunk) use (&$buf1): void { $buf1 .= $chunk; }
);
$stream2 = CallbackStreamWrapper::open(
static function (string $chunk) use (&$buf2): void { $buf2 .= $chunk; }
);
$this->assertIsResource($stream1);
$this->assertIsResource($stream2);
fwrite($stream1, 'data1');
fwrite($stream2, 'data2');
fclose($stream1);
fclose($stream2);
$this->assertSame('data1', $buf1);
$this->assertSame('data2', $buf2);
}
public function testExceptionHandlingInCallback(): void
{
$stream = CallbackStreamWrapper::open(
static function (string $chunk): void {
throw new RuntimeException('Callback error');
}
);
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Callback function failed during stream write: Callback error');
fwrite($stream, 'test data');
}
public function testLargeDataChunks(): void
{
$receivedChunks = [];
$totalBytes = 0;
$stream = CallbackStreamWrapper::open(
static function (string $chunk) use (&$receivedChunks, &$totalBytes): void {
$receivedChunks[] = strlen($chunk);
$totalBytes += strlen($chunk);
}
);
// Write large chunks of data
$largeData = str_repeat('x', 65536); // 64KB
fwrite($stream, $largeData);
fwrite($stream, $largeData);
fclose($stream);
$this->assertSame(131072, $totalBytes); // 128KB total
$this->assertNotEmpty($receivedChunks);
// Large data should be written (possibly in multiple chunks)
$this->assertGreaterThan(0, max($receivedChunks));
}
public function testStreamPositionTracking(): void
{
$stream = CallbackStreamWrapper::open(
static function (string $chunk): void { /* no-op */ }
);
$this->assertSame(0, ftell($stream));
fwrite($stream, 'hello');
$this->assertSame(5, ftell($stream));
fwrite($stream, ' world');
$this->assertSame(11, ftell($stream));
fclose($stream);
}
public function testInvalidModeRejection(): void
{
$stream = CallbackStreamWrapper::open(
static function (string $chunk): void { /* no-op */ }
);
// Close the stream first
fclose($stream);
// Try to open with read mode - should fail
$readStream = fopen('zipcb://invalid', 'rb');
$this->assertFalse($readStream);
}
public function testStreamStatistics(): void
{
$stream = CallbackStreamWrapper::open(
static function (string $chunk): void { /* no-op */ }
);
fwrite($stream, 'test data');
$stats = fstat($stream);
$this->assertIsArray($stats);
$this->assertSame(9, $stats['size']); // Length of 'test data'
$this->assertSame(0o100666, $stats['mode']); // Regular file permissions
fclose($stream);
}
public function testProgressTracking(): void
{
$progress = [];
$totalBytes = 0;
$zip = new ZipStream(
outputStream: CallbackStreamWrapper::open(
static function (string $chunk) use (&$progress, &$totalBytes): void {
$totalBytes += strlen($chunk);
$progress[] = $totalBytes;
}
),
sendHttpHeaders: false
);
$zip->addFile('file1.txt', 'Content 1');
$zip->addFile('file2.txt', 'Content 2');
$zip->finish();
$this->assertNotEmpty($progress);
$this->assertGreaterThan(0, $totalBytes);
$this->assertTrue(count($progress) > 1, 'Should have multiple progress updates');
}
public function testCallbackCleanupOnClose(): void
{
$callbackExecuted = false;
$stream = CallbackStreamWrapper::open(
static function (string $chunk) use (&$callbackExecuted): void {
$callbackExecuted = true;
}
);
fwrite($stream, 'test');
$this->assertTrue($callbackExecuted);
fclose($stream);
// After closing, callback should be cleaned up
// We can't directly test this, but the tearDown cleanup should work without issues
$this->assertTrue(true); // Placeholder assertion
}
}