toBeInstanceOf(PicturePath::class) ->and($path->value)->toBe($pathValue); })->with([ 'simple path' => 'coupons/logo.png', 'nested directory' => 'uploads/2026/03/avatar.jpg', 'safe punctuation' => 'images/my-photo_(1).webp', 'windows backslash' => 'coupons\sub\logo.jpeg', ]); it('rejects an empty or whitespace-only path', function (string $pathValue): void { // WHEN $action = fn (): PicturePath => new PicturePath($pathValue); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path cannot be empty.' ); })->with([ 'empty' => '', 'spaces' => ' ', ]); it('rejects a path that exceeds the maximum allowed length', function (): void { // GIVEN $pathValue = str_repeat('a', 197) . '.jpg'; // 201 chars // WHEN $action = fn (): PicturePath => new PicturePath($pathValue); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path exceeds the maximum allowed length.' ); }); it('accepts a path of exactly the maximum length', function (): void { // GIVEN $pathValue = str_repeat('a', 196) . '.jpg'; // 200 chars // WHEN $path = new PicturePath($pathValue); // THEN expect($path)->toBeInstanceOf(PicturePath::class) ->and(mb_strlen($path->value))->toBe(200); }); it('rejects null byte characters', function (): void { // WHEN $action = fn (): PicturePath => new PicturePath("coupons/logo\0.png"); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path contains invalid null byte.' ); }); it('rejects invalid, invisible, or forbidden filesystem characters', function (string $pathValue): void { // WHEN $action = fn (): PicturePath => new PicturePath($pathValue); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path contains invalid, non-printable, or dangerous characters.' ); })->with([ 'contains space' => 'coupons/my logo.png', 'contains newline' => "coupons/logo\n.png", 'contains asterisk' => 'coupons/*.png', 'contains question' => 'coupons/img?.png', 'contains pipe' => 'coupons/logo|1.png', 'contains colon' => 'coupons:logo.png', 'contains quotes' => 'coupons/"logo".png', ]); it('rejects paths starting with double slashes', function (): void { // WHEN $action = fn (): PicturePath => new PicturePath('//coupons/logo.png'); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path must not start with "//".' ); }); it('rejects drive paths and URIs', function (string $pathValue): void { // WHEN $action = fn (): PicturePath => new PicturePath($pathValue); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path must not be an absolute drive path or a URI.' ); })->with([ 'windows drive letter' => 'C:/images/logo.png', 'windows backslash' => 'D:\images\logo.png', 'http uri' => 'http://example.com/logo.png', 'https uri' => 'https://example.com/logo.png', 'custom scheme' => 's3://bucket/logo.png', ]); it('rejects directory traversal segments', function (string $pathValue): void { // WHEN $action = fn (): PicturePath => new PicturePath($pathValue); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path must not contain relative traversal segments ("." or "..").' ); })->with([ 'parent traversal' => '../logo.png', 'nested parent traversal' => 'coupons/../../secret.png', 'current directory segment' => 'coupons/./logo.png', ]); it('rejects an invalid or missing image extension', function (string $pathValue): void { // WHEN $action = fn (): PicturePath => new PicturePath($pathValue); // THEN expect($action) ->toThrow( \DomainException::class, 'Picture Path must end with one of: ' . implode(', ', PicturePath::ALLOWED_EXTENSIONS) ); })->with([ 'no extension' => 'coupons/image', 'forbidden extension' => 'coupons/image.gif', 'executable extension' => 'coupons/shell.php', 'trailing dot' => 'coupons/image.', ]); });