a4d5562008d16e7100827b23ebbe68251ba7dd0b
[yaffs-website] / web / core / modules / big_pipe / src / Tests / BigPipeTest.php
1 <?php
2
3 namespace Drupal\big_pipe\Tests;
4
5 use Drupal\big_pipe\Render\Placeholder\BigPipeStrategy;
6 use Drupal\big_pipe\Render\BigPipe;
7 use Drupal\Component\Serialization\Json;
8 use Drupal\Component\Utility\Html;
9 use Drupal\Core\Logger\RfcLogLevel;
10 use Drupal\Core\Url;
11 use Drupal\simpletest\WebTestBase;
12
13 /**
14  * Tests BigPipe's no-JS detection & response delivery (with and without JS).
15  *
16  * Covers:
17  * - big_pipe_page_attachments()
18  * - \Drupal\big_pipe\Controller\BigPipeController
19  * - \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
20  * - \Drupal\big_pipe\Render\BigPipe
21  *
22  * @group big_pipe
23  */
24 class BigPipeTest extends WebTestBase {
25
26   /**
27    * Modules to enable.
28    *
29    * @var array
30    */
31   public static $modules = ['big_pipe', 'big_pipe_test', 'dblog'];
32
33   /**
34    * {@inheritdoc}
35    */
36   protected $dumpHeaders = TRUE;
37
38   /**
39    * {@inheritdoc}
40    */
41   protected function setUp() {
42     parent::setUp();
43
44     // Ignore the <meta> refresh that big_pipe.module sets. It causes a redirect
45     // to a page that sets another cookie, which causes WebTestBase to lose the
46     // session cookie. To avoid this problem, tests should first call
47     // drupalGet() and then call checkForMetaRefresh() manually, and then reset
48     // $this->maximumMetaRefreshCount and $this->metaRefreshCount.
49     // @see doMetaRefresh()
50     $this->maximumMetaRefreshCount = 0;
51   }
52
53   /**
54    * Performs a single <meta> refresh explicitly.
55    *
56    * This test disables the automatic <meta> refresh checking, each time it is
57    * desired that this runs, a test case must explicitly call this.
58    *
59    * @see setUp()
60    */
61   protected function performMetaRefresh() {
62     $this->maximumMetaRefreshCount = 1;
63     $this->checkForMetaRefresh();
64     $this->maximumMetaRefreshCount = 0;
65     $this->metaRefreshCount = 0;
66   }
67
68   /**
69    * Tests BigPipe's no-JS detection.
70    *
71    * Covers:
72    * - big_pipe_page_attachments()
73    * - \Drupal\big_pipe\Controller\BigPipeController
74    */
75   public function testNoJsDetection() {
76     $no_js_to_js_markup = '<script>document.cookie = "' . BigPipeStrategy::NOJS_COOKIE . '=1; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"</script>';
77
78     // 1. No session (anonymous).
79     $this->drupalGet(Url::fromRoute('<front>'));
80     $this->assertSessionCookieExists(FALSE);
81     $this->assertBigPipeNoJsCookieExists(FALSE);
82     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
83     $this->assertNoRaw($no_js_to_js_markup);
84
85     // 2. Session (authenticated).
86     $this->drupalLogin($this->rootUser);
87     $this->assertSessionCookieExists(TRUE);
88     $this->assertBigPipeNoJsCookieExists(FALSE);
89     $this->assertRaw('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . base_path() . 'user/1" />' . "\n" . '</noscript>');
90     $this->assertNoRaw($no_js_to_js_markup);
91     $this->assertBigPipeNoJsMetaRefreshRedirect();
92     $this->assertBigPipeNoJsCookieExists(TRUE);
93     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
94     $this->assertRaw($no_js_to_js_markup);
95     $this->drupalLogout();
96
97     // Close the prior connection and remove the collected state.
98     $this->curlClose();
99     $this->curlCookies = [];
100     $this->cookies = [];
101
102     // 3. Session (anonymous).
103     $this->drupalGet(Url::fromRoute('user.login', [], ['query' => ['trigger_session' => 1]]));
104     $this->drupalGet(Url::fromRoute('user.login'));
105     $this->assertSessionCookieExists(TRUE);
106     $this->assertBigPipeNoJsCookieExists(FALSE);
107     $this->assertRaw('<noscript><meta http-equiv="Refresh" content="0; URL=' . base_path() . 'big_pipe/no-js?destination=' . base_path() . 'user/login" />' . "\n" . '</noscript>');
108     $this->assertNoRaw($no_js_to_js_markup);
109     $this->assertBigPipeNoJsMetaRefreshRedirect();
110     $this->assertBigPipeNoJsCookieExists(TRUE);
111     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
112     $this->assertRaw($no_js_to_js_markup);
113
114     // Close the prior connection and remove the collected state.
115     $this->curlClose();
116     $this->curlCookies = [];
117     $this->cookies = [];
118
119     // Edge case: route with '_no_big_pipe' option.
120     $this->drupalGet(Url::fromRoute('no_big_pipe'));
121     $this->assertSessionCookieExists(FALSE);
122     $this->assertBigPipeNoJsCookieExists(FALSE);
123     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
124     $this->assertNoRaw($no_js_to_js_markup);
125     $this->drupalLogin($this->rootUser);
126     $this->drupalGet(Url::fromRoute('no_big_pipe'));
127     $this->assertSessionCookieExists(TRUE);
128     $this->assertBigPipeNoJsCookieExists(FALSE);
129     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
130     $this->assertNoRaw($no_js_to_js_markup);
131   }
132
133   /**
134    * Tests BigPipe-delivered HTML responses when JavaScript is enabled.
135    *
136    * Covers:
137    * - \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
138    * - \Drupal\big_pipe\Render\BigPipe
139    * - \Drupal\big_pipe\Render\BigPipe::sendPlaceholders()
140    *
141    * @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
142    */
143   public function testBigPipe() {
144     // Simulate production.
145     $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
146
147     $this->drupalLogin($this->rootUser);
148     $this->assertSessionCookieExists(TRUE);
149     $this->assertBigPipeNoJsCookieExists(FALSE);
150
151     $log_count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
152
153     // By not calling performMetaRefresh() here, we simulate JavaScript being
154     // enabled, because as far as the BigPipe module is concerned, JavaScript is
155     // enabled in the browser as long as the BigPipe no-JS cookie is *not* set.
156     // @see setUp()
157     // @see performMetaRefresh()
158
159     $this->drupalGet(Url::fromRoute('big_pipe_test'));
160     $this->assertBigPipeResponseHeadersPresent();
161     $this->assertNoCacheTag('cache_tag_set_in_lazy_builder');
162
163     $this->setCsrfTokenSeedInTestEnvironment();
164     $cases = $this->getTestCases();
165     $this->assertBigPipeNoJsPlaceholders([
166       $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholder     => $cases['edge_case__invalid_html']->embeddedHtmlResponse,
167       $cases['html_attribute_value']->bigPipeNoJsPlaceholder        => $cases['html_attribute_value']->embeddedHtmlResponse,
168       $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholder => $cases['html_attribute_value_subset']->embeddedHtmlResponse,
169     ]);
170     $this->assertBigPipePlaceholders([
171       $cases['html']->bigPipePlaceholderId                             => Json::encode($cases['html']->embeddedAjaxResponseCommands),
172       $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
173       $cases['exception__lazy_builder']->bigPipePlaceholderId          => NULL,
174       $cases['exception__embedded_response']->bigPipePlaceholderId     => NULL,
175     ], [
176       0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId,
177       // The 'html' case contains the 'status messages' placeholder, which is
178       // always rendered last.
179       1 => $cases['html']->bigPipePlaceholderId,
180     ]);
181
182     $this->assertRaw('</body>', 'Closing body tag present.');
183
184     $this->pass('Verifying BigPipe assets are present…', 'Debug');
185     $this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings present.');
186     $this->assertTrue(in_array('big_pipe/big_pipe', explode(',', $this->getDrupalSettings()['ajaxPageState']['libraries'])), 'BigPipe asset library is present.');
187
188     // Verify that the two expected exceptions are logged as errors.
189     $this->assertEqual($log_count + 2, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), 'Two new watchdog entries.');
190     $records = db_query('SELECT * FROM {watchdog} ORDER BY wid DESC LIMIT 2')->fetchAll();
191     $this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
192     $this->assertTrue(FALSE !== strpos((string) unserialize($records[0]->variables)['@message'], 'Oh noes!'));
193     $this->assertEqual(RfcLogLevel::ERROR, $records[1]->severity);
194     $this->assertTrue(FALSE !== strpos((string) unserialize($records[1]->variables)['@message'], 'You are not allowed to say llamas are not cool!'));
195
196     // Verify that 4xx responses work fine. (4xx responses are handled by
197     // subrequests to a route pointing to a controller with the desired output.)
198     $this->drupalGet(Url::fromUri('base:non-existing-path'));
199
200     // Simulate development.
201     $this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
202     $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
203     $this->drupalGet(Url::fromRoute('big_pipe_test'));
204     // The 'edge_case__html_exception' case throws an exception.
205     $this->assertRaw('The website encountered an unexpected error. Please try again later');
206     $this->assertRaw('You are not allowed to say llamas are not cool!');
207     $this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent: error occurred before then.');
208     $this->assertNoRaw('</body>', 'Closing body tag absent: error occurred before then.');
209     // The exception is expected. Do not interpret it as a test failure.
210     unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
211   }
212
213   /**
214    * Tests BigPipe-delivered HTML responses when JavaScript is disabled.
215    *
216    * Covers:
217    * - \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
218    * - \Drupal\big_pipe\Render\BigPipe
219    * - \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders()
220    *
221    * @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
222    */
223   public function testBigPipeNoJs() {
224     // Simulate production.
225     $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
226
227     $this->drupalLogin($this->rootUser);
228     $this->assertSessionCookieExists(TRUE);
229     $this->assertBigPipeNoJsCookieExists(FALSE);
230
231     // By calling performMetaRefresh() here, we simulate JavaScript being
232     // disabled, because as far as the BigPipe module is concerned, it is
233     // enabled in the browser when the BigPipe no-JS cookie is set.
234     // @see setUp()
235     // @see performMetaRefresh()
236     $this->performMetaRefresh();
237     $this->assertBigPipeNoJsCookieExists(TRUE);
238
239     $this->drupalGet(Url::fromRoute('big_pipe_test'));
240     $this->assertBigPipeResponseHeadersPresent();
241     $this->assertNoCacheTag('cache_tag_set_in_lazy_builder');
242
243     $this->setCsrfTokenSeedInTestEnvironment();
244     $cases = $this->getTestCases();
245     $this->assertBigPipeNoJsPlaceholders([
246       $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholder           => $cases['edge_case__invalid_html']->embeddedHtmlResponse,
247       $cases['html_attribute_value']->bigPipeNoJsPlaceholder              => $cases['html_attribute_value']->embeddedHtmlResponse,
248       $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholder       => $cases['html_attribute_value_subset']->embeddedHtmlResponse,
249       $cases['html']->bigPipeNoJsPlaceholder                              => $cases['html']->embeddedHtmlResponse,
250       $cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholder  => $cases['edge_case__html_non_lazy_builder']->embeddedHtmlResponse,
251       $cases['exception__lazy_builder']->bigPipePlaceholderId             => NULL,
252       $cases['exception__embedded_response']->bigPipePlaceholderId        => NULL,
253     ]);
254
255     $this->pass('Verifying there are no BigPipe placeholders & replacements…', 'Debug');
256     $this->assertEqual('<none>', $this->drupalGetHeader('BigPipe-Test-Placeholders'));
257     $this->pass('Verifying BigPipe start/stop signals are absent…', 'Debug');
258     $this->assertNoRaw(BigPipe::START_SIGNAL, 'BigPipe start signal absent.');
259     $this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent.');
260
261     $this->pass('Verifying BigPipe assets are absent…', 'Debug');
262     $this->assertTrue(!isset($this->getDrupalSettings()['bigPipePlaceholderIds']) && empty($this->getDrupalSettings()['ajaxPageState']), 'BigPipe drupalSettings and BigPipe asset library absent.');
263     $this->assertRaw('</body>', 'Closing body tag present.');
264
265     // Verify that 4xx responses work fine. (4xx responses are handled by
266     // subrequests to a route pointing to a controller with the desired output.)
267     $this->drupalGet(Url::fromUri('base:non-existing-path'));
268
269     // Simulate development.
270     $this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
271     $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
272     $this->drupalGet(Url::fromRoute('big_pipe_test'));
273     // The 'edge_case__html_exception' case throws an exception.
274     $this->assertRaw('The website encountered an unexpected error. Please try again later');
275     $this->assertRaw('You are not allowed to say llamas are not cool!');
276     $this->assertNoRaw('</body>', 'Closing body tag absent: error occurred before then.');
277     // The exception is expected. Do not interpret it as a test failure.
278     unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
279   }
280
281   /**
282    * Tests BigPipe with a multi-occurrence placeholder.
283    */
284   public function testBigPipeMultiOccurrencePlaceholders() {
285     $this->drupalLogin($this->rootUser);
286     $this->assertSessionCookieExists(TRUE);
287     $this->assertBigPipeNoJsCookieExists(FALSE);
288
289     // By not calling performMetaRefresh() here, we simulate JavaScript being
290     // enabled, because as far as the BigPipe module is concerned, JavaScript is
291     // enabled in the browser as long as the BigPipe no-JS cookie is *not* set.
292     // @see setUp()
293     // @see performMetaRefresh()
294
295     $this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
296     $big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
297     $expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
298     $this->assertRaw('The count is 1.');
299     $this->assertNoRaw('The count is 2.');
300     $this->assertNoRaw('The count is 3.');
301     $raw_content = $this->getRawContent();
302     $this->assertTrue(substr_count($raw_content, $expected_placeholder_replacement) == 1, 'Only one placeholder replacement was found for the duplicate #lazy_builder arrays.');
303
304     // By calling performMetaRefresh() here, we simulate JavaScript being
305     // disabled, because as far as the BigPipe module is concerned, it is
306     // enabled in the browser when the BigPipe no-JS cookie is set.
307     // @see setUp()
308     // @see performMetaRefresh()
309     $this->performMetaRefresh();
310     $this->assertBigPipeNoJsCookieExists(TRUE);
311     $this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
312     $this->assertRaw('The count is 1.');
313     $this->assertNoRaw('The count is 2.');
314     $this->assertNoRaw('The count is 3.');
315   }
316
317   protected function assertBigPipeResponseHeadersPresent() {
318     $this->pass('Verifying BigPipe response headers…', 'Debug');
319     $this->assertTrue(FALSE !== strpos($this->drupalGetHeader('Cache-Control'), 'private'), 'Cache-Control header set to "private".');
320     $this->assertEqual('no-store, content="BigPipe/1.0"', $this->drupalGetHeader('Surrogate-Control'));
321     $this->assertEqual('no', $this->drupalGetHeader('X-Accel-Buffering'));
322   }
323
324   /**
325    * Asserts expected BigPipe no-JS placeholders are present and replaced.
326    *
327    * @param array $expected_big_pipe_nojs_placeholders
328    *   Keys: BigPipe no-JS placeholder markup. Values: expected replacement
329    *   markup.
330    */
331   protected function assertBigPipeNoJsPlaceholders(array $expected_big_pipe_nojs_placeholders) {
332     $this->pass('Verifying BigPipe no-JS placeholders & replacements…', 'Debug');
333     $this->assertSetsEqual(array_keys($expected_big_pipe_nojs_placeholders), array_map('rawurldecode', explode(' ', $this->drupalGetHeader('BigPipe-Test-No-Js-Placeholders'))));
334     foreach ($expected_big_pipe_nojs_placeholders as $big_pipe_nojs_placeholder => $expected_replacement) {
335       $this->pass('Checking whether the replacement for the BigPipe no-JS placeholder "' . $big_pipe_nojs_placeholder . '" is present:');
336       $this->assertNoRaw($big_pipe_nojs_placeholder);
337       if ($expected_replacement !== NULL) {
338         $this->assertRaw($expected_replacement);
339       }
340     }
341   }
342
343   /**
344    * Asserts expected BigPipe placeholders are present and replaced.
345    *
346    * @param array $expected_big_pipe_placeholders
347    *   Keys: BigPipe placeholder IDs. Values: expected AJAX response.
348    * @param array $expected_big_pipe_placeholder_stream_order
349    *   Keys: BigPipe placeholder IDs. Values: expected AJAX response. Keys are
350    *   defined in the order that they are expected to be rendered & streamed.
351    */
352   protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders, array $expected_big_pipe_placeholder_stream_order) {
353     $this->pass('Verifying BigPipe placeholders & replacements…', 'Debug');
354     $this->assertSetsEqual(array_keys($expected_big_pipe_placeholders), explode(' ', $this->drupalGetHeader('BigPipe-Test-Placeholders')));
355     $placeholder_positions = [];
356     $placeholder_replacement_positions = [];
357     foreach ($expected_big_pipe_placeholders as $big_pipe_placeholder_id => $expected_ajax_response) {
358       $this->pass('BigPipe placeholder: ' . $big_pipe_placeholder_id, 'Debug');
359       // Verify expected placeholder.
360       $expected_placeholder_html = '<span data-big-pipe-placeholder-id="' . $big_pipe_placeholder_id . '"></span>';
361       $this->assertRaw($expected_placeholder_html, 'BigPipe placeholder for placeholder ID "' . $big_pipe_placeholder_id . '" found.');
362       $pos = strpos($this->getRawContent(), $expected_placeholder_html);
363       $placeholder_positions[$pos] = $big_pipe_placeholder_id;
364       // Verify expected placeholder replacement.
365       $expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
366       $result = $this->xpath('//script[@data-big-pipe-replacement-for-placeholder-with-id=:id]', [':id' => Html::decodeEntities($big_pipe_placeholder_id)]);
367       if ($expected_ajax_response === NULL) {
368         $this->assertEqual(0, count($result));
369         $this->assertNoRaw($expected_placeholder_replacement);
370         continue;
371       }
372       $this->assertEqual($expected_ajax_response, trim((string) $result[0]));
373       $this->assertRaw($expected_placeholder_replacement);
374       $pos = strpos($this->getRawContent(), $expected_placeholder_replacement);
375       $placeholder_replacement_positions[$pos] = $big_pipe_placeholder_id;
376     }
377     ksort($placeholder_positions, SORT_NUMERIC);
378     $this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
379     $placeholders = array_map(function(\SimpleXMLElement $element) { return (string) $element['data-big-pipe-placeholder-id']; }, $this->cssSelect('[data-big-pipe-placeholder-id]'));
380     $this->assertEqual(count($expected_big_pipe_placeholders), count(array_unique($placeholders)));
381     $expected_big_pipe_placeholders_with_replacements = [];
382     foreach ($expected_big_pipe_placeholder_stream_order as $big_pipe_placeholder_id) {
383       $expected_big_pipe_placeholders_with_replacements[$big_pipe_placeholder_id] = $expected_big_pipe_placeholders[$big_pipe_placeholder_id];
384     }
385     $this->assertEqual($expected_big_pipe_placeholders_with_replacements, array_filter($expected_big_pipe_placeholders));
386     $this->assertSetsEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
387     $this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
388
389     $this->pass('Verifying BigPipe start/stop signals…', 'Debug');
390     $this->assertRaw(BigPipe::START_SIGNAL, 'BigPipe start signal present.');
391     $this->assertRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal present.');
392     $start_signal_position = strpos($this->getRawContent(), BigPipe::START_SIGNAL);
393     $stop_signal_position = strpos($this->getRawContent(), BigPipe::STOP_SIGNAL);
394     $this->assertTrue($start_signal_position < $stop_signal_position, 'BigPipe start signal appears before stop signal.');
395
396     $this->pass('Verifying BigPipe placeholder replacements and start/stop signals were streamed in the correct order…', 'Debug');
397     $expected_stream_order = array_keys($expected_big_pipe_placeholders_with_replacements);
398     array_unshift($expected_stream_order, BigPipe::START_SIGNAL);
399     array_push($expected_stream_order, BigPipe::STOP_SIGNAL);
400     $actual_stream_order = $placeholder_replacement_positions + [
401         $start_signal_position => BigPipe::START_SIGNAL,
402         $stop_signal_position => BigPipe::STOP_SIGNAL,
403       ];
404     ksort($actual_stream_order, SORT_NUMERIC);
405     $this->assertEqual($expected_stream_order, array_values($actual_stream_order));
406   }
407
408   /**
409    * Ensures CSRF tokens can be generated for the current user's session.
410    */
411   protected function setCsrfTokenSeedInTestEnvironment() {
412     $session_data = $this->container->get('session_handler.write_safe')->read($this->cookies[$this->getSessionName()]['value']);
413     $csrf_token_seed = unserialize(explode('_sf2_meta|', $session_data)[1])['s'];
414     $this->container->get('session_manager.metadata_bag')->setCsrfTokenSeed($csrf_token_seed);
415   }
416
417   /**
418    * @return \Drupal\big_pipe\Tests\BigPipePlaceholderTestCase[]
419    */
420   protected function getTestCases($has_session = TRUE) {
421     return BigPipePlaceholderTestCases::cases($this->container, $this->rootUser);
422   }
423
424   /**
425    * Asserts whether arrays A and B are equal, when treated as sets.
426    */
427   protected function assertSetsEqual(array $a, array $b) {
428     return count($a) == count($b) && !array_diff_assoc($a, $b);
429   }
430
431   /**
432    * Asserts whether a BigPipe no-JS cookie exists or not.
433    */
434   protected function assertBigPipeNoJsCookieExists($expected) {
435     $this->assertCookieExists('big_pipe_nojs', $expected, 'BigPipe no-JS');
436   }
437
438   /**
439    * Asserts whether a session cookie exists or not.
440    */
441   protected function assertSessionCookieExists($expected) {
442     $this->assertCookieExists($this->getSessionName(), $expected, 'Session');
443   }
444
445   /**
446    * Asserts whether a cookie exists on the client or not.
447    */
448   protected function assertCookieExists($cookie_name, $expected, $cookie_label) {
449     $non_deleted_cookies = array_filter($this->cookies, function ($item) { return $item['value'] !== 'deleted'; });
450     $this->assertEqual($expected, isset($non_deleted_cookies[$cookie_name]), $expected ? "$cookie_label cookie exists." : "$cookie_label cookie does not exist.");
451   }
452
453   /**
454    * Calls ::performMetaRefresh() and asserts the responses.
455    */
456   protected function assertBigPipeNoJsMetaRefreshRedirect() {
457     $original_url = $this->url;
458     $this->performMetaRefresh();
459
460     $this->assertEqual($original_url, $this->url, 'Redirected back to the original location.');
461
462     $headers = $this->drupalGetHeaders(TRUE);
463     $this->assertEqual(2, count($headers), 'Two requests were made upon following the <meta> refresh, there are headers for two responses.');
464
465     // First response: redirect.
466     $this->assertEqual('HTTP/1.1 302 Found', $headers[0][':status'], 'The first response was a 302 (redirect).');
467     $this->assertIdentical(0, strpos($headers[0]['set-cookie'], 'big_pipe_nojs=1'), 'The first response sets the big_pipe_nojs cookie.');
468     $this->assertEqual($original_url, $headers[0]['location'], 'The first response redirected back to the original page.');
469     $this->assertTrue(empty(array_diff(['cookies:big_pipe_nojs', 'session.exists'], explode(' ', $headers[0]['x-drupal-cache-contexts']))), 'The first response varies by the "cookies:big_pipe_nojs" and "session.exists" cache contexts.');
470     $this->assertFalse(isset($headers[0]['surrogate-control']), 'The first response has no "Surrogate-Control" header.');
471
472     // Second response: redirect followed.
473     $this->assertEqual('HTTP/1.1 200 OK', $headers[1][':status'], 'The second response was a 200.');
474     $this->assertTrue(empty(array_diff(['cookies:big_pipe_nojs', 'session.exists'], explode(' ', $headers[0]['x-drupal-cache-contexts']))), 'The first response varies by the "cookies:big_pipe_nojs" and "session.exists" cache contexts.');
475     $this->assertEqual('no-store, content="BigPipe/1.0"', $headers[1]['surrogate-control'], 'The second response has a "Surrogate-Control" header.');
476
477     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=', 'Once the BigPipe no-JS cookie is set, the <meta> refresh is absent: only one redirect ever happens.');
478   }
479
480 }