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