toBeInstanceOf(SanitizedFile::class) ->and($vo->originalName)->toBe($originalName) ->and($vo->sanitizedName)->toBe($sanitizedName) ->and($vo->contents)->toBe($stream) ->and($vo->mimeType)->toBe($mimeType) ->and($vo->size)->toBe($size); }); it('can instantiate with default optional parameters as null', function (): void { // GIVEN $stream = FileStream::fromString('another content'); $originalName = 'photo.png'; $sanitizedName = 'photo.png'; // WHEN $vo = new SanitizedFile( originalName: $originalName, sanitizedName: $sanitizedName, contents: $stream ); // THEN expect($vo)->toBeInstanceOf(SanitizedFile::class) ->and($vo->originalName)->toBe($originalName) ->and($vo->sanitizedName)->toBe($sanitizedName) ->and($vo->contents)->toBe($stream) ->and($vo->mimeType)->toBeNull() ->and($vo->size)->toBeNull(); }); }); describe('SanitizedFile originalName validation', function (): void { it('throws exception if originalName is empty or only whitespace', function (string $invalidName): void { // GIVEN $stream = FileStream::fromString('dummy'); $sanitizedName = 'valid-name.txt'; // WHEN $action = fn () => new SanitizedFile( originalName: $invalidName, sanitizedName: $sanitizedName, contents: $stream ); // THEN expect($action)->toThrow( UnexpectedStorageException::class, 'Original name cannot be empty.' ); })->with([ 'empty string' => '', 'spaces only' => ' ', 'tabs and newlines' => "\t \n \r", ]); }); describe('SanitizedFile sanitizedName validation', function (): void { it('throws exception if sanitizedName is empty or only whitespace', function (string $invalidName): void { // GIVEN $stream = FileStream::fromString('dummy'); $originalName = 'valid_original.txt'; // WHEN $action = fn () => new SanitizedFile( originalName: $originalName, sanitizedName: $invalidName, contents: $stream ); // THEN expect($action)->toThrow( UnexpectedStorageException::class, 'Sanitized name cannot be empty.' ); })->with([ 'empty string' => '', 'spaces only' => ' ', 'tabs and newlines' => "\t \n", ]); it('throws exception if sanitizedName contains invalid characters', function (string $invalidName): void { // GIVEN $stream = FileStream::fromString('dummy'); $originalName = 'original.txt'; // WHEN $action = fn () => new SanitizedFile( originalName: $originalName, sanitizedName: $invalidName, contents: $stream ); // THEN expect($action)->toThrow( UnexpectedStorageException::class, 'Sanitized name contains invalid characters.' ); })->with([ 'contains space' => 'file name.txt', 'contains slash' => 'dir/file.txt', 'contains backslash' => 'dir\\file.txt', 'contains question' => 'file?.txt', 'contains hash' => 'file#name.txt', 'contains at symbol' => 'file@name.txt', 'contains accented é' => 'été.pdf', 'contains parenthesis' => 'file(1).pdf', 'contains emoji' => 'file🔥.txt', ]); it('accepts valid sanitizedName containing allowed characters', function (string $validName): void { // GIVEN $stream = FileStream::fromString('dummy'); $originalName = 'original.txt'; // WHEN $vo = new SanitizedFile( originalName: $originalName, sanitizedName: $validName, contents: $stream ); // THEN expect($vo)->toBeInstanceOf(SanitizedFile::class) ->and($vo->sanitizedName)->toBe($validName); })->with([ 'lowercase alphanumeric' => 'abc123.txt', 'uppercase letters' => 'FILE_NAME.PDF', 'hyphens and dots' => 'my-custom-file.tar.gz', 'underscores' => 'report_final_2026.csv', ]); });