toString())->toBe('http://example.com'); }); it('creates from valid HTTPS URL', function () { $url = DestinationUrl::fromString('https://example.com'); expect($url->toString())->toBe('https://example.com'); }); it('creates from URL with path', function () { $url = DestinationUrl::fromString('https://example.com/path/to/page'); expect($url->toString())->toBe('https://example.com/path/to/page'); }); it('creates from URL with query parameters', function () { $url = DestinationUrl::fromString('https://example.com?param1=value1¶m2=value2'); expect($url->toString())->toBe('https://example.com?param1=value1¶m2=value2'); }); it('creates from URL with fragment', function () { $url = DestinationUrl::fromString('https://example.com#section'); expect($url->toString())->toBe('https://example.com#section'); }); it('throws exception for invalid URL format', function () { expect(fn() => DestinationUrl::fromString('not-a-url')) ->toThrow(\InvalidArgumentException::class, 'Invalid destination URL format'); }); it('throws exception for empty string', function () { expect(fn() => DestinationUrl::fromString('')) ->toThrow(\InvalidArgumentException::class, 'Invalid destination URL format'); }); it('throws exception for FTP protocol', function () { expect(fn() => DestinationUrl::fromString('ftp://example.com')) ->toThrow(\InvalidArgumentException::class, 'Destination URL must use HTTP or HTTPS protocol'); }); it('throws exception for file protocol', function () { expect(fn() => DestinationUrl::fromString('file:///path/to/file')) ->toThrow(\InvalidArgumentException::class, 'Destination URL must use HTTP or HTTPS protocol'); }); it('throws exception for javascript protocol', function () { // Note: filter_var may accept javascript: as valid URL format // but parse_url scheme check should reject it expect(fn() => DestinationUrl::fromString('javascript:alert(1)')) ->toThrow(\InvalidArgumentException::class); }); it('extracts host correctly', function () { $url = DestinationUrl::fromString('https://example.com/path'); expect($url->getHost())->toBe('example.com'); }); it('extracts host with subdomain', function () { $url = DestinationUrl::fromString('https://www.example.com'); expect($url->getHost())->toBe('www.example.com'); }); it('identifies HTTPS as secure', function () { $url = DestinationUrl::fromString('https://example.com'); expect($url->isSecure())->toBeTrue(); }); it('identifies HTTP as not secure', function () { $url = DestinationUrl::fromString('http://example.com'); expect($url->isSecure())->toBeFalse(); }); it('compares URLs correctly', function () { $url1 = DestinationUrl::fromString('https://example.com'); $url2 = DestinationUrl::fromString('https://example.com'); $url3 = DestinationUrl::fromString('https://different.com'); expect($url1->equals($url2))->toBeTrue(); expect($url1->equals($url3))->toBeFalse(); }); it('converts to string via toString', function () { $urlString = 'https://example.com/path'; $url = DestinationUrl::fromString($urlString); expect($url->toString())->toBe($urlString); }); it('converts to string via __toString', function () { $urlString = 'https://example.com/path'; $url = DestinationUrl::fromString($urlString); expect((string) $url)->toBe($urlString); }); it('handles complex URLs', function () { $complexUrl = 'https://user:pass@example.com:8080/path/to/resource?param=value#fragment'; $url = DestinationUrl::fromString($complexUrl); expect($url->toString())->toBe($complexUrl); expect($url->getHost())->toBe('example.com'); expect($url->isSecure())->toBeTrue(); }); });