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 prefix', function (): void { it('prepends the configured prefix to the path', function (): void { // GIVEN $config = new StorageServiceConfig( defaultDisk: 'local', eligibleDisks: ['local'], laravelExistingDisks: [(object) ['disk' => 'local', 'driver' => 'local']], allowedExtensions: ['png'], prefix: 'avatars/users' ); $generator = new DateHashPathGenerator($config); $disk = new DiskName('local', 'local'); $sanitizedFile = new SanitizedFile( originalName: 'avatar.png', sanitizedName: 'avatar.png', contents: FileStream::fromString('image data') ); // WHEN $result = $generator->generate($disk, $sanitizedFile); // 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); }); }); 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$#'); }); });