toBe('test-disk:sitemaps/sitemap.xml'); }); it('normalizes path by trimming whitespace and leading slashes', function () { // GIVEN $disk = Storage::disk('test-disk'); $normalizer = new WhitespacePathNormalizer(); // WHEN $storage = new SitemapStorage( 'test-disk', $disk, ' /nested/folder/sitemap.xml ', $normalizer ); // THEN expect((string) $storage)->toBe('test-disk:nested/folder/sitemap.xml'); }); it('accepts uppercase XML extension', function () { // GIVEN $disk = Storage::disk('test-disk'); $normalizer = new WhitespacePathNormalizer(); // WHEN $storage = new SitemapStorage( 'test-disk', $disk, 'sitemap.XML', $normalizer ); // THEN expect((string) $storage)->toBe('test-disk:sitemap.XML'); }); it('throws exception when path does not end with xml extension', function (string $invalidPath) { // GIVEN $disk = Storage::disk('test-disk'); $normalizer = new WhitespacePathNormalizer(); // WHEN $action = fn () => new SitemapStorage( 'test-disk', $disk, $invalidPath, $normalizer ); // THEN expect($action)->toThrow( SitemapException::class, 'The path must indicate a valid XML file location.' ); })->with([ 'txt extension' => 'sitemap.txt', 'json extension' => 'sitemap.json', 'no extension' => 'sitemap', ]); it('throws exception when path has empty filename', function (string $invalidPath) { // GIVEN $disk = Storage::disk('test-disk'); $normalizer = new WhitespacePathNormalizer(); // WHEN $action = fn () => new SitemapStorage( 'test-disk', $disk, $invalidPath, $normalizer ); // THEN expect($action)->toThrow( SitemapException::class, 'The path must indicate a valid XML file location.' ); })->with([ 'only extension' => '.xml', 'empty string' => '', 'whitespace string' => ' ', ]); it('throws exception when path traversal is detected', function (string $traversalPath) { // GIVEN $disk = Storage::disk('test-disk'); $normalizer = new WhitespacePathNormalizer(); // WHEN $action = fn () => new SitemapStorage( 'test-disk', $disk, $traversalPath, $normalizer ); // THEN expect($action)->toThrow(SitemapException::class); })->with([ 'parent directory' => '../sitemap.xml', 'nested parent traversal' => 'foo/../../sitemap.xml', ]); it('checks if sitemap exists on disk', function () { // GIVEN $disk = Storage::disk('test-disk'); $storage = new SitemapStorage( 'test-disk', $disk, 'sitemap.xml', new WhitespacePathNormalizer() ); // WHEN / THEN expect($storage->sitemapExists())->toBeFalse(); $storage->writeInto(''); expect($storage->sitemapExists())->toBeTrue(); }); it('writes content into disk successfully', function () { // GIVEN $disk = Storage::disk('test-disk'); $storage = new SitemapStorage( 'test-disk', $disk, 'sitemap.xml', new WhitespacePathNormalizer() ); // WHEN $storage->writeInto('https://example.com'); // THEN expect($disk->exists('sitemap.xml'))->toBeTrue() ->and($disk->get('sitemap.xml'))->toBe('https://example.com'); }); it('throws exception when write fails', function () { // GIVEN /** @var Filesystem&Mockery\MockInterface $diskMock */ $diskMock = Mockery::mock(Filesystem::class); $diskMock->expects('put')->with('sitemap.xml', '')->andReturnFalse(); $storage = new SitemapStorage( 'mock-disk', $diskMock, 'sitemap.xml', new WhitespacePathNormalizer() ); // WHEN $action = fn () => $storage->writeInto(''); // THEN expect($action)->toThrow( SitemapException::class, 'Failed to write sitemap content to disk.' ); }); it('reads content from disk successfully', function () { // GIVEN $disk = Storage::disk('test-disk'); $storage = new SitemapStorage( 'test-disk', $disk, 'sitemap.xml', new WhitespacePathNormalizer() ); $content = 'https://example.com'; $storage->writeInto($content); // WHEN $readContent = $storage->readFromDisk(); // THEN expect($readContent)->toBe($content); }); it('throws exception when reading non-existing file', function () { // GIVEN $disk = Storage::disk('test-disk'); $storage = new SitemapStorage( 'test-disk', $disk, 'sitemap.xml', new WhitespacePathNormalizer() ); // WHEN $action = fn () => $storage->readFromDisk(); // THEN expect($action)->toThrow( SitemapException::class, 'Sitemap file does not exist on disk.' ); }); it('throws exception when reading content returns null', function () { // GIVEN /** @var Filesystem&Mockery\MockInterface $diskMock */ $diskMock = Mockery::mock(Filesystem::class); $diskMock->expects('exists')->with('sitemap.xml')->andReturnTrue(); $diskMock->expects('get')->with('sitemap.xml')->andReturnNull(); $storage = new SitemapStorage( 'mock-disk', $diskMock, 'sitemap.xml', new WhitespacePathNormalizer() ); // WHEN $action = fn () => $storage->readFromDisk(); // THEN expect($action)->toThrow( SitemapException::class, 'Failed to read sitemap content from disk.' ); }); it('streams content from disk successfully', function () { // GIVEN $disk = Storage::disk('test-disk'); $storage = new SitemapStorage( 'test-disk', $disk, 'sitemap.xml', new WhitespacePathNormalizer() ); $content = 'https://example.com'; $storage->writeInto($content); // WHEN $stream = $storage->streamFromDisk(); // THEN expect(is_resource($stream))->toBeTrue(); $streamedContent = stream_get_contents($stream); fclose($stream); expect($streamedContent)->toBe($content); }); it('throws exception when streaming non-existing file', function () { // GIVEN $disk = Storage::disk('test-disk'); $storage = new SitemapStorage( 'test-disk', $disk, 'sitemap.xml', new WhitespacePathNormalizer() ); // WHEN $action = fn () => $storage->streamFromDisk(); // THEN expect($action)->toThrow( SitemapException::class, 'Sitemap file does not exist on disk.' ); }); it('throws exception when streaming fails to return a resource', function () { // GIVEN /** @var Filesystem&Mockery\MockInterface $diskMock */ $diskMock = Mockery::mock(Filesystem::class); $diskMock->expects('exists')->with('sitemap.xml')->andReturnTrue(); $diskMock->expects('readStream')->with('sitemap.xml')->andReturnNull(); $storage = new SitemapStorage( 'mock-disk', $diskMock, 'sitemap.xml', new WhitespacePathNormalizer() ); // WHEN $action = fn () => $storage->streamFromDisk(); // THEN expect($action)->toThrow( SitemapException::class, 'Failed to read sitemap stream from disk.' ); }); });