*/ public static function pathOnlyAccepted(): array { return [ ['/', 'root'], ['/foo', 'simple path'], ['/users?error=db', 'path with query string'], ['/sprints/1#tab=tasks', 'path with fragment'], ['/users?flash=demoted', 'flash query'], ['/sprints/import/abc-123', 'token-style segment'], ]; } #[DataProvider('pathOnlyAccepted')] public function testRedirectAcceptsPathOnlyLocations(string $location, string $label): void { $resp = Response::redirect($location); self::assertSame(302, $resp->status, $label); self::assertSame($location, $resp->headers['Location'], $label); } public function testRedirectAcceptsCustomStatus(): void { $resp = Response::redirect('/foo', 303); self::assertSame(303, $resp->status); } /** @return list */ public static function nonPathLocations(): array { return [ ['', 'empty string'], ['foo', 'relative path'], ['foo/bar', 'multi-segment relative'], ['?next=/x', 'query-only (relative)'], ['#anchor', 'fragment-only'], ['//evil.example.com/x', 'protocol-relative URL → off-origin redirect'], ['///evil.example.com/x', 'triple-slash variant'], ['https://evil.example.com/x', 'absolute https URL'], ['http://evil.example.com/', 'absolute http URL'], ['javascript:alert(1)', 'javascript: scheme'], ['data:text/html,