test(DateHashPathGenerator): add unit tests for path generation
Added comprehensive unit tests for DateHashPathGenerator to validate path generation with and without directories, ensuring unique paths for identical files and proper slugging of filenames.
This commit is contained in:
parent
4a41f429ac
commit
dcfec3de04
2 changed files with 339 additions and 0 deletions
199
tests/Unit/Lib/PathGenerators/DateHashPathGeneratorTest.php
Normal file
199
tests/Unit/Lib/PathGenerators/DateHashPathGeneratorTest.php
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Uwil\Storage\Tests\Unit\Lib\PathGenerators;
|
||||
|
||||
use Uwil\Storage\Lib\PathGenerators\DateHashPathGenerator;
|
||||
use Uwil\Storage\Lib\ValueObjects\DiskName;
|
||||
use Uwil\Storage\Lib\ValueObjects\FileStream;
|
||||
use Uwil\Storage\Lib\ValueObjects\SanitizedFile;
|
||||
use Uwil\Storage\Lib\ValueObjects\StorageFilePath;
|
||||
|
||||
covers(DateHashPathGenerator::class);
|
||||
|
||||
describe('DateHashPathGenerator generation without directory', function (): void {
|
||||
it('generates a storage file path with UTC date segments and hash prefix', function (): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'document.pdf',
|
||||
sanitizedName: 'document.pdf',
|
||||
contents: FileStream::fromString('test content')
|
||||
);
|
||||
|
||||
$expectedYear = gmdate('Y');
|
||||
$expectedMonth = gmdate('m');
|
||||
$expectedDay = gmdate('d');
|
||||
$expectedHash = substr(hash('sha256', 'document.pdf'), 0, 2);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(StorageFilePath::class);
|
||||
|
||||
$pattern = sprintf(
|
||||
'#^%s/%s/%s/%s/document-[a-f0-9]{8}\.pdf$#',
|
||||
$expectedYear,
|
||||
$expectedMonth,
|
||||
$expectedDay,
|
||||
$expectedHash
|
||||
);
|
||||
expect($result->value)->toMatch($pattern);
|
||||
});
|
||||
|
||||
it('generates a path for a file without extension', function (): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'LICENSE',
|
||||
sanitizedName: 'LICENSE',
|
||||
contents: FileStream::fromString('MIT License')
|
||||
);
|
||||
|
||||
$expectedHash = substr(hash('sha256', 'LICENSE'), 0, 2);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(StorageFilePath::class);
|
||||
|
||||
$pattern = sprintf(
|
||||
'#^%s/%s/%s/%s/license-[a-f0-9]{8}$#',
|
||||
gmdate('Y'),
|
||||
gmdate('m'),
|
||||
gmdate('d'),
|
||||
$expectedHash
|
||||
);
|
||||
expect($result->value)->toMatch($pattern);
|
||||
});
|
||||
|
||||
it('generates unique paths for identical files generated consecutively', function (): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'photo.jpg',
|
||||
sanitizedName: 'photo.jpg',
|
||||
contents: FileStream::fromString('image bytes')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$path1 = $generator->generate($disk, $sanitizedFile);
|
||||
$path2 = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($path1->value)->not->toBe($path2->value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DateHashPathGenerator generation with directory', function (): void {
|
||||
it('prepends the given directory to the path', function (): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'avatar.png',
|
||||
sanitizedName: 'avatar.png',
|
||||
contents: FileStream::fromString('image data')
|
||||
);
|
||||
$directory = 'avatars/users';
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile, $directory);
|
||||
|
||||
// THEN
|
||||
$expectedHash = substr(hash('sha256', 'avatar.png'), 0, 2);
|
||||
$pattern = sprintf(
|
||||
'#^avatars/users/%s/%s/%s/%s/avatar-[a-f0-9]{8}\.png$#',
|
||||
gmdate('Y'),
|
||||
gmdate('m'),
|
||||
gmdate('d'),
|
||||
$expectedHash
|
||||
);
|
||||
expect($result->value)->toMatch($pattern);
|
||||
});
|
||||
|
||||
it('trims leading and trailing slashes from the directory', function (string $directory): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'file.txt',
|
||||
sanitizedName: 'file.txt',
|
||||
contents: FileStream::fromString('txt')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile, $directory);
|
||||
|
||||
// THEN
|
||||
expect($result->value)->toStartWith('uploads/archive/');
|
||||
})->with([
|
||||
'leading slash' => '/uploads/archive',
|
||||
'trailing slash' => 'uploads/archive/',
|
||||
'both slashes' => '/uploads/archive/',
|
||||
'multiple outer slashes' => '///uploads/archive///',
|
||||
]);
|
||||
|
||||
it('ignores empty or slash-only directory', function (string $directory): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'report.pdf',
|
||||
sanitizedName: 'report.pdf',
|
||||
contents: FileStream::fromString('pdf')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile, $directory);
|
||||
|
||||
// THEN
|
||||
expect($result->value)->toStartWith(gmdate('Y') . '/');
|
||||
})->with([
|
||||
'empty string' => '',
|
||||
'single slash' => '/',
|
||||
'multi slashes' => '///',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('DateHashPathGenerator filename slugging', function (): void {
|
||||
it('slugs filename containing underscores or mixed case', function (): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'My_Monthly_Report_2026.csv',
|
||||
sanitizedName: 'My_Monthly_Report_2026.csv',
|
||||
contents: FileStream::fromString('a,b,c')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($result->value)->toMatch('#/my-monthly-report-2026-[a-f0-9]{8}\.csv$#');
|
||||
});
|
||||
|
||||
it('falls back to "file" when filename produces an empty slug', function (): void {
|
||||
// GIVEN
|
||||
$generator = new DateHashPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: '.env.backup',
|
||||
sanitizedName: '.backup',
|
||||
contents: FileStream::fromString('KEY=VAL')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($result->value)->toMatch('#/file-[a-f0-9]{8}\.backup$#');
|
||||
});
|
||||
});
|
||||
140
tests/Unit/Lib/PathGenerators/UuidPathGeneratorTest.php
Normal file
140
tests/Unit/Lib/PathGenerators/UuidPathGeneratorTest.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Uwil\Storage\Tests\Unit\Lib\PathGenerators;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\Storage\Lib\PathGenerators\UuidPathGenerator;
|
||||
use Uwil\Storage\Lib\ValueObjects\DiskName;
|
||||
use Uwil\Storage\Lib\ValueObjects\FileStream;
|
||||
use Uwil\Storage\Lib\ValueObjects\SanitizedFile;
|
||||
use Uwil\Storage\Lib\ValueObjects\StorageFilePath;
|
||||
|
||||
covers(UuidPathGenerator::class);
|
||||
|
||||
describe('UuidPathGenerator generation without directory', function (): void {
|
||||
it('generates a storage file path containing a valid UUID and extension', function (): void {
|
||||
// GIVEN
|
||||
$generator = new UuidPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'picture.png',
|
||||
sanitizedName: 'picture.png',
|
||||
contents: FileStream::fromString('image bytes')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(StorageFilePath::class);
|
||||
|
||||
$filename = $result->value;
|
||||
expect($filename)->toMatch('#^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.png$#');
|
||||
|
||||
$uuidOnly = pathinfo($filename, PATHINFO_FILENAME);
|
||||
expect(Str::isUuid($uuidOnly))->toBeTrue();
|
||||
});
|
||||
|
||||
it('generates a storage file path without extension when file has no extension', function (): void {
|
||||
// GIVEN
|
||||
$generator = new UuidPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'DOCKERFILE',
|
||||
sanitizedName: 'DOCKERFILE',
|
||||
contents: FileStream::fromString('FROM php:8.5')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(StorageFilePath::class)
|
||||
->and(Str::isUuid($result->value))->toBeTrue();
|
||||
});
|
||||
|
||||
it('generates unique paths on successive calls for the same file', function (): void {
|
||||
// GIVEN
|
||||
$generator = new UuidPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'data.json',
|
||||
sanitizedName: 'data.json',
|
||||
contents: FileStream::fromString('{}')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$path1 = $generator->generate($disk, $sanitizedFile);
|
||||
$path2 = $generator->generate($disk, $sanitizedFile);
|
||||
|
||||
// THEN
|
||||
expect($path1->value)->not->toBe($path2->value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UuidPathGenerator generation with directory', function (): void {
|
||||
it('prepends the given directory to the generated UUID filename', function (): void {
|
||||
// GIVEN
|
||||
$generator = new UuidPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'invoice.pdf',
|
||||
sanitizedName: 'invoice.pdf',
|
||||
contents: FileStream::fromString('PDF DATA')
|
||||
);
|
||||
$directory = 'billing/invoices';
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile, $directory);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(StorageFilePath::class)
|
||||
->and($result->value)->toStartWith('billing/invoices/')
|
||||
->and($result->value)->toMatch('#^billing/invoices/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.pdf$#');
|
||||
});
|
||||
|
||||
it('trims leading and trailing slashes from the directory', function (string $directory): void {
|
||||
// GIVEN
|
||||
$generator = new UuidPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'file.txt',
|
||||
sanitizedName: 'file.txt',
|
||||
contents: FileStream::fromString('text')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile, $directory);
|
||||
|
||||
// THEN
|
||||
expect($result->value)->toStartWith('custom/path/');
|
||||
})->with([
|
||||
'leading slash' => '/custom/path',
|
||||
'trailing slash' => 'custom/path/',
|
||||
'both slashes' => '/custom/path/',
|
||||
'multiple outer slashes' => '///custom/path///',
|
||||
]);
|
||||
|
||||
it('ignores empty or slash-only directory', function (string $directory): void {
|
||||
// GIVEN
|
||||
$generator = new UuidPathGenerator();
|
||||
$disk = new DiskName('local', 'local');
|
||||
$sanitizedFile = new SanitizedFile(
|
||||
originalName: 'file.txt',
|
||||
sanitizedName: 'file.txt',
|
||||
contents: FileStream::fromString('text')
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $generator->generate($disk, $sanitizedFile, $directory);
|
||||
|
||||
// THEN
|
||||
expect($result->value)->toMatch('#^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.txt$#');
|
||||
})->with([
|
||||
'empty string' => '',
|
||||
'single slash' => '/',
|
||||
'multi slashes' => '///',
|
||||
]);
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue