rule = new Url(); }); test('validates empty values as true (handled by Required rule)', function () { expect($this->rule->validate(null))->toBeTrue() ->and($this->rule->validate(''))->toBeTrue(); }); test('validates non-string values as false', function () { expect($this->rule->validate(123))->toBeFalse() ->and($this->rule->validate(['not', 'a', 'url']))->toBeFalse() ->and($this->rule->validate(new stdClass()))->toBeFalse(); }); test('validates valid HTTP URLs', function () { $validUrls = [ 'http://example.com', 'https://example.com', 'http://www.example.com', 'https://www.example.com', 'https://subdomain.example.com', 'https://example.com/path', 'https://example.com/path?query=value', 'https://example.com/path?query=value#fragment', 'http://localhost', 'https://192.168.1.1', 'https://127.0.0.1:8080', ]; foreach ($validUrls as $url) { expect($this->rule->validate($url))->toBeTrue("URL should be valid: $url"); } }); test('validates invalid URLs as false', function () { $invalidUrls = [ 'not-a-url', 'ftp://example.com', // Not HTTP/HTTPS 'javascript:alert("xss")', 'mailto:test@example.com', 'invalid://url', 'http://', 'https://', '://example.com', 'just-text-no-protocol', 'www.example.com', // Missing protocol (unless autoAddProtocol is true) ' ', // Whitespace only 'http://256.256.256.256', // Invalid IP 'https://example..com', // Double dots ]; foreach ($invalidUrls as $url) { expect($this->rule->validate($url))->toBeFalse("URL should be invalid: $url"); } }); test('requireSecure option only allows HTTPS URLs', function () { $secureRule = new Url(requireSecure: true); expect($secureRule->validate('https://example.com'))->toBeTrue() ->and($secureRule->validate('http://example.com'))->toBeFalse(); }); test('allowLocal option controls local URL validation', function () { $noLocalRule = new Url(allowLocal: false); expect($noLocalRule->validate('https://example.com'))->toBeTrue() ->and($noLocalRule->validate('http://localhost'))->toBeFalse() ->and($noLocalRule->validate('https://127.0.0.1'))->toBeFalse() ->and($noLocalRule->validate('https://192.168.1.1'))->toBeFalse() ->and($noLocalRule->validate('https://10.0.0.1'))->toBeFalse() ->and($noLocalRule->validate('https://172.16.0.1'))->toBeFalse(); }); test('autoAddProtocol option automatically adds https prefix', function () { $autoProtocolRule = new Url(autoAddProtocol: true); expect($autoProtocolRule->validate('example.com'))->toBeTrue() ->and($autoProtocolRule->validate('www.example.com'))->toBeTrue() ->and($autoProtocolRule->validate('subdomain.example.com/path'))->toBeTrue(); }); test('combined options work together', function () { $strictRule = new Url(requireSecure: true, allowLocal: false, autoAddProtocol: true); // Should work - gets https:// prefix and is secure and not local expect($strictRule->validate('example.com'))->toBeTrue(); // Should fail - local URL not allowed expect($strictRule->validate('localhost'))->toBeFalse(); // Should work - already secure expect($strictRule->validate('https://example.org'))->toBeTrue(); // Should fail - not secure (even with auto protocol, we can't change existing http://) expect($strictRule->validate('http://example.com'))->toBeFalse(); }); test('default error message', function () { $messages = $this->rule->getErrorMessages(); expect($messages)->toHaveCount(1) ->and($messages[0])->toBe('Bitte geben Sie eine gültige URL ein.'); }); test('custom error message', function () { $customRule = new Url(message: 'Diese URL ist nicht gültig!'); $messages = $customRule->getErrorMessages(); expect($messages)->toHaveCount(1) ->and($messages[0])->toBe('Diese URL ist nicht gültig!'); }); test('specific error messages for different configurations', function () { $secureRule = new Url(requireSecure: true); expect($secureRule->getErrorMessages()[0])->toContain('HTTPS-URL'); $noLocalRule = new Url(allowLocal: false); expect($noLocalRule->getErrorMessages()[0])->toContain('lokale URLs nicht erlaubt'); $strictRule = new Url(requireSecure: true, allowLocal: false); expect($strictRule->getErrorMessages()[0])->toContain('HTTPS-URL') ->and($strictRule->getErrorMessages()[0])->toContain('lokale URLs nicht erlaubt'); });