diff --git a/src/http/Request.php b/src/http/Request.php index da6767e0..29ad90e2 100644 --- a/src/http/Request.php +++ b/src/http/Request.php @@ -524,6 +524,32 @@ public function getScriptUrl(): string return parent::getScriptUrl(); } + /** + * Retrieves the server name for the current request, supporting PSR-7 and Yii2 fallback. + * + * Returns the server name as determined by the PSR-7 adapter if present. + * + * If no adapter is set, falls back to the parent implementation. + * + * This method enables seamless access to the server name in both PSR-7 and Yii2 environments, supporting + * interoperability with modern HTTP stacks and legacy workflows. + * + * @return string|null Server name for the current request, or null if not available. + * + * Usage example: + * ```php + * $serverName = $request->getServerName(); + * ``` + */ + public function getServerName(): string|null + { + if ($this->adapter !== null) { + return $this->getServerParam('SERVER_NAME'); + } + + return parent::getServerName(); + } + /** * Retrieves a server parameter value as a string or `null`. * diff --git a/tests/TestCase.php b/tests/TestCase.php index 18dc0df0..da797c99 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -20,6 +20,11 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase { + /** + * A secret key used for cookie validation in tests. + */ + protected const COOKIE_VALIDATION_KEY = 'wefJDF8sfdsfSDefwqdxj9oq'; + /** * @phpstan-var array */ @@ -185,7 +190,7 @@ protected function webApplication(array $config = []): void ], 'components' => [ 'request' => [ - 'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq', + 'cookieValidationKey' => self::COOKIE_VALIDATION_KEY, 'isConsoleRequest' => false, 'scriptFile' => __DIR__ . '/index.php', 'scriptUrl' => '/index.php', diff --git a/tests/adapter/ResponseAdapterTest.php b/tests/adapter/ResponseAdapterTest.php index 8d0ce040..329c0a80 100644 --- a/tests/adapter/ResponseAdapterTest.php +++ b/tests/adapter/ResponseAdapterTest.php @@ -535,7 +535,6 @@ public function testFormatCookieWithDateTimeImmutableExpire(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', ], ], ], @@ -798,7 +797,6 @@ public function testFormatCookieWithExpireAtCurrentTime(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', ], ], ], @@ -908,7 +906,7 @@ public function testFormatCookieWithExpireSetToOne(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', + 'cookieValidationKey' => self::COOKIE_VALIDATION_KEY, ], ], ], @@ -1078,7 +1076,6 @@ public function testFormatCookieWithStringExpire(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', ], ], ], @@ -1154,7 +1151,6 @@ public function testFormatCookieWithStringExpireOne(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', ], ], ], @@ -1212,7 +1208,6 @@ public function testFormatCookieWithValidationDisabled(): void 'components' => [ 'request' => [ 'enableCookieValidation' => false, - 'cookieValidationKey' => 'some-key', ], ], ], @@ -1323,7 +1318,6 @@ public function testFormatCookieWithValidationEnabled(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', ], ], ], @@ -1383,7 +1377,6 @@ public function testFormatCookieWithValidationEnabledFutureExpiration(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', ], ], ], @@ -1573,7 +1566,6 @@ public function testHashCookieValueIncludingName(): void 'components' => [ 'request' => [ 'enableCookieValidation' => true, - 'cookieValidationKey' => 'test-validation-key-32-characters', ], ], ], diff --git a/tests/adapter/ServerRequestAdapterTest.php b/tests/adapter/ServerRequestAdapterTest.php index d2d76e0b..43e63514 100644 --- a/tests/adapter/ServerRequestAdapterTest.php +++ b/tests/adapter/ServerRequestAdapterTest.php @@ -18,6 +18,8 @@ use function dirname; use function filesize; +use function is_array; +use function stream_get_meta_data; #[Group('http')] final class ServerRequestAdapterTest extends TestCase @@ -34,11 +36,10 @@ public function testGetCsrfTokenFromHeaderUsesAdapterWhenAdapterIsNotNull(): voi $request->setPsr7Request( FactoryHelper::createRequest('POST', '/test', [$csrfHeaderName => $expectedToken]), ); - $result = $request->getCsrfTokenFromHeader(); self::assertSame( $expectedToken, - $result, + $request->getCsrfTokenFromHeader(), "Should return CSRF token from adapter headers when adapter is not 'null'", ); } @@ -66,17 +67,17 @@ public function testIndependentRequestsWithDifferentRemoteHosts(): void self::assertSame( $host1, $result1, - 'First request instance should return its own remote host value.', + "First request instance should return its own 'REMOTE_HOST' value.", ); self::assertSame( $host2, $result2, - 'Second request instance should return its own remote host value.', + "Second request instance should return its own 'REMOTE_HOST' value.", ); self::assertNotSame( $result1, $result2, - 'Different request instances should maintain separate remote host values.', + "Different request instances should maintain separate 'REMOTE_HOST' values.", ); } @@ -95,35 +96,34 @@ public function testResetCookieCollectionAfterReset(): void $request->cookieValidationKey = 'test-validation-key-32-characters'; $request->setPsr7Request($psr7Request); - $cookies1 = $request->getCookies(); + $cookies1 = $request->getCookies(); $request->reset(); $newPsr7Request = FactoryHelper::createRequest('GET', '/test'); $newPsr7Request = $newPsr7Request->withCookieParams(['new_cookie' => 'new_value']); + $request->setPsr7Request($newPsr7Request); + $cookies2 = $request->getCookies(); self::assertNotSame( $cookies1, $cookies2, - "After reset, 'getCookies()' should return a new 'CookieCollection' instance.", + "After 'reset' method, 'getCookies()' should return a new CookieCollection instance.", ); self::assertTrue( $cookies2->has('new_cookie'), - "New 'CookieCollection' should contain 'new_cookie' after reset.", + "New CookieCollection should contain 'new_cookie' after 'reset' method.", ); self::assertSame( 'new_value', $cookies2->getValue('new_cookie'), - "New cookie 'new_cookie' should have the expected value after reset.", + "New cookie 'new_cookie' should have the expected value after 'reset' method.", ); } - /** - * @throws InvalidConfigException if the configuration is invalid or incomplete. - */ public function testResetRemoteHostAfterRequestReset(): void { $initialHost = 'initial.host.com'; @@ -140,24 +140,26 @@ public function testResetRemoteHostAfterRequestReset(): void self::assertSame( $initialHost, $result1, - 'Remote host should return the initial host value from the first PSR-7 request.', + "'REMOTE_HOST' should return the initial host value from the first PSR-7 request.", ); $request->reset(); + $request->setPsr7Request( FactoryHelper::createRequest('POST', '/second', serverParams: ['REMOTE_HOST' => $newHost]), ); + $result2 = $request->getRemoteHost(); self::assertSame( $newHost, $result2, - 'Remote host should return the new host value after reset and setting a new PSR-7 request.', + "'REMOTE_HOST' should return the new host value after 'reset' method and setting a new PSR-7 request.", ); self::assertNotSame( $result1, $result2, - 'Remote host values should be different after reset with new PSR-7 request data.', + "'REMOTE_HOST' values should be different after 'reset' method with new PSR-7 request data.", ); } @@ -179,31 +181,32 @@ public function testReturnBodyParamsWhenPsr7RequestHasFormData(): void ], ), ); + $bodyParams = $request->getBodyParams(); self::assertIsArray( $bodyParams, - "Body parameters should be returned as an array when 'PSR-7' request contains form data.", + 'Body parameters should be returned as an array when PSR-7 request contains form data.', ); self::assertArrayHasKey( 'key1', $bodyParams, - "Body parameters should contain the key 'key1' when present in the 'PSR-7' request.", + "Body parameters should contain the key 'key1' when present in the PSR-7 request.", ); self::assertSame( 'value1', $bodyParams['key1'] ?? null, - "Body parameter 'key1' should have the expected value from the 'PSR-7' request.", + "Body parameter 'key1' should have the expected value from the PSR-7 request.", ); self::assertArrayHasKey( 'key2', $bodyParams, - "Body parameters should contain the key 'key2' when present in the 'PSR-7' request.", + "Body parameters should contain the key 'key2' when present in the PSR-7 request.", ); self::assertSame( 'value2', $bodyParams['key2'] ?? null, - "Body parameter 'key2' should have the expected value from the 'PSR-7' request.", + "Body parameter 'key2' should have the expected value from the PSR-7 request.", ); } @@ -226,6 +229,7 @@ public function testReturnBodyParamsWithMethodParamRemoved(): void ], ), ); + $bodyParams = $request->getBodyParams(); self::assertIsArray( @@ -302,34 +306,35 @@ public function testReturnCookieCollectionWhenCookiesPresent(): void $request->cookieValidationKey = 'test-validation-key-32-characters'; $request->setPsr7Request($psr7Request); + $cookies = $request->getCookies(); self::assertCount( 2, $cookies, - "'CookieCollection' should contain '2' cookies when empty cookies are filtered out.", + "CookieCollection should contain '2' cookies when empty cookies are filtered out.", ); self::assertTrue( $cookies->has('session_id'), - "'CookieCollection' should contain 'session_id' cookie.", + "CookieCollection should contain 'session_id' cookie.", ); self::assertSame( 'abc123', $cookies->getValue('session_id'), - "Cookie 'session_id' should have the expected value from the 'PSR-7' request.", + "Cookie 'session_id' should have the expected value from the PSR-7 request.", ); self::assertTrue( $cookies->has('theme'), - "'CookieCollection' should contain 'theme' cookie.", + "CookieCollection should contain 'theme' cookie.", ); self::assertSame( 'dark', $cookies->getValue('theme'), - "Cookie 'theme' should have the expected value from the 'PSR-7' request.", + "Cookie 'theme' should have the expected value from the PSR-7 request.", ); self::assertFalse( $cookies->has('empty_cookie'), - "'CookieCollection' should not contain empty cookies.", + 'CookieCollection should not contain empty cookies.', ); } @@ -346,12 +351,11 @@ public function testReturnCookieCollectionWhenNoCookiesPresent(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/test'), ); - $cookies = $request->getCookies(); self::assertCount( 0, - $cookies, - "'CookieCollection' should be empty when no cookies are present in the 'PSR-7' request.", + $request->getCookies(), + 'CookieCollection should be empty when no cookies are present in the PSR-7 request.', ); } @@ -375,16 +379,17 @@ public function testReturnCookieCollectionWithValidationDisabled(): void $request->cookieValidationKey = 'test-validation-key-32-characters'; $request->setPsr7Request($psr7Request); + $cookies = $request->getCookies(); self::assertCount( 2, $cookies, - "'CookieCollection' should contain all non-empty cookies when validation is disabled.", + 'CookieCollection should contain all non-empty cookies when validation is disabled.', ); self::assertTrue( $cookies->has('user_id'), - "'CookieCollection' should contain 'user_id' cookie when validation is disabled.", + "CookieCollection should contain 'user_id' cookie when validation is disabled.", ); self::assertSame( '42', @@ -393,7 +398,7 @@ public function testReturnCookieCollectionWithValidationDisabled(): void ); self::assertTrue( $cookies->has('preferences'), - "'CookieCollection' should contain 'preferences' cookie when validation is disabled.", + "CookieCollection should contain 'preferences' cookie when validation is disabled.", ); self::assertSame( 'compact', @@ -420,6 +425,7 @@ public function testReturnCookieWithCorrectNamePropertyWhenAdapterIsSet(): void $request->cookieValidationKey = 'test-validation-key-32-characters'; $request->setPsr7Request($psr7Request); + $cookies = $request->getCookies(); $cookie = $cookies[$cookieName] ?? null; @@ -463,12 +469,11 @@ public function testReturnCsrfTokenFromHeaderWhenAdapterIsSet(): void $request->setPsr7Request( FactoryHelper::createRequest('POST', '/test', ['X-CSRF-Token' => $csrfToken]), ); - $result = $request->getCsrfTokenFromHeader(); self::assertSame( $csrfToken, - $result, - "'CSRF' token from header should match the value provided in the 'PSR-7' request header 'X-CSRF-Token'.", + $request->getCsrfTokenFromHeader(), + "CSRF token from header should match the value provided in the PSR-7 request header 'X-CSRF-Token'.", ); } @@ -484,33 +489,14 @@ public function testReturnCsrfTokenFromHeaderWithCustomHeaderWhenAdapterIsSet(): $request->setPsr7Request( FactoryHelper::createRequest('PUT', '/api/resource', [$customHeaderName => $csrfToken]), ); - $result = $request->getCsrfTokenFromHeader(); self::assertSame( $csrfToken, - $result, - "'CSRF' token from header should match the value provided in the custom 'PSR-7' request header.", + $request->getCsrfTokenFromHeader(), + 'CSRF token from header should match the value provided in the custom PSR-7 request header.', ); } - public function testReturnEmptyCookieCollectionWhenValidationEnabledButNoValidationKey(): void - { - $psr7Request = FactoryHelper::createRequest('GET', '/test'); - - $psr7Request = $psr7Request->withCookieParams(['session_id' => 'abc123']); - - $request = new Request(); - - $request->enableCookieValidation = true; - $request->cookieValidationKey = ''; - - $this->expectException(InvalidConfigException::class); - $this->expectExceptionMessage(Message::COOKIE_VALIDATION_KEY_REQUIRED->getMessage()); - - $request->setPsr7Request($psr7Request); - $request->getCookies(); - } - /** * @throws InvalidConfigException if the configuration is invalid or incomplete. */ @@ -531,12 +517,11 @@ public function testReturnEmptyCookieCollectionWhenValidationEnabledWithInvalidC $request->cookieValidationKey = 'test-validation-key-32-characters'; $request->setPsr7Request($psr7Request); - $cookies = $request->getCookies(); self::assertCount( 0, - $cookies, - "'CookieCollection' should be empty when validation is enabled but cookies are invalid.", + $request->getCookies(), + 'CookieCollection should be empty when validation is enabled but cookies are invalid.', ); } @@ -547,11 +532,10 @@ public function testReturnEmptyQueryParamsWhenAdapterIsSet(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/products'), ); - $queryParams = $request->getQueryParams(); self::assertEmpty( - $queryParams, - "Query parameters should be empty when 'PSR-7' request has no query string.", + $request->getQueryParams(), + 'Query parameters should be empty when PSR-7 request has no query string.', ); } @@ -562,9 +546,11 @@ public function testReturnEmptyQueryStringWhenAdapterIsSetWithNoQuery(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/test'), ); - $result = $request->getQueryString(); - self::assertEmpty($result, 'Query string should be empty when no query parameters are present.'); + self::assertEmpty( + $request->getQueryString(), + 'Query string should be empty when no query parameters are present.', + ); } /** @@ -577,10 +563,9 @@ public function testReturnEmptyScriptUrlWhenAdapterIsSetInTraditionalModeWithout $request->setPsr7Request( FactoryHelper::createRequest('GET', '/test'), ); - $scriptUrl = $request->getScriptUrl(); self::assertEmpty( - $scriptUrl, + $request->getScriptUrl(), "Script URL should be empty when adapter is set in traditional mode without 'SCRIPT_NAME'.", ); } @@ -595,12 +580,10 @@ public function testReturnEmptyScriptUrlWhenAdapterIsSetInWorkerMode(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/test'), ); - $scriptUrl = $request->getScriptUrl(); - self::assertSame( - '', - $scriptUrl, - 'Script URL should be empty when adapter is set in worker mode (default).', + self::assertEmpty( + $request->getScriptUrl(), + "Script URL should be empty when adapter is set in 'worker' mode (default).", ); } @@ -624,6 +607,22 @@ public function testReturnEmptyServerParamsWhenAdapterIsSet(): void ); } + public function testReturnEmptyStringFromHeaderWhenCsrfHeaderPresentButEmpty(): void + { + $request = new Request(); + + $request->setPsr7Request( + FactoryHelper::createRequest('PATCH', '/api/update', ['X-CSRF-Token' => '']), + ); + + self::assertSame( + '', + $request->getCsrfTokenFromHeader(), + 'CSRF token from header should return empty string when CSRF header is present but empty in the PSR-7 ' . + 'request.', + ); + } + public function testReturnEmptyStringWhenRemoteHostIsEmptyStringInPsr7Request(): void { $request = new Request(); @@ -635,8 +634,8 @@ public function testReturnEmptyStringWhenRemoteHostIsEmptyStringInPsr7Request(): self::assertSame( '', $request->getRemoteHost(), - "Remote host should return an empty string when 'REMOTE_HOST' parameter is an empty string in " . - "PSR-7 'serverParams'.", + "Remote host should return an empty string when 'REMOTE_HOST' parameter is an empty string in PSR-7 " . + "'serverParams'.", ); } @@ -647,11 +646,10 @@ public function testReturnHttpMethodFromAdapterWhenAdapterIsSet(): void $request->setPsr7Request( FactoryHelper::createRequest('POST', '/test'), ); - $method = $request->getMethod(); self::assertSame( 'POST', - $method, + $request->getMethod(), 'HTTP method should be returned from adapter when adapter is set.', ); } @@ -671,11 +669,10 @@ public function testReturnHttpMethodWithBodyOverrideAndLowerCaseMethodsWhenAdapt ], ), ); - $method = $request->getMethod(); self::assertSame( 'PUT', - $method, + $request->getMethod(), 'HTTP method should be overridden by body parameter when adapter is set.', ); } @@ -695,11 +692,10 @@ public function testReturnHttpMethodWithBodyOverrideWhenAdapterIsSet(): void ], ), ); - $method = $request->getMethod(); self::assertSame( 'PUT', - $method, + $request->getMethod(), 'HTTP method should be overridden by body parameter when adapter is set.', ); } @@ -721,11 +717,10 @@ public function testReturnHttpMethodWithCustomMethodParamWhenAdapterIsSet(): voi ], ), ); - $method = $request->getMethod(); self::assertSame( 'PATCH', - $method, + $request->getMethod(), 'HTTP method should be overridden by custom method parameter when adapter is set.', ); } @@ -737,11 +732,10 @@ public function testReturnHttpMethodWithHeaderOverrideWhenAdapterIsSet(): void $request->setPsr7Request( FactoryHelper::createRequest('POST', '/test', ['X-Http-Method-Override' => 'DELETE']), ); - $method = $request->getMethod(); self::assertSame( 'DELETE', - $method, + $request->getMethod(), 'HTTP method should be overridden by header when adapter is set.', ); } @@ -753,11 +747,10 @@ public function testReturnHttpMethodWithoutOverrideWhenAdapterIsSet(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/test'), ); - $method = $request->getMethod(); self::assertSame( 'GET', - $method, + $request->getMethod(), 'HTTP method should return original method when no override is present and adapter is set.', ); } @@ -773,8 +766,8 @@ public function testReturnIPv4AddressFromPsr7RequestWhenRemoteHostIsIP(): void self::assertSame( '192.168.1.100', $request->getRemoteHost(), - "Remote host should correctly return IPv4 address from PSR-7 'serverParams' when 'REMOTE_HOST' contains " . - 'an IPv4 address.', + "'REMOTE_HOST' should correctly return 'IPv4' address from PSR-7 'serverParams' when 'REMOTE_HOST' " . + "contains an 'IPv4' address.", ); } @@ -791,8 +784,8 @@ public function testReturnIPv6AddressFromPsr7RequestWhenRemoteHostIsIPv6(): void self::assertSame( $expectedHost, $request->getRemoteHost(), - "Remote host should correctly return IPv6 address from PSR-7 'serverParams' when 'REMOTE_HOST' contains " . - 'an IPv6 address.', + "'REMOTE_HOST' should correctly return 'IPv6' address from PSR-7 'serverParams' when 'REMOTE_HOST' " . + "contains an 'IPv6' address.", ); } @@ -807,7 +800,7 @@ public function testReturnLocalhostFromPsr7RequestWhenRemoteHostIsLocalhost(): v self::assertSame( 'localhost', $request->getRemoteHost(), - "Remote host should correctly return 'localhost' from PSR-7 'serverParams' when 'REMOTE_HOST' is " . + "'REMOTE_HOST' should correctly return 'localhost' from PSR-7 'serverParams' when 'REMOTE_HOST' is " . "'localhost'.", ); } @@ -826,11 +819,11 @@ public function testReturnMultipleUploadedFilesWithDifferentStructures(): void self::assertNotFalse( $tmpFileSize1, - "File size for 'test1.txt' should not be 'false'.", + "'filesize' for 'test1.txt' should not be 'false'.", ); self::assertNotFalse( $tmpFileSize2, - "File size for 'test2.php' should not be 'false'.", + "'filesize' for 'test2.php' should not be 'false'.", ); $uploadedFiles = [ @@ -881,6 +874,7 @@ public function testReturnMultipleUploadedFilesWithDifferentStructures(): void $request->setPsr7Request( FactoryHelper::createRequest('POST', '/upload')->withUploadedFiles($uploadedFiles), ); + $convertedFiles = $request->getUploadedFiles(); self::assertCount( @@ -969,6 +963,7 @@ public function testReturnMultipleValidatedCookiesWhenValidationEnabledWithMulti $request->cookieValidationKey = $validationKey; $request->setPsr7Request($psr7Request); + $cookieCollection = $request->getCookies(); self::assertCount( @@ -1025,45 +1020,88 @@ public function testReturnNewCookieCollectionInstanceOnEachCall(): void $request->cookieValidationKey = 'test-validation-key-32-characters'; $request->setPsr7Request($psr7Request); + $cookies1 = $request->getCookies(); $cookies2 = $request->getCookies(); self::assertNotSame( $cookies1, $cookies2, - "Each call to 'getCookies()' should return a new 'CookieCollection' instance, not a cached one.", + "Each call to 'getCookies()' should return a new CookieCollection instance, not a cached one.", + ); + } + + public function testReturnNullFromHeaderWhenCsrfHeaderNotPresentAndAdapterIsSet(): void + { + $request = new Request(); + + $request->setPsr7Request( + FactoryHelper::createRequest('DELETE', '/api/resource'), + ); + + self::assertNull( + $request->getCsrfTokenFromHeader(), + "CSRF token from header should return 'null' when no CSRF header is present in the PSR-7 request.", ); } - public function testReturnNullFromHeaderWhenCsrfHeaderEmptyAndAdapterIsSet(): void + public function testReturnNullWhenPsr7RequestServerNameIsEmptyArray(): void { $request = new Request(); $request->setPsr7Request( - FactoryHelper::createRequest('PATCH', '/api/update', ['X-CSRF-Token' => '']), + FactoryHelper::createRequest('GET', '/test', serverParams: ['SERVER_NAME' => []]), ); - $result = $request->getCsrfTokenFromHeader(); - self::assertSame( - '', - $result, - "'CSRF' token from header should return empty string when 'CSRF' header is present but empty in the " . - "'PSR-7' request.", + self::assertNull( + $request->getServerName(), + "'SERVER_NAME' should return 'null' from PSR-7 'serverParams' when adapter is set but 'SERVER_NAME' is " . + 'an empty array.', ); } - public function testReturnNullFromHeaderWhenCsrfHeaderNotPresentAndAdapterIsSet(): void + public function testReturnNullWhenPsr7RequestServerNameIsNotPresent(): void { $request = new Request(); $request->setPsr7Request( - FactoryHelper::createRequest('DELETE', '/api/resource'), + FactoryHelper::createRequest('GET', '/test', serverParams: ['HTTP_HOST' => 'example.com']), ); - $result = $request->getCsrfTokenFromHeader(); self::assertNull( - $result, - "'CSRF' token from header should return 'null' when no 'CSRF' header is present in the 'PSR-7' request.", + $request->getServerName(), + "'SERVER_NAME' should return 'null' from PSR-7 'serverParams' when adapter is set but 'SERVER_NAME' is " . + 'not present.', + ); + } + + public function testReturnNullWhenPsr7RequestServerNameIsNotString(): void + { + $request = new Request(); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test', serverParams: ['SERVER_NAME' => 12345]), + ); + + self::assertNull( + $request->getServerName(), + "'SERVER_NAME' should return 'null' from PSR-7 'serverParams' when adapter is set but 'SERVER_NAME' is " . + 'not a string.', + ); + } + + public function testReturnNullWhenPsr7RequestServerNameIsNull(): void + { + $request = new Request(); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test', serverParams: ['SERVER_NAME' => null]), + ); + + self::assertNull( + $request->getServerName(), + "'SERVER_NAME' should return 'null' from PSR-7 'serverParams' when adapter is set but 'SERVER_NAME' is " . + "'null'.", ); } @@ -1077,7 +1115,7 @@ public function testReturnNullWhenRemoteHostIsNotStringInPsr7Request(): void self::assertNull( $request->getRemoteHost(), - "Remote host should return 'null' when 'REMOTE_HOST' parameter is not a string in PSR-7 'serverParams'.", + "'REMOTE_HOST' should return 'null' when 'REMOTE_HOST' parameter is not a string in PSR-7 'serverParams'.", ); } @@ -1091,7 +1129,7 @@ public function testReturnNullWhenRemoteHostNotPresentInPsr7Request(): void self::assertNull( $request->getRemoteHost(), - "Remote host should return 'null' when 'REMOTE_HOST' parameter is not present in PSR-7 'serverParams'.", + "'REMOTE_HOST' should return 'null' when 'REMOTE_HOST' parameter is not present in PSR-7 'serverParams'.", ); } @@ -1115,11 +1153,10 @@ public function testReturnParentCsrfTokenFromHeaderWhenAdapterIsNull(): void // ensure adapter is `null` (default state) $request->reset(); - $result = $request->getCsrfTokenFromHeader(); self::assertNull( - $result, - "'CSRF' token from header should return parent implementation result when adapter is 'null'.", + $request->getCsrfTokenFromHeader(), + "CSRF token from header should return parent implementation result when adapter is 'null'.", ); } @@ -1135,30 +1172,7 @@ public function testReturnParentGetParsedBodyWhenAdapterIsNull(): void self::assertEmpty( $request->getParsedBody(), - "Parsed body should return empty array when 'PSR-7' request has no parsed body and adapter is 'null'.", - ); - } - - /** - * @throws InvalidConfigException if the configuration is invalid or incomplete. - */ - public function testReturnParentGetScriptUrlWhenAdapterIsNull(): void - { - $request = new Request(); - - // ensure adapter is `null` (default state) - $request->reset(); - - $_SERVER['SCRIPT_NAME'] = '/test.php'; - $_SERVER['SCRIPT_FILENAME'] = '/path/to/test.php'; - - $scriptUrl = $request->getScriptUrl(); - - // verify the method executes without throwing exception when adapter is `null` - self::assertSame( - '/test.php', - $scriptUrl, - "'getScriptUrl()' should return 'SCRIPT_NAME' when adapter is 'null'.", + "Parsed body should return empty array when PSR-7 request has no parsed body and adapter is 'null'.", ); } @@ -1168,9 +1182,8 @@ public function testReturnParentHttpMethodWhenAdapterIsNull(): void // ensure adapter is `null` (default state) $request->reset(); - $method = $request->getMethod(); - self::assertNotEmpty($method, "HTTP method should not be empty when adapter is 'null'."); + self::assertNotEmpty($request->getMethod(), "HTTP method should not be empty when adapter is 'null'."); } public function testReturnParentQueryParamsWhenAdapterIsNull(): void @@ -1179,20 +1192,20 @@ public function testReturnParentQueryParamsWhenAdapterIsNull(): void // ensure adapter is `null` (default state) $request->reset(); - $queryParams = $request->getQueryParams(); - self::assertEmpty($queryParams, "Query parameters should be empty when 'PSR-7' request has no query string."); + self::assertEmpty( + $request->getQueryParams(), + 'Query parameters should be empty when PSR-7 request has no query string.', + ); } public function testReturnParentQueryStringWhenAdapterIsNull(): void { $request = new Request(); - $queryString = $request->getQueryString(); - self::assertEmpty( - $queryString, - "Query string should be empty when 'PSR-7' request has no query string and adapter is 'null'.", + $request->getQueryString(), + "Query string should be empty when PSR-7 request has no query string and adapter is 'null'.", ); } @@ -1202,37 +1215,13 @@ public function testReturnParentRawBodyWhenAdapterIsNull(): void // ensure adapter is `null` (default state) $request->reset(); - $result = $request->getRawBody(); self::assertEmpty( - $result, - "Raw body should return empty string when 'PSR-7' request has no body content and adapter is 'null'.", + $request->getRawBody(), + "Raw body should return empty string when PSR-7 request has no body content and adapter is 'null'.", ); } - /** - * @throws InvalidConfigException if the configuration is invalid or incomplete. - */ - public function testReturnParentUrlWhenAdapterIsNull(): void - { - $_SERVER['REQUEST_URI'] = '/legacy/path?param=value'; - - $request = new Request(); - - // ensure adapter is `null` (default state) - $request->reset(); - - $url = $request->getUrl(); - - self::assertSame( - '/legacy/path?param=value', - $url, - "URL should return parent implementation result when adapter is 'null'.", - ); - - unset($_SERVER['REQUEST_URI']); - } - /** * @throws InvalidConfigException if the configuration is invalid or incomplete. */ @@ -1254,16 +1243,17 @@ public function testReturnParsedBodyArrayWhenAdapterIsSet(): void $parsedBodyData, ), ); + $result = $request->getParsedBody(); self::assertIsArray( $result, - "Parsed body should return an array when 'PSR-7' request contains array data.", + 'Parsed body should return an array when PSR-7 request contains array data.', ); self::assertSame( $parsedBodyData, $result, - "Parsed body should match the original data from 'PSR-7' request.", + 'Parsed body should match the original data from PSR-7 request.', ); self::assertArrayHasKey( 'name', @@ -1273,7 +1263,7 @@ public function testReturnParsedBodyArrayWhenAdapterIsSet(): void self::assertSame( 'John', $result['name'], - 'Name field should match the expected value.', + "'name' field should match the expected value.", ); } @@ -1287,9 +1277,11 @@ public function testReturnParsedBodyNullWhenAdapterIsSetWithNullBody(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/api/users'), ); - $result = $request->getParsedBody(); - self::assertNull($result, "Parsed body should return 'null' when 'PSR-7' request has no parsed body."); + self::assertNull( + $request->getParsedBody(), + "Parsed body should return 'null' when PSR-7 request has no parsed body.", + ); } /** @@ -1312,16 +1304,17 @@ public function testReturnParsedBodyObjectWhenAdapterIsSet(): void $parsedBodyObject, ), ); + $result = $request->getParsedBody(); self::assertIsObject( $result, - "Parsed body should return an 'object' when 'PSR-7' request contains 'object' data.", + 'Parsed body should return an object when PSR-7 request contains object data.', ); self::assertSame( $parsedBodyObject, $result, - "Parsed body 'object' should match the original 'object' from 'PSR-7' request.", + 'Parsed body object should match the original object from PSR-7 request.', ); self::assertSame( 'Test Article', @@ -1335,6 +1328,9 @@ public function testReturnParsedBodyObjectWhenAdapterIsSet(): void ); } + /** + * @throws InvalidConfigException + */ public function testReturnPsr7RequestInstanceWhenAdapterIsSet(): void { $request = new Request(); @@ -1346,7 +1342,7 @@ public function testReturnPsr7RequestInstanceWhenAdapterIsSet(): void self::assertInstanceOf( ServerRequestInterface::class, $request->getPsr7Request(), - "'getPsr7Request()' should return a '" . ServerRequestInterface::class . "' instance when the 'PSR-7' " . + "'getPsr7Request()' should return a '" . ServerRequestInterface::class . "' instance when the PSR-7 " . 'adapter is set.', ); } @@ -1358,37 +1354,38 @@ public function testReturnQueryParamsWhenAdapterIsSet(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/products?category=electronics&price=500&sort=desc'), ); + $queryParams = $request->getQueryParams(); self::assertArrayHasKey( 'category', $queryParams, - "Query parameters should contain the key 'category' when present in the 'PSR-7' request URI.", + "Query parameters should contain the key 'category' when present in the PSR-7 request URI.", ); self::assertSame( 'electronics', $queryParams['category'] ?? null, - "Query parameter 'category' should have the expected value from the 'PSR-7' request URI.", + "Query parameter 'category' should have the expected value from the PSR-7 request URI.", ); self::assertArrayHasKey( 'price', $queryParams, - "Query parameters should contain the key 'price' when present in the 'PSR-7' request URI.", + "Query parameters should contain the key 'price' when present in the PSR-7 request URI.", ); self::assertSame( '500', $queryParams['price'] ?? null, - "Query parameter 'price' should have the expected value from the 'PSR-7' request URI.", + "Query parameter 'price' should have the expected value from the PSR-7 request URI.", ); self::assertArrayHasKey( 'sort', $queryParams, - "Query parameters should contain the key 'sort' when present in the 'PSR-7' request URI.", + "Query parameters should contain the key 'sort' when present in the PSR-7 request URI.", ); self::assertSame( 'desc', $queryParams['sort'] ?? null, - "Query parameter 'sort' should have the expected value from the 'PSR-7' request URI.", + "Query parameter 'sort' should have the expected value from the PSR-7 request URI.", ); } @@ -1403,11 +1400,10 @@ public function testReturnQueryStringWhenAdapterIsSet(string $queryString, strin $request->setPsr7Request( FactoryHelper::createRequest('GET', "/test?{$queryString}"), ); - $result = $request->getQueryString(); self::assertSame( $expectedString, - $result, + $request->getQueryString(), "Query string should match the expected value for: '{$queryString}'.", ); } @@ -1425,12 +1421,11 @@ public function testReturnRawBodyFromAdapterWhenAdapterIsSet(): void $request->setPsr7Request( FactoryHelper::createRequest('POST', '/api/contact')->withBody($stream), ); - $result = $request->getRawBody(); self::assertSame( $bodyContent, - $result, - "Raw body should return the exact content from the 'PSR-7' request body when adapter is set.", + $request->getRawBody(), + 'Raw body should return the exact content from the PSR-7 request body when adapter is set.', ); } @@ -1441,46 +1436,10 @@ public function testReturnRawBodyWhenAdapterIsSetWithEmptyBody(): void $request->setPsr7Request( FactoryHelper::createRequest('GET', '/test'), ); - $result = $request->getRawBody(); self::assertEmpty( - $result, - "Raw body should return empty string when 'PSR-7' request has no body content.", - ); - } - - /** - * @throws InvalidConfigException if the configuration is invalid or incomplete. - */ - public function testReturnReadOnlyCookieCollectionWhenAdapterIsSet(): void - { - $psr7Request = FactoryHelper::createRequest('GET', '/test'); - - $psr7Request = $psr7Request->withCookieParams( - [ - 'another_cookie' => 'another_value', - 'test_cookie' => 'test_value', - ], - ); - - $request = new Request(); - - $request->enableCookieValidation = false; - $request->cookieValidationKey = 'test-validation-key-32-characters'; - - $request->setPsr7Request($psr7Request); - $cookies = $request->getCookies(); - - $this->expectException(InvalidCallException::class); - $this->expectExceptionMessage('The cookie collection is read only.'); - - $cookies->add( - new Cookie( - [ - 'name' => 'new_cookie', - 'value' => 'new_value', - ], - ), + $request->getRawBody(), + 'Raw body should return empty string when PSR-7 request has no body content.', ); } @@ -1495,7 +1454,7 @@ public function testReturnRemoteHostFromPsr7RequestWhenRemoteHostIsPresent(): vo self::assertSame( 'example.host.com', $request->getRemoteHost(), - "Remote host should match the value from PSR-7 'serverParams' when 'REMOTE_HOST' is present.", + "'REMOTE_HOST' should match the value from PSR-7 'serverParams' when 'REMOTE_HOST' is present.", ); } @@ -1511,11 +1470,9 @@ public function testReturnRemoteIPFromPsr7ServerParams(): void ), ); - $remoteIP = $request->getRemoteIP(); - self::assertSame( '192.168.1.100', - $remoteIP, + $request->getRemoteIP(), "'getRemoteIP()' should return the 'REMOTE_ADDR' value from PSR-7 'serverParams'.", ); } @@ -1534,11 +1491,9 @@ public function testReturnRemoteIPFromPsr7ServerParamsOverridesGlobalServer(): v ), ); - $remoteIP = $request->getRemoteIP(); - self::assertSame( '10.0.0.1', - $remoteIP, + $request->getRemoteIP(), "'getRemoteIP()' should return the 'REMOTE_ADDR' value from PSR-7 'serverParams', not from global " . '$_SERVER.', ); @@ -1554,23 +1509,32 @@ public function testReturnScriptNameWhenAdapterIsSetInTraditionalMode(): void $request = new Request(['workerMode' => false]); $request->setPsr7Request( - FactoryHelper::createRequest( - 'GET', - '/test', - [], - null, - ['SCRIPT_NAME' => $expectedScriptName], - ), + FactoryHelper::createRequest('GET', '/test', serverParams: ['SCRIPT_NAME' => $expectedScriptName]), ); - $scriptUrl = $request->getScriptUrl(); self::assertSame( $expectedScriptName, - $scriptUrl, + $request->getScriptUrl(), "Script URL should return 'SCRIPT_NAME' when adapter is set in traditional mode.", ); } + public function testReturnServerNameFromPsr7RequestWhenAdapterIsSetAndServerNamePresent(): void + { + $request = new Request(); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test', serverParams: ['SERVER_NAME' => 'example.server.com']), + ); + + self::assertSame( + 'example.server.com', + $request->getServerName(), + "'SERVER_NAME' should return 'example.server.com' from PSR-7 'serverParams' when adapter is set and " . + "'SERVER_NAME' is present as a string.", + ); + } + public function testReturnServerParamFromPsr7RequestWhenAdapterIsSet(): void { $request = new Request(); @@ -1614,8 +1578,7 @@ public function testReturnServerParamsFromPsr7RequestOverridesGlobalServer(): vo self::assertSame( '10.0.0.50', $serverParams['REMOTE_ADDR'] ?? null, - "Server parameter 'REMOTE_ADDR' should be taken from PSR-7 'serverParams', not from global " . - '$_SERVER.', + "Server parameter 'REMOTE_ADDR' should be taken from PSR-7 'serverParams', not from global \$_SERVER.", ); self::assertNull( $serverParams['REQUEST_TIME'] ?? null, @@ -1666,11 +1629,11 @@ public function testReturnUploadedFilesRecursivelyConvertsNestedArrays(): void self::assertNotFalse( $size1, - "File size for 'test1.txt' should not be 'false'.", + "'filesize' for 'test1.txt' should not be 'false'.", ); self::assertNotFalse( $size2, - "File size for 'test2.php' should not be 'false'.", + "'filesize' for 'test2.php' should not be 'false'.", ); $uploadedFile1 = FactoryHelper::createUploadedFile('test1.txt', 'text/plain', $file1, size: $size1); @@ -1688,6 +1651,7 @@ public function testReturnUploadedFilesRecursivelyConvertsNestedArrays(): void $psr7Request = FactoryHelper::createRequest('POST', '/upload')->withUploadedFiles($deepNestedFiles); $request = new Request(); + $request->setPsr7Request($psr7Request); $deepNestedUploadedFiles = $request->getUploadedFiles(); @@ -1772,11 +1736,11 @@ public function testReturnUploadedFilesWhenAdapterIsSet(): void self::assertNotFalse( $size1, - "File size for 'test1.txt' should not be 'false'.", + "'filesize' for 'test1.txt' should not be 'false'.", ); self::assertNotFalse( $size2, - "File size for 'test2.php' should not be 'false'.", + "'filesize' for 'test2.php' should not be 'false'.", ); $uploadedFile1 = FactoryHelper::createUploadedFile('test1.txt', 'text/plain', $file1, size: $size1); @@ -1793,6 +1757,7 @@ public function testReturnUploadedFilesWhenAdapterIsSet(): void $request = new Request(); $request->setPsr7Request($psr7Request); + $uploadedFiles = $request->getUploadedFiles(); $expectedNames = [ @@ -1877,6 +1842,7 @@ public function testReturnUploadedFileWithZeroSizeWhenPsr7FileSizeIsNull(): void $request = new Request(); $request->setPsr7Request($psr7Request); + $uploadedFiles = $request->getUploadedFiles(); self::assertArrayHasKey( @@ -1930,11 +1896,10 @@ public function testReturnUrlFromAdapterWhenAdapterIsSet(string $url, string $ex $request->setPsr7Request( FactoryHelper::createRequest('GET', $url), ); - $url = $request->getUrl(); self::assertSame( $expectedUrl, - $url, + $request->getUrl(), "URL should match the expected value for: {$url}.", ); } @@ -1967,16 +1932,17 @@ public function testReturnValidatedCookiesWhenValidationEnabledWithValidCookies( $request->cookieValidationKey = $validationKey; $request->setPsr7Request($psr7Request); + $cookies = $request->getCookies(); self::assertCount( 1, $cookies, - "'CookieCollection' should contain only the valid signed cookie when validation is enabled.", + 'CookieCollection should contain only the valid signed cookie when validation is enabled.', ); self::assertTrue( $cookies->has($cookieName), - "'CookieCollection' should contain the valid signed cookie '{$cookieName}'.", + "CookieCollection should contain the valid signed cookie '{$cookieName}'.", ); self::assertSame( $cookieValue, @@ -1985,7 +1951,7 @@ public function testReturnValidatedCookiesWhenValidationEnabledWithValidCookies( ); self::assertFalse( $cookies->has('invalid_cookie'), - "'CookieCollection' should not contain invalid cookies when validation is enabled.", + 'CookieCollection should not contain invalid cookies when validation is enabled.', ); } @@ -2011,6 +1977,7 @@ public function testReturnValidatedCookieWithCorrectNamePropertyWhenValidationEn $request->cookieValidationKey = $validationKey; $request->setPsr7Request($psr7Request); + $cookies = $request->getCookies(); $cookie = null; @@ -2066,7 +2033,7 @@ public function testReturnValidHostnameFromPsr7RequestWithDomainName(): void self::assertSame( 'api.example-service.com', $request->getRemoteHost(), - "Remote host should correctly return domain name from PSR-7 'serverParams' when 'REMOTE_HOST' contains " . + "'REMOTE_HOST' should correctly return domain name from PSR-7 'serverParams' when 'REMOTE_HOST' contains " . 'a valid hostname.', ); } @@ -2111,50 +2078,174 @@ public function testSecureHeadersAreFilteredWhenNotFromTrustedHost(): void $headerCollection = $request->getHeaders(); - self::assertSame( - null, + self::assertNull( $headerCollection->get('X-Forwarded-For'), - "'X-Forwarded-For' header should be filtered out when request is not from trusted host.", + "'X-Forwarded-For' header should be filtered out when request is not from 'trustedHosts'.", ); - self::assertSame( - null, + self::assertNull( $headerCollection->get('X-Forwarded-Proto'), - "'X-Forwarded-Proto' header should be filtered out when request is not from trusted host.", + "'X-Forwarded-Proto' header should be filtered out when request is not from 'trustedHosts'.", ); - self::assertSame( - null, + self::assertNull( $headerCollection->get('X-Forwarded-Host'), - 'X-Forwarded-Host header should be filtered out when request is not from trusted host.', + "'X-Forwarded-Host' header should be filtered out when request is not from 'trustedHosts'.", ); - self::assertSame( - null, + self::assertNull( $headerCollection->get('X-Forwarded-Port'), - "'X-Forwarded-Port' header should be filtered out when request is not from trusted host.", + "'X-Forwarded-Port' header should be filtered out when request is not from 'trustedHosts'.", ); - self::assertSame( - null, + self::assertNull( $headerCollection->get('Front-End-Https'), - "'Front-End-Https' header should be filtered out when request is not from trusted host.", + "'Front-End-Https' header should be filtered out when request is not from 'trustedHosts'.", ); - self::assertSame( - null, + self::assertNull( $headerCollection->get('X-Real-IP'), - "'X-Real-IP' header should be filtered out when request is not from trusted host.", + "'X-Real-IP' header should be filtered out when request is not from 'trustedHosts'.", ); self::assertSame( 'application/json', $headerCollection->get('Content-Type'), - 'Content-Type header should NOT be filtered as it is not a secure header.', + "'Content-Type' header should NOT be filtered as it is not a 'secureHeaders'.", ); self::assertSame( 'Bearer token123', $headerCollection->get('Authorization'), - 'Authorization header should NOT be filtered as it is not a secure header.', + "'Authorization' header should NOT be filtered as it is not a 'secureHeaders'.", ); self::assertSame( 'Test-Agent/1.0', $headerCollection->get('User-Agent'), - 'User-Agent header should NOT be filtered as it is not a secure header.', + "'User-Agent' header should NOT be filtered as it is not a secure header.", + ); + } + + public function testServerNameAfterRequestReset(): void + { + $initialServerName = 'initial.server.com'; + $newServerName = 'new.server.com'; + + $request = new Request(); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test', serverParams: ['SERVER_NAME' => $initialServerName]), + ); + + $result1 = $request->getServerName(); + + self::assertSame( + $initialServerName, + $result1, + "'SERVER_NAME' should return '{$initialServerName}' from initial PSR-7 request.", + ); + + $request->reset(); + + $request->setPsr7Request( + FactoryHelper::createRequest('GET', '/test', serverParams: ['SERVER_NAME' => $newServerName]), + ); + + $result2 = $request->getServerName(); + + self::assertSame( + $newServerName, + $result2, + "'SERVER_NAME' should return '{$newServerName}' from new PSR-7 request after 'reset' method.", + ); + self::assertNotSame( + $result1, + $result2, + "'SERVER_NAME' should change after request 'reset' method and new PSR-7 request assignment.", + ); + } + + public function testServerNameIndependentRequestsWithDifferentServerNames(): void + { + $serverName1 = 'server1.example.com'; + $serverName2 = 'server2.example.org'; + + $request1 = new Request(); + + $request1->setPsr7Request( + FactoryHelper::createRequest('GET', '/test1', serverParams: ['SERVER_NAME' => $serverName1]), + ); + + $request2 = new Request(); + + $request2->setPsr7Request( + FactoryHelper::createRequest('GET', '/test2', serverParams: ['SERVER_NAME' => $serverName2]), + ); + + $result1 = $request1->getServerName(); + $result2 = $request2->getServerName(); + + self::assertSame( + $serverName1, + $result1, + "First request should return '{$serverName1}' from its PSR-7 'serverParams'.", + ); + self::assertSame( + $serverName2, + $result2, + "Second request should return '{$serverName2}' from its PSR-7 'serverParams'.", + ); + self::assertNotSame( + $result1, + $result2, + 'Independent request instances should return different server names when configured with different values.', + ); + } + + /** + * @throws InvalidConfigException if the configuration is invalid or incomplete. + */ + public function testThrowInvalidCallExceptionWhenReturnReadOnlyCookieCollectionWhenAdapterIsSet(): void + { + $psr7Request = FactoryHelper::createRequest('GET', '/test'); + + $psr7Request = $psr7Request->withCookieParams( + [ + 'another_cookie' => 'another_value', + 'test_cookie' => 'test_value', + ], + ); + + $request = new Request(); + + $request->enableCookieValidation = false; + $request->cookieValidationKey = 'test-validation-key-32-characters'; + + $request->setPsr7Request($psr7Request); + + $cookies = $request->getCookies(); + + $this->expectException(InvalidCallException::class); + $this->expectExceptionMessage('The cookie collection is read only.'); + + $cookies->add( + new Cookie( + [ + 'name' => 'new_cookie', + 'value' => 'new_value', + ], + ), ); } + + public function testThrowInvalidConfigExceptionWhenValidationEnabledButNoValidationKey(): void + { + $psr7Request = FactoryHelper::createRequest('GET', '/test'); + + $psr7Request = $psr7Request->withCookieParams(['session_id' => 'abc123']); + + $request = new Request(); + + $request->enableCookieValidation = true; + $request->cookieValidationKey = ''; + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage(Message::COOKIE_VALIDATION_KEY_REQUIRED->getMessage()); + + $request->setPsr7Request($psr7Request); + $request->getCookies(); + } } diff --git a/tests/http/RequestTest.php b/tests/http/RequestTest.php index 18f318c3..fa23307a 100644 --- a/tests/http/RequestTest.php +++ b/tests/http/RequestTest.php @@ -21,13 +21,6 @@ #[Group('http')] final class RequestTest extends TestCase { - protected function tearDown(): void - { - $this->closeApplication(); - - parent::tearDown(); - } - /** * @phpstan-param string[] $trustedHosts */ @@ -88,14 +81,14 @@ public function testCsrfHeaderValidation(): void self::assertFalse( $request->validateCsrfToken(), - "'CSRF' token validation should fail when the 'CSRF' header is missing for unsafe 'HTTP' methods.", + 'CSRF token validation should fail when the CSRF header is missing for unsafe HTTP methods.', ); $request->headers->add(Request::CSRF_HEADER, ''); self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass when the 'CSRF' header is present for unsafe 'HTTP' methods.", + 'CSRF token validation should pass when the CSRF header is present for unsafe HTTP methods.', ); } @@ -105,9 +98,11 @@ public function testCsrfHeaderValidation(): void self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass for safe 'HTTP' methods regardless of 'CSRF' header.", + 'CSRF token validation should pass for safe HTTP methods regardless of CSRF header.', ); } + + $this->closeApplication(); } /** @@ -125,13 +120,15 @@ public function testCsrfTokenContainsASCIIOnly(): void self::assertNotNull( $token, - "'CSRF' token should not be null after generation.", + 'CSRF token should not be null after generation.', ); self::assertMatchesRegularExpression( '~[-_=a-z0-9]~i', $token, - "'CSRF' token should only contain ASCII characters ('a-z', '0-9', '-', '_', '=').", + "CSRF token should only contain ASCII characters ('a-z', '0-9', '-', '_', '=').", ); + + $this->closeApplication(); } /** @@ -153,8 +150,8 @@ public function testCsrfTokenHeader(): void self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass for safe 'HTTP' methods ('GET', 'HEAD', 'OPTIONS') even " . - 'if no token is provided.', + "CSRF token validation should pass for safe HTTP methods ('GET', 'HEAD', 'OPTIONS') even if no token " . + 'is provided.', ); } @@ -167,22 +164,24 @@ public function testCsrfTokenHeader(): void self::assertFalse( $request->validateCsrfToken(), - "'CSRF' token validation should fail for unsafe 'HTTP' methods ('POST', 'PUT', 'DELETE') if no " . - 'token is provided.', + "CSRF token validation should fail for unsafe HTTP methods ('POST', 'PUT', 'DELETE') if no token is " . + 'provided.', ); self::assertNotNull( $token, - "'CSRF' token should not be 'null' after generation.", + "CSRF token should not be 'null' after generation.", ); $request->headers->add(Request::CSRF_HEADER, $token); self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass for unsafe 'HTTP' methods ('POST', 'PUT', 'DELETE') if a " . - 'valid token is provided in the header.', + "CSRF token validation should pass for unsafe HTTP methods ('POST', 'PUT', 'DELETE') if a valid " . + 'token is provided in the header.', ); } + + $this->closeApplication(); } /** @@ -204,8 +203,8 @@ public function testCsrfTokenPost(): void self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass for safe 'HTTP' methods ('GET', 'HEAD', 'OPTIONS') even if no " . - "token is provided in 'POST' params.", + "CSRF token validation should pass for safe HTTP methods ('GET', 'HEAD', 'OPTIONS') even if no token " . + "is provided in 'POST' params.", ); } @@ -217,18 +216,20 @@ public function testCsrfTokenPost(): void self::assertFalse( $request->validateCsrfToken(), - "'CSRF' token validation should fail for unsafe 'HTTP' methods ('POST', 'PUT', 'DELETE') if no " . - "token is provided in 'POST' params.", + "CSRF token validation should fail for unsafe HTTP methods ('POST', 'PUT', 'DELETE') if no token is " . + "provided in 'POST' params.", ); $request->setBodyParams([$request->csrfParam => $token]); self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass for unsafe 'HTTP' methods ('POST', 'PUT', 'DELETE') if a " . - "valid token is provided in 'POST' params.", + "CSRF token validation should pass for unsafe HTTP methods ('POST', 'PUT', 'DELETE') if a valid " . + "token is provided in 'POST' params.", ); } + + $this->closeApplication(); } public function testCsrfTokenValidation(): void @@ -246,15 +247,15 @@ public function testCsrfTokenValidation(): void self::assertTrue( $request->validateCsrfToken($token), - "'CSRF' token validation should pass for any value if 'CSRF' validation is disabled.", + 'CSRF token validation should pass for any value if CSRF validation is disabled.', ); self::assertTrue( $request->validateCsrfToken($token . 'a'), - "'CSRF' token validation should pass for any value if 'CSRF' validation is disabled.", + 'CSRF token validation should pass for any value if CSRF validation is disabled.', ); self::assertTrue( $request->validateCsrfToken(null), - "'CSRF' token validation should pass for 'null' value if 'CSRF' validation is disabled.", + "CSRF token validation should pass for 'null' value if CSRF validation is disabled.", ); // enable validation @@ -266,15 +267,15 @@ public function testCsrfTokenValidation(): void self::assertTrue( $request->validateCsrfToken($token), - "'CSRF' token validation should pass for valid token on safe 'HTTP' methods ('GET', 'HEAD', 'OPTIONS').", + "CSRF token validation should pass for valid token on safe HTTP methods ('GET', 'HEAD', 'OPTIONS').", ); self::assertTrue( $request->validateCsrfToken($token . 'a'), - "'CSRF' token validation should pass for any value on safe 'HTTP' methods ('GET', 'HEAD', 'OPTIONS').", + "CSRF token validation should pass for any value on safe HTTP methods ('GET', 'HEAD', 'OPTIONS').", ); self::assertTrue( $request->validateCsrfToken(null), - "'CSRF' token validation should pass for 'null' value on safe 'HTTP' methods ('GET', 'HEAD', 'OPTIONS').", + "CSRF token validation should pass for 'null' value on safe HTTP methods ('GET', 'HEAD', 'OPTIONS').", ); } @@ -284,17 +285,19 @@ public function testCsrfTokenValidation(): void self::assertTrue( $request->validateCsrfToken($token), - "'CSRF' token validation should pass for valid token on unsafe 'HTTP' methods ('POST', 'PUT', 'DELETE').", + "CSRF token validation should pass for valid token on unsafe HTTP methods ('POST', 'PUT', 'DELETE').", ); self::assertFalse( $request->validateCsrfToken($token . 'a'), - "'CSRF' token validation should fail for invalid token on unsafe 'HTTP' methods ('POST', 'PUT', 'DELETE').", + "CSRF token validation should fail for invalid token on unsafe HTTP methods ('POST', 'PUT', 'DELETE').", ); self::assertFalse( $request->validateCsrfToken(null), - "'CSRF' token validation should fail for 'null' value on unsafe 'HTTP' methods ('POST', 'PUT', 'DELETE').", + "CSRF token validation should fail for 'null' value on unsafe HTTP methods ('POST', 'PUT', 'DELETE').", ); } + + $this->closeApplication(); } public function testCustomHeaderCsrfHeaderValidation(): void @@ -315,16 +318,18 @@ public function testCustomHeaderCsrfHeaderValidation(): void self::assertFalse( $request->validateCsrfToken(), - "'CSRF' token validation should fail when the custom 'CSRF' header is missing for unsafe 'HTTP' methods.", + 'CSRF token validation should fail when the custom CSRF header is missing for unsafe HTTP methods.', ); $request->headers->add('X-JGURDA', ''); self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass when the custom 'CSRF' header is present for unsafe 'HTTP' methods.", + 'CSRF token validation should pass when the custom CSRF header is present for unsafe HTTP methods.', ); } + + $this->closeApplication(); } public function testCustomSafeMethodsCsrfTokenValidation(): void @@ -344,19 +349,19 @@ public function testCustomSafeMethodsCsrfTokenValidation(): void self::assertTrue( $request->validateCsrfToken($token), - "'CSRF' token validation should pass for valid token on custom safe 'HTTP' methods ('OPTIONS').", + "CSRF token validation should pass for valid token on custom safe HTTP methods ('OPTIONS').", ); self::assertTrue( $request->validateCsrfToken($token . 'a'), - "'CSRF' token validation should pass for any value on custom safe 'HTTP' methods ('OPTIONS').", + "CSRF token validation should pass for any value on custom safe HTTP methods ('OPTIONS').", ); self::assertTrue( $request->validateCsrfToken(null), - "'CSRF' token validation should pass for 'null' value on custom safe 'HTTP' methods ('OPTIONS').", + "CSRF token validation should pass for 'null' value on custom safe HTTP methods ('OPTIONS').", ); self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass when no token is provided on custom safe 'HTTP' methods ('OPTIONS').", + "CSRF token validation should pass when no token is provided on custom safe HTTP methods ('OPTIONS').", ); // only accept valid token on other requests @@ -365,21 +370,23 @@ public function testCustomSafeMethodsCsrfTokenValidation(): void self::assertTrue( $request->validateCsrfToken($token), - "'CSRF' token validation should pass for valid token on 'HTTP' methods ('GET', 'HEAD', 'POST').", + "CSRF token validation should pass for valid token on HTTP methods ('GET', 'HEAD', 'POST').", ); self::assertFalse( $request->validateCsrfToken($token . 'a'), - "'CSRF' token validation should fail for invalid token on 'HTTP' methods ('GET', 'HEAD', 'POST').", + "CSRF token validation should fail for invalid token on HTTP methods ('GET', 'HEAD', 'POST').", ); self::assertFalse( $request->validateCsrfToken(null), - "'CSRF' token validation should fail for 'null' value on 'HTTP' methods ('GET', 'HEAD', 'POST').", + "CSRF token validation should fail for 'null' value on HTTP methods ('GET', 'HEAD', 'POST').", ); self::assertFalse( $request->validateCsrfToken(), - "'CSRF' token validation should fail when no token is provided on 'HTTP' methods ('GET', 'HEAD', 'POST').", + "CSRF token validation should fail when no token is provided on HTTP methods ('GET', 'HEAD', 'POST').", ); } + + $this->closeApplication(); } public function testCustomUnsafeMethodsCsrfHeaderValidation(): void @@ -399,16 +406,14 @@ public function testCustomUnsafeMethodsCsrfHeaderValidation(): void self::assertFalse( $request->validateCsrfToken(), - "'CSRF' token validation should fail when the custom header is missing for unsafe 'HTTP' methods " . - "('POST').", + "CSRF token validation should fail when the custom header is missing for unsafe HTTP methods ('POST').", ); $request->headers->add(Request::CSRF_HEADER, ''); self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass when the custom header is present for unsafe 'HTTP' methods " . - "('POST').", + "CSRF token validation should pass when the custom header is present for unsafe HTTP methods ('POST').", ); // accept no value on other requests @@ -419,10 +424,12 @@ public function testCustomUnsafeMethodsCsrfHeaderValidation(): void self::assertTrue( $request->validateCsrfToken(), - "'CSRF' token validation should pass for safe 'HTTP' methods ('GET', 'HEAD') regardless of custom " . - 'header presence.', + "CSRF token validation should pass for safe HTTP methods ('GET', 'HEAD') regardless of custom header " . + 'presence.', ); } + + $this->closeApplication(); } public function testForwardedNotTrusted(): void @@ -485,7 +492,7 @@ static function (string $key): bool { "'getAuthCredentials()' should return ['null', 'null'] when no authentication data is available.", ); - // test when Authorization header is present but not Basic auth + // test when Authorization header is present but not basic auth $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'; $request = new Request(); @@ -493,7 +500,7 @@ static function (string $key): bool { self::assertSame( [null, null], $request->getAuthCredentials(), - "'getAuthCredentials()' should return ['null', 'null'] when 'Authorization' header is not Basic " . + "'getAuthCredentials()' should return ['null', 'null'] when 'Authorization' header is not basic " . 'authentication.', ); @@ -508,7 +515,7 @@ static function (string $key): bool { "'getAuthCredentials()' should return ['null', 'null'] when 'Authorization' header is empty.", ); - // test when Authorization header contains only 'Basic' without credentials + // test when Authorization header contains only basic without credentials $_SERVER['HTTP_AUTHORIZATION'] = 'Basic'; $request = new Request(); @@ -516,7 +523,7 @@ static function (string $key): bool { self::assertSame( [null, null], $request->getAuthCredentials(), - "'getAuthCredentials()' should return ['null', 'null'] when 'Authorization' header contains only 'Basic' " . + "'getAuthCredentials()' should return ['null', 'null'] when 'Authorization' header contains only basic " . 'without credentials.', ); } @@ -613,11 +620,14 @@ public function testGetCsrfTokenFromHeaderUsesParentWhenAdapterIsNull(): void $request->csrfHeader = 'X-CSRF-Token'; $request->reset(); - $result = $request->getCsrfTokenFromHeader(); - self::assertNotNull($result, "Should return result from parent when adapter is 'null'."); + self::assertSame( + 'parent-csrf-token-456', + $request->getCsrfTokenFromHeader(), + "Should return value 'parent-csrf-token-456' result from parent when adapter is 'null'.", + ); - unset($_SERVER['HTTP_X_CSRF_TOKEN']); + $this->closeApplication(); } /** @@ -644,13 +654,13 @@ public function testGetHostInfo(array $server, array $expected): void ], ); - self::assertEquals( + self::assertSame( $expected[0] ?? null, $request->getHostInfo(), "'getHostInfo()' should return the expected value for the given 'secureHeaders' and 'trustedHosts' " . 'configuration.', ); - self::assertEquals( + self::assertSame( $expected[1] ?? null, $request->getHostName(), "'getHostName()' should return the expected value for the given 'secureHeaders' and 'trustedHosts' " . @@ -674,12 +684,12 @@ public function testGetHostInfo(array $server, array $expected): void ], ); - self::assertEquals( + self::assertSame( $expected[0] ?? null, $request->getHostInfo(), "'getHostInfo()' should return the expected value when 'trustedHosts' is an associative array.", ); - self::assertEquals( + self::assertSame( $expected[1] ?? null, $request->getHostName(), "'getHostName()' should return the expected value when 'trustedHosts' is an associative array.", @@ -699,7 +709,7 @@ public function testGetIsAjax(array $server, bool $expected): void $request = new Request(); - self::assertEquals( + self::assertSame( $expected, $request->getIsAjax(), "'getIsAjax()' should return the expected value based on the simulated \$_SERVER input.", @@ -719,7 +729,7 @@ public function testGetIsPjax(array $server, bool $expected): void $request = new Request(); - self::assertEquals( + self::assertSame( $expected, $request->getIsPjax(), "'getIsPjax()' should return the expected value based on the simulated \$_SERVER input.", @@ -753,7 +763,7 @@ public function testGetIsSecureConnection(array $server, bool $expected): void ], ); - self::assertEquals( + self::assertSame( $expected, $request->getIsSecureConnection(), "'getIsSecureConnection()' should return the expected value for the given 'secureHeaders' and " . @@ -780,7 +790,7 @@ public function testGetIsSecureConnection(array $server, bool $expected): void ], ); - self::assertEquals( + self::assertSame( $expected, $request->getIsSecureConnection(), "'getIsSecureConnection()' should return the expected value for the associative 'trustedHosts' and " . @@ -818,7 +828,7 @@ public function testGetIsSecureConnectionWithoutTrustedHost(array $server, bool ], ); - self::assertEquals( + self::assertSame( $expected, $request->getIsSecureConnection(), "'getIsSecureConnection()' should return the expected value for the associative 'trustedHosts' and " . @@ -835,18 +845,15 @@ public function testGetIsSecureConnectionWithoutTrustedHost(array $server, bool #[DataProviderExternal(RequestProvider::class, 'getMethod')] public function testGetMethod(array $server, string $expected): void { - $original = $_SERVER; $_SERVER = $server; $request = new Request(); - self::assertEquals( + self::assertSame( $expected, $request->getMethod(), "'getMethod()' should return the expected value based on the simulated \$_SERVER input.", ); - - $_SERVER = $original; } public function testGetOrigin(): void @@ -855,7 +862,7 @@ public function testGetOrigin(): void $request = new Request(); - self::assertEquals( + self::assertSame( 'https://www.w3.org', $request->getOrigin(), "'getOrigin()' should return the correct origin when 'HTTP_ORIGIN' is set.", @@ -879,10 +886,8 @@ public function testGetQueryStringWhenEmpty(): void self::assertEmpty( $request->getQueryString(), - 'Query string should be empty when \'$_SERVER[\'QUERY_STRING\']\' is empty.', + "Query string should be empty when \$_SERVER['QUERY_STRING'] is empty.", ); - - unset($_SERVER['QUERY_STRING']); } public function testGetQueryStringWhenNotSet(): void @@ -893,7 +898,7 @@ public function testGetQueryStringWhenNotSet(): void self::assertEmpty( $request->getQueryString(), - 'Query string should be empty when \'$_SERVER[\'QUERY_STRING\']\' is not set.', + "Query string should be empty when \$_SERVER['QUERY_STRING'] is not set.", ); } @@ -912,51 +917,25 @@ public function testGetQueryStringWithVariousParams(string $queryString, string $request->getQueryString(), "Query string should match the expected value for: '{$queryString}'.", ); - - unset($_SERVER['QUERY_STRING']); - } - - public function testGetScriptFileWithEmptyServer(): void - { - $request = new Request(); - - $_SERVER = []; - - $this->expectException(InvalidConfigException::class); - $this->expectExceptionMessage('Unable to determine the entry script file path.'); - - $request->getScriptFile(); - } - - public function testGetScriptUrlWithEmptyServer(): void - { - $request = new Request(); - - $_SERVER = []; - - $this->expectException(InvalidConfigException::class); - $this->expectExceptionMessage('Unable to determine the entry script file path.'); - - $request->getScriptUrl(); } public function testGetServerName(): void { - $request = new Request(); - $_SERVER['SERVER_NAME'] = 'servername'; - self::assertEquals( + $request = new Request(); + + self::assertSame( 'servername', $request->getServerName(), - '\'getServerName()\' should return the value of \'$_SERVER[\'SERVER_NAME\']\' when it is set.', + "'getServerName()' should return the value of \$_SERVER['SERVER_NAME'] when it is set.", ); unset($_SERVER['SERVER_NAME']); self::assertNull( $request->getServerName(), - '\'getServerName()\' should return \'null\' when \'$_SERVER[\'SERVER_NAME\']\' is not set.', + "'getServerName()' should return 'null' when \$_SERVER['SERVER_NAME'] is not set.", ); } @@ -989,21 +968,21 @@ public function testGetServerParamsReturnsExpectedServerArray(): void public function testGetServerPort(): void { - $request = new Request(); - $_SERVER['SERVER_PORT'] = 33; - self::assertEquals( + $request = new Request(); + + self::assertSame( 33, $request->getServerPort(), - '\'getServerPort()\' should return the value of \'$_SERVER[\'SERVER_PORT\']\' when it is set.', + "'getServerPort()' should return the value of \$_SERVER['SERVER_PORT'] when it is set.", ); unset($_SERVER['SERVER_PORT']); self::assertNull( $request->getServerPort(), - '\'getServerPort()\' should return \'null\' when $_SERVER[\'SERVER_PORT\'] is not set.', + "'getServerPort()' should return 'null' when \$_SERVER['SERVER_PORT'] is not set.", ); } @@ -1029,10 +1008,8 @@ public function testGetUrlWhenRequestUriIsSet(): void self::assertSame( '/search?q=hello+world&category=books&price[min]=10&price[max]=50', $request->getUrl(), - 'URL should match the value of \'REQUEST_URI\' when it is set.', + "URL should match the value of 'REQUEST_URI' when it is set.", ); - - unset($_SERVER['REQUEST_URI']); } /** @@ -1049,8 +1026,6 @@ public function testGetUrlWithRootPath(): void $request->getUrl(), "URL should return 'root' path when 'REQUEST_URI' is set to 'root'.", ); - - unset($_SERVER['REQUEST_URI']); } /** @@ -1060,7 +1035,6 @@ public function testGetUrlWithRootPath(): void #[DataProviderExternal(RequestProvider::class, 'getUserIP')] public function testGetUserIP(array $server, string $expected): void { - $original = $_SERVER; $_SERVER = $server; $request = new Request( @@ -1079,7 +1053,7 @@ public function testGetUserIP(array $server, string $expected): void ], ); - self::assertEquals( + self::assertSame( $expected, $request->getUserIP(), "'getUserIP()' should return the expected value for the given 'secureHeaders' and 'trustedHosts'.", @@ -1104,13 +1078,11 @@ public function testGetUserIP(array $server, string $expected): void ], ); - self::assertEquals( + self::assertSame( $expected, $request->getUserIP(), "'getUserIP()' should return the expected value for the associative 'trustedHosts' and 'secureHeaders'.", ); - - $_SERVER = $original; } /** @@ -1119,7 +1091,6 @@ public function testGetUserIP(array $server, string $expected): void #[DataProviderExternal(RequestProvider::class, 'getUserIPWithoutTrustedHost')] public function testGetUserIPWithoutTrustedHost(array $server, string $expected): void { - $original = $_SERVER; $_SERVER = $server; $request = new Request( @@ -1140,13 +1111,11 @@ public function testGetUserIPWithoutTrustedHost(array $server, string $expected) ], ); - self::assertEquals( + self::assertSame( $expected, $request->getUserIP(), "'getUserIP()' should return the expected value for the associative 'trustedHosts' and 'secureHeaders'.", ); - - $_SERVER = $original; } /** @@ -1155,8 +1124,6 @@ public function testGetUserIPWithoutTrustedHost(array $server, string $expected) #[DataProviderExternal(RequestProvider::class, 'httpAuthorizationHeaders')] public function testHttpAuthCredentialsFromHttpAuthorizationHeader(string $secret, array $expected): void { - $original = $_SERVER; - $request = new Request(); $_SERVER['HTTP_AUTHORIZATION'] = "Basic {$secret}"; @@ -1177,8 +1144,6 @@ public function testHttpAuthCredentialsFromHttpAuthorizationHeader(string $secre "'getAuthPassword()' should return the expected password from 'HTTP_AUTHORIZATION'.", ); - $_SERVER = $original; - $request = new Request(); $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] = "Basic {$secret}"; @@ -1198,13 +1163,10 @@ public function testHttpAuthCredentialsFromHttpAuthorizationHeader(string $secre $request->getAuthPassword(), "'getAuthPassword()' should return the expected password from 'REDIRECT_HTTP_AUTHORIZATION'.", ); - - $_SERVER = $original; } public function testHttpAuthCredentialsFromServerSuperglobal(): void { - $original = $_SERVER; [$user, $pw] = ['foo', 'bar']; $_SERVER['PHP_AUTH_USER'] = $user; $_SERVER['PHP_AUTH_PW'] = $pw; @@ -1228,35 +1190,31 @@ public function testHttpAuthCredentialsFromServerSuperglobal(): void $request->getAuthPassword(), "'getAuthPassword()' should return the password from 'PHP_AUTH_PW' when set.", ); - - $_SERVER = $original; } public function testIssue15317(): void { - $originalCookie = $_COOKIE; - $this->webApplication(); $_COOKIE[(new Request())->csrfParam] = ''; + $request = new Request(); $request->enableCsrfCookie = true; $request->enableCookieValidation = false; + $_SERVER['REQUEST_METHOD'] = 'POST'; Yii::$app->security->unmaskToken(''); self::assertFalse( $request->validateCsrfToken(''), - "'validateCsrfToken()' should return 'false' when an empty 'CSRF' token is provided.", + "'validateCsrfToken()' should return 'false' when an empty CSRF token is provided.", ); self::assertNotEmpty( $request->getCsrfToken(), - "'getCsrfToken()' should return a non-empty value after an empty 'CSRF' token is validated.", + "'getCsrfToken()' should return a non-empty value after an empty CSRF token is validated.", ); - - $_COOKIE = $originalCookie; } public function testNoCsrfTokenCsrfHeaderValidation(): void @@ -1269,7 +1227,7 @@ public function testNoCsrfTokenCsrfHeaderValidation(): void self::assertNull( $request->getCsrfToken(), - "'getCsrfToken()' should return 'null' when no 'CSRF' token is set.", + "'getCsrfToken()' should return 'null' when no CSRF token is set.", ); } @@ -1277,12 +1235,12 @@ public function testParseAcceptHeader(): void { $request = new Request(); - self::assertEquals( + self::assertSame( [], $request->parseAcceptHeader(' '), "'parseAcceptHeader()' should return an empty array when the header is blank.", ); - self::assertEquals( + self::assertSame( [ 'audio/basic' => ['q' => 1], 'audio/*' => ['q' => 0.2], @@ -1290,7 +1248,7 @@ public function testParseAcceptHeader(): void $request->parseAcceptHeader('audio/*; q=0.2, audio/basic'), "'parseAcceptHeader()' should correctly parse media types and quality values.", ); - self::assertEquals( + self::assertSame( [ 'application/json' => ['q' => 1, 'version' => '1.0'], 'application/xml' => ['q' => 1, 'version' => '2.0', 'x'], @@ -1360,7 +1318,7 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = []; - self::assertEquals( + self::assertSame( 'en', $request->getPreferredLanguage(), "Should return 'en' when no 'acceptableLanguages' are set.", @@ -1370,7 +1328,7 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = ['de']; - self::assertEquals( + self::assertSame( 'en', $request->getPreferredLanguage(), "Should return 'en' when 'acceptableLanguages' does not match the default.", @@ -1380,7 +1338,7 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - self::assertEquals( + self::assertSame( 'en', $request->getPreferredLanguage(['en']), "Should return 'en' when 'en' is in the preferred list.", @@ -1390,12 +1348,12 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - self::assertEquals( + self::assertSame( 'de', $request->getPreferredLanguage(['ru', 'de']), "Should return 'de' when 'de' is in the preferred list.", ); - self::assertEquals( + self::assertSame( 'de-DE', $request->getPreferredLanguage(['ru', 'de-DE']), "Should return 'de-DE' when 'de-DE' is in the preferred list.", @@ -1405,7 +1363,7 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - self::assertEquals( + self::assertSame( 'de', $request->getPreferredLanguage(['de', 'ru']), "Should return 'de' when 'de' is the first match in the preferred list.", @@ -1415,7 +1373,7 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = ['en-us', 'de', 'ru-RU']; - self::assertEquals( + self::assertSame( 'ru-ru', $request->getPreferredLanguage(['ru-ru']), "Should return 'ru-ru' when 'ru-ru' is in the preferred list.", @@ -1425,12 +1383,12 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = ['en-us', 'de']; - self::assertEquals( + self::assertSame( 'ru-ru', $request->getPreferredLanguage(['ru-ru', 'pl']), "Should return 'ru-ru' when 'ru-ru' is the first in the preferred list.", ); - self::assertEquals( + self::assertSame( 'ru-RU', $request->getPreferredLanguage(['ru-RU', 'pl']), "Should return 'ru-RU' when 'ru-RU' is the first in the preferred list.", @@ -1440,7 +1398,7 @@ public function testPreferredLanguage(): void $request->acceptableLanguages = ['en-us', 'de']; - self::assertEquals( + self::assertSame( 'pl', $request->getPreferredLanguage(['pl', 'ru-ru']), "Should return 'pl' when 'pl' is the first in the preferred list and not present in 'acceptableLanguages'.", @@ -1465,7 +1423,7 @@ public function testRequestMethodCanNotBeDowngraded( self::assertSame( $expectedMethod, $request->getMethod(), - "'getMethod()' should return the expected 'HTTP' method after considering override logic.", + "'getMethod()' should return the expected HTTP method after considering override logic.", ); } @@ -1493,28 +1451,29 @@ public function testResolve(): void $request = new Request(); $request->pathInfo = 'posts'; + $_GET['page'] = 1; $result = $request->resolve(); - self::assertEquals( + self::assertSame( [ 'post/list', ['page' => 1], ], $result, - '\'resolve()\' should return the correct route and query parameters when \'page\' is set in \'$_GET\'.', + "'resolve()' should return the correct route and query parameters when 'page' is set in \$_GET.", ); - self::assertEquals( + self::assertSame( ['page' => 1], $_GET, - '\'$_GET\' should contain only the \'page\' parameter after resolving the \'posts\' route.', + "\$_GET should contain only the 'page' parameter after resolving the 'posts' route.", ); $request->setQueryParams(['page' => 5]); $result = $request->resolve(); - self::assertEquals( + self::assertSame( [ 'post/list', ['page' => 5], @@ -1522,16 +1481,16 @@ public function testResolve(): void $result, "'resolve()' should return the correct route and query parameters when 'page' is set via 'setQueryParams()'.", ); - self::assertEquals( + self::assertSame( ['page' => 1], $_GET, - '\'$_GET\' should remain unchanged after \'setQueryParams()\' is used on the request object.', + "\$_GET should remain unchanged after 'setQueryParams()' is used on the request object.", ); $request->setQueryParams(['custom-page' => 5]); $result = $request->resolve(); - self::assertEquals( + self::assertSame( [ 'post/list', ['custom-page' => 5], @@ -1539,10 +1498,10 @@ public function testResolve(): void $result, "'resolve()' should return the correct route and custom query parameters when 'setQueryParams()' is used.", ); - self::assertEquals( + self::assertSame( ['page' => 1], $_GET, - '\'$_GET\' should not be affected by custom query parameters set via \'setQueryParams()\'.', + "\$_GET should not be affected by custom query parameters set via 'setQueryParams()'.", ); unset($_GET['page']); @@ -1551,45 +1510,45 @@ public function testResolve(): void $request->pathInfo = 'post/21'; - self::assertEquals( + self::assertSame( [], $_GET, - '\$_GET\' should be empty after unsetting the \'page\' parameter and before resolving a new route.', + "\$_GET should be empty after unsetting the 'page' parameter and before resolving a new route.", ); $result = $request->resolve(); - self::assertEquals( + self::assertSame( [ 'post/view', - ['id' => 21], + ['id' => '21'], ], $result, - "'resolve()' should return the correct route and parameters when resolving a path with an \'id\'.", + "'resolve()' should return the correct route and parameters when resolving a path with an 'id'.", ); - self::assertEquals( - ['id' => 21], + self::assertSame( + ['id' => '21'], $_GET, - '\'$_GET\' should contain the \'id\' parameter after resolving the \'post/21\' route.', + "\$_GET should contain the 'id' parameter after resolving the 'post/21' route.", ); $_GET['id'] = 42; $result = $request->resolve(); - self::assertEquals( + self::assertSame( [ 'post/view', - ['id' => 21], + ['id' => '21'], ], $result, - '\'resolve()\' should return the same route and parameters even if \'$_GET[\'id\']\' is set to a ' . - 'different value before resolving.', + "'resolve()' should return the same route and parameters even if \$_GET['id'] is set to a different " . + 'value before resolving.', ); - self::assertEquals( - ['id' => 21], + self::assertSame( + ['id' => '21'], $_GET, - '\'$_GET\' should be overwritten with the resolved \'id\' parameter after resolving the route.', + "\$_GET should be overwritten with the resolved 'id' parameter after resolving the route.", ); $_GET['id'] = 63; @@ -1597,11 +1556,11 @@ public function testResolve(): void $request->setQueryParams(['token' => 'secret']); $result = $request->resolve(); - self::assertEquals( + self::assertSame( [ 'post/view', [ - 'id' => 21, + 'id' => '21', 'token' => 'secret', ], ], @@ -1609,10 +1568,10 @@ public function testResolve(): void "'resolve()' should merge additional query parameters set via 'setQueryParams()' with the resolved route " . 'parameters.', ); - self::assertEquals( + self::assertSame( ['id' => 63], $_GET, - '\'$_GET\' should remain unchanged by \'setQueryParams()\' after resolving the route with extra parameters.', + "\$_GET should remain unchanged by 'setQueryParams()' after resolving the route with extra parameters.", ); } @@ -1622,31 +1581,94 @@ public function testReturnNullFromParentWhenRemoteHostNotSetInServerGlobals(): v $request = new Request(); - $result = $request->getRemoteHost(); - self::assertNull( - $result, + $request->getRemoteHost(), "Remote host should return 'null' from parent implementation when PSR-7 adapter is not set and " . "'REMOTE_HOST' is not present in \$_SERVER.", ); } + /** + * @throws InvalidConfigException if the configuration is invalid or incomplete. + */ + public function testReturnParentGetScriptUrlWhenAdapterIsNull(): void + { + $request = new Request(); + + $_SERVER['SCRIPT_NAME'] = '/test.php'; + $_SERVER['SCRIPT_FILENAME'] = '/path/to/test.php'; + + // ensure adapter is `null` (default state) + $request->reset(); + + // verify the method executes without throwing exception when adapter is `null` + self::assertSame( + '/test.php', + $request->getScriptUrl(), + "'getScriptUrl()' should return 'SCRIPT_NAME' when adapter is 'null'.", + ); + } + + public function testReturnParentNullWhenAdapterIsNotSetAndServerNameNotPresent(): void + { + unset($_SERVER['SERVER_NAME']); + + $request = new Request(); + + self::assertNull( + $request->getServerName(), + "Server name should return 'null' from parent implementation when PSR-7 adapter is not set and " . + "'SERVER_NAME' is not present in \$_SERVER.", + ); + } + public function testReturnParentRemoteHostWhenAdapterIsNull(): void { $_SERVER['REMOTE_HOST'] = 'parent.host.com'; $request = new Request(); - $result = $request->getRemoteHost(); - self::assertSame( 'parent.host.com', - $result, + $request->getRemoteHost(), 'Remote host should return the value from parent implementation when PSR-7 adapter is not set and ' . "'REMOTE_HOST' is present in \$_SERVER.", ); } + public function testReturnParentServerNameWhenAdapterIsNotSet(): void + { + $_SERVER['SERVER_NAME'] = 'fallback.server.com'; + + $request = new Request(); + + self::assertSame( + 'fallback.server.com', + $request->getServerName(), + "Server name should return 'fallback.server.com' from parent implementation when PSR-7 adapter is not " . + "set and 'SERVER_NAME' is present in \$_SERVER.", + ); + } + + /** + * @throws InvalidConfigException if the configuration is invalid or incomplete. + */ + public function testReturnParentUrlWhenAdapterIsNull(): void + { + $_SERVER['REQUEST_URI'] = '/legacy/path?param=value'; + + $request = new Request(); + + // ensure adapter is `null` (default state) + $request->reset(); + + self::assertSame( + '/legacy/path?param=value', + $request->getUrl(), + "URL should return parent implementation result when adapter is 'null'.", + ); + } + public function testSetHostInfo(): void { $request = new Request(); @@ -1698,6 +1720,26 @@ public function testThrowExceptionWhenRequestUriIsMissing(): void $request->getUrl(); } + public function testThrowInvalidConfigExceptionWhenGetScriptFileItsEmptyServer(): void + { + $request = new Request(); + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage('Unable to determine the entry script file path.'); + + $request->getScriptFile(); + } + + public function testThrowInvalidConfigExceptionWhenGetScriptUrlItsEmptyServer(): void + { + $request = new Request(); + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage('Unable to determine the entry script file path.'); + + $request->getScriptUrl(); + } + public function testThrowTypeErrorWhenRemoteHostIsNotStringOrNull(): void { $_SERVER['REMOTE_HOST'] = 456;