diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index 2a4ea5b629a..b939bae33ea 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -2,10 +2,13 @@ namespace Drupal\rest\Tests; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\Entity\ConfigEntityType; use Drupal\node\NodeInterface; use Drupal\rest\RestResourceConfigInterface; use Drupal\simpletest\WebTestBase; +use GuzzleHttp\Cookie\FileCookieJar; +use GuzzleHttp\Cookie\SetCookie; /** * Test helper class that provides a REST client method to send HTTP requests. @@ -62,6 +65,13 @@ abstract class RESTTestBase extends WebTestBase { */ public static $modules = array('rest', 'entity_test'); + /** + * The last response. + * + * @var \Psr\Http\Message\ResponseInterface + */ + protected $response; + protected function setUp() { parent::setUp(); $this->defaultFormat = 'hal_json'; @@ -72,6 +82,38 @@ abstract class RESTTestBase extends WebTestBase { if (in_array('node', static::$modules)) { $this->drupalCreateContentType(array('name' => 'resttest', 'type' => 'resttest')); } + + $this->cookieFile = $this->publicFilesDirectory . '/cookie.jar'; + } + + /** + * Calculates cookies used by guzzle later. + * + * @return \GuzzleHttp\Cookie\CookieJarInterface + * The used CURL options in guzzle. + */ + protected function cookies() { + $cookies = []; + + foreach ($this->cookies as $key => $cookie) { + $cookies[$key][] = $cookie['value']; + } + + $request = \Drupal::request(); + $cookies = NestedArray::mergeDeep($cookies, $this->extractCookiesFromRequest($request)); + + $cookie_jar = new FileCookieJar($this->cookieFile); + foreach ($cookies as $key => $cookie_values) { + foreach ($cookie_values as $cookie_value) { + // setcookie() sets the value of a cookie to be deleted, when its gonna + // be removed. + if ($cookie_value !== 'deleted') { + $cookie_jar->setCookie(new SetCookie(['Name' => $key, 'Value' => $cookie_value, 'Domain' => $request->getHost()])); + } + } + } + + return $cookie_jar; } /** @@ -103,113 +145,132 @@ abstract class RESTTestBase extends WebTestBase { $requested_token = $this->drupalGet('session/token'); } + $client = \Drupal::httpClient(); $url = $this->buildUrl($url); - $curl_options = array(); + $options = [ + 'http_errors' => FALSE, + 'cookies' => $this->cookies(), + 'curl' => [ + CURLOPT_HEADERFUNCTION => [&$this, 'curlHeaderCallback'], + ], + ]; switch ($method) { case 'GET': - // Set query if there are additional GET parameters. - $curl_options = array( - CURLOPT_HTTPGET => TRUE, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_URL => $url, - CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type), - ); + $options += [ + 'headers' => [ + 'Accept' => $mime_type, + ], + ]; + $response = $client->get($url, $options); break; case 'HEAD': - $curl_options = array( - CURLOPT_HTTPGET => FALSE, - CURLOPT_CUSTOMREQUEST => 'HEAD', - CURLOPT_URL => $url, - CURLOPT_NOBODY => TRUE, - CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type), - ); + $response = $client->head($url, $options); break; case 'POST': - $curl_options = array( - CURLOPT_HTTPGET => FALSE, - CURLOPT_POST => TRUE, - CURLOPT_POSTFIELDS => $body, - CURLOPT_URL => $url, - CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( - 'Content-Type: ' . $mime_type, - 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), - ) : array( - 'Content-Type: ' . $mime_type, - ), - ); + $options += [ + 'headers' => $csrf_token !== FALSE ? [ + 'Content-Type' => $mime_type, + 'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token), + ] : [ + 'Content-Type' => $mime_type, + ], + 'body' => $body, + ]; + $response = $client->post($url, $options); break; case 'PUT': - $curl_options = array( - CURLOPT_HTTPGET => FALSE, - CURLOPT_CUSTOMREQUEST => 'PUT', - CURLOPT_POSTFIELDS => $body, - CURLOPT_URL => $url, - CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( - 'Content-Type: ' . $mime_type, - 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), - ) : array( - 'Content-Type: ' . $mime_type, - ), - ); + $options += [ + 'headers' => $csrf_token !== FALSE ? [ + 'Content-Type' => $mime_type, + 'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token), + ] : [ + 'Content-Type' => $mime_type, + ], + 'body' => $body, + ]; + $response = $client->put($url, $options); break; case 'PATCH': - $curl_options = array( - CURLOPT_HTTPGET => FALSE, - CURLOPT_CUSTOMREQUEST => 'PATCH', - CURLOPT_POSTFIELDS => $body, - CURLOPT_URL => $url, - CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( - 'Content-Type: ' . $mime_type, - 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), - ) : array( - 'Content-Type: ' . $mime_type, - ), - ); + $options += [ + 'headers' => $csrf_token !== FALSE ? [ + 'Content-Type' => $mime_type, + 'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token), + ] : [ + 'Content-Type' => $mime_type, + ], + 'body' => $body, + ]; + $response = $client->patch($url, $options); break; case 'DELETE': - $curl_options = array( - CURLOPT_HTTPGET => FALSE, - CURLOPT_CUSTOMREQUEST => 'DELETE', - CURLOPT_URL => $url, - CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( - 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), - ) : array(), - ); + $options += [ + 'headers' => $csrf_token !== FALSE ? [ + 'Content-Type' => $mime_type, + 'X-CSRF-Token' => ($csrf_token === NULL ? $requested_token : $csrf_token), + ] : [], + ]; + $response = $client->delete($url, $options); break; } - if ($mime_type === 'none') { - unset($curl_options[CURLOPT_HTTPHEADER]['Content-Type']); - } - - $this->responseBody = $this->curlExec($curl_options); + $this->response = $response; + $this->responseBody = (string) $response->getBody(); + $this->setRawContent($this->responseBody); // Ensure that any changes to variables in the other thread are picked up. $this->refreshVariables(); - $headers = $this->drupalGetHeaders(); - $this->verbose($method . ' request to: ' . $url . - '
Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) . - (isset($curl_options[CURLOPT_HTTPHEADER]) ? '
Request headers: ' . nl2br(print_r($curl_options[CURLOPT_HTTPHEADER], TRUE)) : '' ) . - (isset($curl_options[CURLOPT_POSTFIELDS]) ? '
Request body: ' . nl2br(print_r($curl_options[CURLOPT_POSTFIELDS], TRUE)) : '' ) . - '
Response headers: ' . nl2br(print_r($headers, TRUE)) . + '
Code: ' . $this->response->getStatusCode() . + (isset($options['headers']) ? '
Request headers: ' . nl2br(print_r($options['headers'], TRUE)) : '') . + (isset($options['body']) ? '
Request body: ' . nl2br(print_r($options['body'], TRUE)) : '') . + '
Response headers: ' . nl2br(print_r($response->getHeaders(), TRUE)) . '
Response body: ' . $this->responseBody); return $this->responseBody; } + /** + * {@inheritdoc} + */ + protected function assertResponse($code, $message = '', $group = 'Browser') { + if (!isset($this->response)) { + return parent::assertResponse($code, $message, $group); + } + return $this->assertEqual($code, $this->response->getStatusCode(), $message ? $message : "HTTP response expected $code, actual {$this->response->getStatusCode()}", $group); + } + + /** + * {@inheritdoc} + */ + protected function drupalGetHeaders($all_requests = FALSE) { + if (!isset($this->response)) { + return parent::drupalGetHeaders($all_requests); + } + $lowercased_keys = array_map('strtolower', array_keys($this->response->getHeaders())); + return array_map(function (array $header) { + return implode(', ', $header); + }, array_combine($lowercased_keys, array_values($this->response->getHeaders()))); + } + + /** + * {@inheritdoc} + */ + protected function drupalGetHeader($name, $all_requests = FALSE) { + if (!isset($this->response)) { + return parent::drupalGetHeader($name, $all_requests); + } + if ($header = $this->response->getHeader($name)) { + return implode(', ', $header); + } + } + /** * Creates entity objects based on their types. * @@ -369,6 +430,8 @@ abstract class RESTTestBase extends WebTestBase { * override it every time it is omitted. */ protected function curlExec($curl_options, $redirect = FALSE) { + unset($this->response); + if (!isset($curl_options[CURLOPT_CUSTOMREQUEST])) { if (!empty($curl_options[CURLOPT_HTTPGET])) { $curl_options[CURLOPT_CUSTOMREQUEST] = 'GET'; diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php index d3784ed6669..3eef1a9f7ef 100644 --- a/core/modules/rest/src/Tests/UpdateTest.php +++ b/core/modules/rest/src/Tests/UpdateTest.php @@ -258,6 +258,11 @@ class UpdateTest extends RESTTestBase { $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(200); + // Log out the user to clear the cookies used by the Guzzle client so that a + // log in request can be made for the changed user. + $this->httpRequest('user/logout', 'GET'); + $this->loggedInUser = FALSE; + // Verify that we can log in with the new password. $account->pass_raw = $new_password; $this->drupalLogin($account); diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 516a202f122..36226429764 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -26,6 +26,7 @@ use Drupal\Core\Test\AssertMailTrait; use Drupal\Core\Url; use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; use Drupal\Tests\TestFileCreationTrait; +use Drupal\Tests\XdebugRequestTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Yaml\Yaml as SymfonyYaml; @@ -63,6 +64,8 @@ abstract class WebTestBase extends TestBase { createAdminRole as drupalCreateAdminRole; } + use XdebugRequestTrait; + /** * The profile to install as a basis for testing. * @@ -1117,29 +1120,10 @@ abstract class WebTestBase extends TestBase { if (!empty($this->curlCookies)) { $cookies = $this->curlCookies; } - // In order to debug web tests you need to either set a cookie, have the - // Xdebug session in the URL or set an environment variable in case of CLI - // requests. If the developer listens to connection on the parent site, by - // default the cookie is not forwarded to the client side, so you cannot - // debug the code running on the child site. In order to make debuggers work - // this bit of information is forwarded. Make sure that the debugger listens - // to at least three external connections. - $request = \Drupal::request(); - $cookie_params = $request->cookies; - if ($cookie_params->has('XDEBUG_SESSION')) { - $cookies[] = 'XDEBUG_SESSION=' . $cookie_params->get('XDEBUG_SESSION'); - } - // For CLI requests, the information is stored in $_SERVER. - $server = $request->server; - if ($server->has('XDEBUG_CONFIG')) { - // $_SERVER['XDEBUG_CONFIG'] has the form "key1=value1 key2=value2 ...". - $pairs = explode(' ', $server->get('XDEBUG_CONFIG')); - foreach ($pairs as $pair) { - list($key, $value) = explode('=', $pair); - // Account for key-value pairs being separated by multiple spaces. - if (trim($key, ' ') == 'idekey') { - $cookies[] = 'XDEBUG_SESSION=' . trim($value, ' '); - } + + foreach ($this->extractCookiesFromRequest(\Drupal::request()) as $cookie_name => $values) { + foreach ($values as $value) { + $cookies[] = $cookie_name . '=' . $value; } } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index d0fabd37d10..5dfa68a6895 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -65,6 +65,7 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { createRole as drupalCreateRole; createUser as drupalCreateUser; } + use XdebugRequestTrait; /** * Class loader. @@ -519,29 +520,10 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { // Setup Mink. $session = $this->initMink(); - // In order to debug web tests you need to either set a cookie, have the - // Xdebug session in the URL or set an environment variable in case of CLI - // requests. If the developer listens to connection when running tests, by - // default the cookie is not forwarded to the client side, so you cannot - // debug the code running on the test site. In order to make debuggers work - // this bit of information is forwarded. Make sure that the debugger listens - // to at least three external connections. - $request = \Drupal::request(); - $cookie_params = $request->cookies; - if ($cookie_params->has('XDEBUG_SESSION')) { - $session->setCookie('XDEBUG_SESSION', $cookie_params->get('XDEBUG_SESSION')); - } - // For CLI requests, the information is stored in $_SERVER. - $server = $request->server; - if ($server->has('XDEBUG_CONFIG')) { - // $_SERVER['XDEBUG_CONFIG'] has the form "key1=value1 key2=value2 ...". - $pairs = explode(' ', $server->get('XDEBUG_CONFIG')); - foreach ($pairs as $pair) { - list($key, $value) = explode('=', $pair); - // Account for key-value pairs being separated by multiple spaces. - if (trim($key) == 'idekey') { - $session->setCookie('XDEBUG_SESSION', trim($value)); - } + $cookies = $this->extractCookiesFromRequest(\Drupal::request()); + foreach ($cookies as $cookie_name => $values) { + foreach ($values as $value) { + $session->setCookie($cookie_name, $value); } } diff --git a/core/tests/Drupal/Tests/XdebugRequestTrait.php b/core/tests/Drupal/Tests/XdebugRequestTrait.php new file mode 100644 index 00000000000..5da86a51bde --- /dev/null +++ b/core/tests/Drupal/Tests/XdebugRequestTrait.php @@ -0,0 +1,48 @@ +cookies; + $cookies = []; + if ($cookie_params->has('XDEBUG_SESSION')) { + $cookies['XDEBUG_SESSION'][] = $cookie_params->get('XDEBUG_SESSION'); + } + // For CLI requests, the information is stored in $_SERVER. + $server = $request->server; + if ($server->has('XDEBUG_CONFIG')) { + // $_SERVER['XDEBUG_CONFIG'] has the form "key1=value1 key2=value2 ...". + $pairs = explode(' ', $server->get('XDEBUG_CONFIG')); + foreach ($pairs as $pair) { + list($key, $value) = explode('=', $pair); + // Account for key-value pairs being separated by multiple spaces. + if (trim($key, ' ') == 'idekey') { + $cookies['XDEBUG_SESSION'][] = trim($value, ' '); + } + } + } + return $cookies; + } + +}