ldc-file-storage-service/tests/Unit/Lib/ValueObjects/StorageFilePathTest.php

163 lines
4.7 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Uwil\Storage\Tests\Unit\Lib\ValueObjects;
use Uwil\Storage\Exceptions\UnexpectedStorageException;
use Uwil\Storage\Lib\ValueObjects\StorageFilePath;
covers(StorageFilePath::class);
describe('StorageFilePath instantiation', function (): void {
it('can instantiate with a valid single file name', function (): void {
// GIVEN
$path = 'document.pdf';
// WHEN
$vo = new StorageFilePath($path);
// THEN
expect($vo)->toBeInstanceOf(StorageFilePath::class)
->and($vo->value)->toBe('document.pdf');
});
it('can instantiate with a valid nested path', function (string $validPath): void {
// GIVEN
$path = $validPath;
// WHEN
$vo = new StorageFilePath($path);
// THEN
expect($vo)->toBeInstanceOf(StorageFilePath::class)
->and($vo->value)->toBe($validPath);
})->with([
'simple directory' => 'uploads/file.png',
'multi-level nested' => '2026/11/03/invoice_123.pdf',
'with hyphens dots' => 'user-data/avatar.min.jpg',
]);
});
describe('StorageFilePath validation', function (): void {
it('throws exception if path is empty or only whitespace', function (string $invalidPath): void {
// GIVEN
$path = $invalidPath;
// WHEN
$action = fn () => new StorageFilePath($path);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Path cannot be empty.'
);
})->with([
'empty string' => '',
'spaces only' => ' ',
'tabs and newlines' => "\t \n \r",
]);
it('throws exception if path starts with a slash', function (string $invalidPath): void {
// GIVEN
$path = $invalidPath;
// WHEN
$action = fn () => new StorageFilePath($path);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Path must not start with a "/".'
);
})->with([
'single slash root' => '/file.txt',
'leading nested path' => '/uploads/document.pdf',
]);
it('throws exception if path ends with a slash', function (string $invalidPath): void {
// GIVEN
$path = $invalidPath;
// WHEN
$action = fn () => new StorageFilePath($path);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Path must not end with a "/".'
);
})->with([
'trailing slash on file' => 'file.txt/',
'trailing directory' => 'uploads/2026/',
]);
it('throws exception if path contains double slashes', function (string $invalidPath): void {
// GIVEN
$path = $invalidPath;
// WHEN
$action = fn () => new StorageFilePath($path);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Path must not contain "//".'
);
})->with([
'double slash in middle' => 'uploads//file.txt',
'triple slash' => 'folder///nested/file.txt',
]);
it('throws exception if path contains backslashes', function (string $invalidPath): void {
// GIVEN
$path = $invalidPath;
// WHEN
$action = fn () => new StorageFilePath($path);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Path must not contain "\\".'
);
})->with([
'windows separator' => 'uploads\\file.txt',
'multiple backslash' => 'folder\\sub\\file.txt',
]);
it('throws exception if path contains directory traversal', function (string $invalidPath): void {
// GIVEN
$path = $invalidPath;
// WHEN
$action = fn () => new StorageFilePath($path);
// THEN
expect($action)->toThrow(
UnexpectedStorageException::class,
'Path must not contain "..".'
);
})->with([
'relative parent traversal' => 'uploads/../secret.txt',
'leading traversal' => '../file.txt',
'trailing traversal' => 'folder/..',
]);
});
describe('StorageFilePath string representation', function (): void {
it('returns the path when cast to string or via __toString', function (): void {
// GIVEN
$path = 'documents/archive/2026.zip';
$vo = new StorageFilePath($path);
// WHEN
$asString = (string) $vo;
$fromToStringMethod = $vo->__toString();
// THEN
expect($asString)->toBe($path)
->and($fromToStringMethod)->toBe($path);
});
});