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.
199 lines
6.7 KiB
PHP
199 lines
6.7 KiB
PHP
<?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$#');
|
|
});
|
|
});
|