[TASK] Reduce amount of executed functional frontend tests
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Functional / SiteHandling / SiteRequestTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Core\Bootstrap;
19 use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
20 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
21 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
22 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
23 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
24 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
25
26 /**
27 * Test case for frontend requests having site handling configured
28 */
29 class SiteRequestTest extends AbstractTestCase
30 {
31 /**
32 * @var string
33 */
34 private $siteTitle = 'A Company that Manufactures Everything Inc';
35
36 /**
37 * @var InternalRequestContext
38 */
39 private $internalRequestContext;
40
41 protected function setUp()
42 {
43 parent::setUp();
44
45 // these settings are forwarded to the frontend sub-request as well
46 $this->internalRequestContext = (new InternalRequestContext())
47 ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
48
49 $backendUser = $this->setUpBackendUserFromFixture(1);
50 Bootstrap::initializeLanguageObject();
51
52 $scenarioFile = __DIR__ . '/Fixtures/scenario.yaml';
53 $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
54 $writer = DataHandlerWriter::withBackendUser($backendUser);
55 $writer->invokeFactory($factory);
56 static::failIfArrayIsNotEmpty(
57 $writer->getErrors()
58 );
59
60 $this->setUpFrontendRootPage(
61 1000,
62 [
63 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
64 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
65 ],
66 [
67 'title' => 'ACME Root',
68 'sitetitle' => $this->siteTitle,
69 ]
70 );
71 }
72
73 protected function tearDown()
74 {
75 unset($this->internalRequestContext);
76 parent::tearDown();
77 }
78
79 /**
80 * @return array
81 */
82 public function requestsAreRedirectedDataProvider(): array
83 {
84 $domainPaths = [
85 '/',
86 'https://localhost/',
87 'https://website.local/',
88 ];
89
90 $queries = [
91 '?',
92 '?id=1000',
93 '?id=acme-root'
94 ];
95
96 return $this->wrapInArray(
97 $this->keysFromValues(
98 $this->meltStrings([$domainPaths, $queries])
99 )
100 );
101 }
102
103 /**
104 * @param string $uri
105 *
106 * @test
107 * @dataProvider requestsAreRedirectedDataProvider
108 */
109 public function requestsAreRedirected(string $uri)
110 {
111 $this->writeSiteConfiguration(
112 'website-local',
113 $this->buildSiteConfiguration(1000, 'https://website.local/')
114 );
115
116 $expectedStatusCode = 307;
117 $expectedHeaders = ['location' => ['/?id=acme-first']];
118
119 $response = $this->executeFrontendRequest(
120 new InternalRequest($uri),
121 $this->internalRequestContext
122 );
123 static::assertSame($expectedStatusCode, $response->getStatusCode());
124 static::assertSame($expectedHeaders, $response->getHeaders());
125 }
126
127 /**
128 * @return array
129 */
130 public function pageIsRenderedWithPathsDataProvider(): array
131 {
132 $domainPaths = [
133 // @todo currently base needs to be defined with domain
134 // '/',
135 'https://website.local/',
136 ];
137
138 $languagePaths = [
139 '',
140 'en-en/',
141 'fr-fr/',
142 'fr-ca/',
143 ];
144
145 $queries = [
146 '?id=1100',
147 '?id=acme-first',
148 ];
149
150 return array_map(
151 function (string $uri) {
152 if (strpos($uri, '/fr-fr/') !== false) {
153 $expectedPageTitle = 'FR: Welcome';
154 } elseif (strpos($uri, '/fr-ca/') !== false) {
155 $expectedPageTitle = 'FR-CA: Welcome';
156 } else {
157 $expectedPageTitle = 'EN: Welcome';
158 }
159 return [$uri, $expectedPageTitle];
160 },
161 $this->keysFromValues(
162 $this->meltStrings([$domainPaths, $languagePaths, $queries])
163 )
164 );
165 }
166
167 /**
168 * @param string $uri
169 * @param string $expectedPageTitle
170 *
171 * @test
172 * @dataProvider pageIsRenderedWithPathsDataProvider
173 */
174 public function pageIsRenderedWithPaths(string $uri, string $expectedPageTitle)
175 {
176 $this->writeSiteConfiguration(
177 'website-local',
178 $this->buildSiteConfiguration(1000, 'https://website.local/'),
179 [
180 $this->buildDefaultLanguageConfiguration('EN', '/en-en/'),
181 $this->buildLanguageConfiguration('FR', '/fr-fr/', ['EN']),
182 $this->buildLanguageConfiguration('FR-CA', '/fr-ca/', ['FR', 'EN']),
183 ]
184 );
185
186 $response = $this->executeFrontendRequest(
187 new InternalRequest($uri),
188 $this->internalRequestContext
189 );
190 $responseStructure = ResponseContent::fromString(
191 (string)$response->getBody()
192 );
193
194 static::assertSame(
195 200,
196 $response->getStatusCode()
197 );
198 static::assertSame(
199 $this->siteTitle,
200 $responseStructure->getScopePath('template/sitetitle')
201 );
202 static::assertSame(
203 $expectedPageTitle,
204 $responseStructure->getScopePath('page/title')
205 );
206 }
207
208 /**
209 * @return array
210 */
211 public function pageIsRenderedWithDomainsDataProvider(): array
212 {
213 $domainPaths = [
214 'https://website.local/',
215 'https://website.us/',
216 'https://website.fr/',
217 'https://website.ca/',
218 'https://website.other/',
219 ];
220
221 $queries = [
222 '?id=1100',
223 '?id=acme-first',
224 ];
225
226 return array_map(
227 function (string $uri) {
228 if (strpos($uri, '.fr/') !== false) {
229 $expectedPageTitle = 'FR: Welcome';
230 } elseif (strpos($uri, '.ca/') !== false) {
231 $expectedPageTitle = 'FR-CA: Welcome';
232 } else {
233 $expectedPageTitle = 'EN: Welcome';
234 }
235 return [$uri, $expectedPageTitle];
236 },
237 $this->keysFromValues(
238 $this->meltStrings([$domainPaths, $queries])
239 )
240 );
241 }
242
243 /**
244 * @param string $uri
245 * @param string $expectedPageTitle
246 *
247 * @test
248 * @dataProvider pageIsRenderedWithDomainsDataProvider
249 */
250 public function pageIsRenderedWithDomains(string $uri, string $expectedPageTitle)
251 {
252 $this->writeSiteConfiguration(
253 'website-local',
254 $this->buildSiteConfiguration(1000, 'https://website.local/'),
255 [
256 $this->buildDefaultLanguageConfiguration('EN', 'https://website.us/'),
257 $this->buildLanguageConfiguration('FR', 'https://website.fr/', ['EN']),
258 $this->buildLanguageConfiguration('FR-CA', 'https://website.ca/', ['FR', 'EN']),
259 ]
260 );
261
262 $response = $this->executeFrontendRequest(
263 new InternalRequest($uri),
264 $this->internalRequestContext
265 );
266 $responseStructure = ResponseContent::fromString(
267 (string)$response->getBody()
268 );
269
270 static::assertSame(
271 200,
272 $response->getStatusCode()
273 );
274 static::assertSame(
275 $this->siteTitle,
276 $responseStructure->getScopePath('template/sitetitle')
277 );
278 static::assertSame(
279 $expectedPageTitle,
280 $responseStructure->getScopePath('page/title')
281 );
282 }
283
284 /**
285 * @return array
286 */
287 public function restrictedPageIsRenderedDataProvider(): array
288 {
289 $instructions = [
290 // frontend user 1
291 ['https://website.local/?id=1510', 1, 'Whitepapers'],
292 ['https://website.local/?id=1511', 1, 'Products'],
293 ['https://website.local/?id=1512', 1, 'Solutions'],
294 // frontend user 2
295 ['https://website.local/?id=1510', 2, 'Whitepapers'],
296 ['https://website.local/?id=1511', 2, 'Products'],
297 ['https://website.local/?id=1515', 2, 'Research'],
298 ['https://website.local/?id=1520', 2, 'Forecasts'],
299 ['https://website.local/?id=1521', 2, 'Current Year'],
300 // frontend user 3
301 ['https://website.local/?id=1510', 3, 'Whitepapers'],
302 ['https://website.local/?id=1511', 3, 'Products'],
303 ['https://website.local/?id=1512', 3, 'Solutions'],
304 ['https://website.local/?id=1515', 3, 'Research'],
305 ['https://website.local/?id=1520', 3, 'Forecasts'],
306 ['https://website.local/?id=1521', 3, 'Current Year'],
307 ];
308
309 return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
310 }
311
312 /**
313 * @param string $uri
314 * @param int $frontendUserId
315 * @param string $expectedPageTitle
316 *
317 * @test
318 * @dataProvider restrictedPageIsRenderedDataProvider
319 */
320 public function restrictedPageIsRendered(string $uri, int $frontendUserId, string $expectedPageTitle)
321 {
322 $this->writeSiteConfiguration(
323 'website-local',
324 $this->buildSiteConfiguration(1000, 'https://website.local/')
325 );
326
327 $response = $this->executeFrontendRequest(
328 new InternalRequest($uri),
329 $this->internalRequestContext
330 ->withFrontendUserId($frontendUserId)
331 );
332 $responseStructure = ResponseContent::fromString(
333 (string)$response->getBody()
334 );
335
336 static::assertSame(
337 200,
338 $response->getStatusCode()
339 );
340 static::assertSame(
341 $this->siteTitle,
342 $responseStructure->getScopePath('template/sitetitle')
343 );
344 static::assertSame(
345 $expectedPageTitle,
346 $responseStructure->getScopePath('page/title')
347 );
348 }
349
350 /**
351 * @return array
352 */
353 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider(): array
354 {
355 $instructions = [
356 // no frontend user given
357 ['https://website.local/?id=1510', 0],
358 ['https://website.local/?id=1511', 0],
359 ['https://website.local/?id=1512', 0],
360 ['https://website.local/?id=1515', 0],
361 ['https://website.local/?id=1520', 0],
362 ['https://website.local/?id=1521', 0],
363 // frontend user 1
364 ['https://website.local/?id=1515', 1],
365 ['https://website.local/?id=1520', 1],
366 ['https://website.local/?id=1521', 1],
367 // frontend user 2
368 ['https://website.local/?id=1512', 2],
369 ];
370
371 return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
372 }
373
374 /**
375 * @param string $uri
376 * @param int $frontendUserId
377 *
378 * @test
379 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
380 */
381 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorWithoutHavingErrorHandling(string $uri, int $frontendUserId)
382 {
383 $this->writeSiteConfiguration(
384 'website-local',
385 $this->buildSiteConfiguration(1000, 'https://website.local/')
386 );
387
388 $response = $this->executeFrontendRequest(
389 new InternalRequest($uri),
390 $this->internalRequestContext
391 ->withFrontendUserId($frontendUserId)
392 );
393
394 static::assertSame(
395 403,
396 $response->getStatusCode()
397 );
398 static::assertThat(
399 (string)$response->getBody(),
400 static::logicalOr(
401 static::stringContains('Reason: ID was not an accessible page'),
402 static::stringContains('Reason: Subsection was found and not accessible')
403 )
404 );
405 }
406
407 /**
408 * @param string $uri
409 * @param int $frontendUserId
410 *
411 * @test
412 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
413 */
414 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorWithHavingFluidErrorHandling(string $uri, int $frontendUserId)
415 {
416 $this->writeSiteConfiguration(
417 'website-local',
418 $this->buildSiteConfiguration(1000, 'https://website.local/'),
419 [],
420 $this->buildErrorHandlingConfiguration('Fluid', [403])
421 );
422
423 $response = $this->executeFrontendRequest(
424 new InternalRequest($uri),
425 $this->internalRequestContext
426 ->withFrontendUserId($frontendUserId)
427 );
428
429 static::assertSame(
430 403,
431 $response->getStatusCode()
432 );
433 static::assertContains(
434 'reasons: code,fe_group',
435 (string)$response->getBody()
436 );
437 static::assertThat(
438 (string)$response->getBody(),
439 static::logicalOr(
440 static::stringContains('message: ID was not an accessible page'),
441 static::stringContains('message: Subsection was found and not accessible')
442 )
443 );
444 }
445
446 /**
447 * @param string $uri
448 * @param int $frontendUserId
449 *
450 * @test
451 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
452 * @todo Response body cannot be asserted since PageContentErrorHandler::handlePageError executes request via HTTP (not internally)
453 */
454 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorWithHavingPageErrorHandling(string $uri, int $frontendUserId)
455 {
456 $this->markTestSkipped('Skipped until PageContentErrorHandler::handlePageError does not use HTTP anymore');
457
458 $this->writeSiteConfiguration(
459 'website-local',
460 $this->buildSiteConfiguration(1000, 'https://website.local/'),
461 [],
462 $this->buildErrorHandlingConfiguration('Page', [403])
463 );
464
465 $response = $this->executeFrontendRequest(
466 new InternalRequest($uri),
467 $this->internalRequestContext
468 ->withFrontendUserId($frontendUserId)
469 );
470
471 static::assertSame(
472 403,
473 $response->getStatusCode()
474 );
475 }
476
477 /**
478 * @param string $uri
479 * @param int $frontendUserId
480 *
481 * @test
482 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
483 */
484 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorWithHavingPhpErrorHandling(string $uri, int $frontendUserId)
485 {
486 $this->writeSiteConfiguration(
487 'website-local',
488 $this->buildSiteConfiguration(1000, 'https://website.local/'),
489 [],
490 $this->buildErrorHandlingConfiguration('PHP', [403])
491 );
492
493 $response = $this->executeFrontendRequest(
494 new InternalRequest($uri),
495 $this->internalRequestContext
496 ->withFrontendUserId($frontendUserId)
497 );
498 $json = json_decode((string)$response->getBody(), true);
499
500 static::assertSame(
501 403,
502 $response->getStatusCode()
503 );
504 static::assertThat(
505 $json['message'] ?? null,
506 static::logicalOr(
507 static::identicalTo('ID was not an accessible page'),
508 static::identicalTo('Subsection was found and not accessible')
509 )
510 );
511 }
512
513 /**
514 * @return array
515 */
516 public function pageRenderingStopsWithInvalidCacheHashDataProvider(): array
517 {
518 $domainPaths = [
519 'https://website.local/',
520 ];
521
522 $queries = [
523 '?',
524 '?id=1000',
525 '?id=acme-root',
526 '?id=1100',
527 '?id=acme-first',
528 ];
529
530 $customQueries = [
531 '&testing[value]=1',
532 '&testing[value]=1&cHash=',
533 '&testing[value]=1&cHash=WRONG',
534 ];
535
536 return $this->wrapInArray(
537 $this->keysFromValues(
538 $this->meltStrings([$domainPaths, $queries, $customQueries])
539 )
540 );
541 }
542
543 /**
544 * @param string $uri
545 *
546 * @test
547 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
548 */
549 public function pageRequestThrowsExceptionWithInvalidCacheHashWithoutHavingErrorHandling(string $uri)
550 {
551 $this->writeSiteConfiguration(
552 'website-local',
553 $this->buildSiteConfiguration(1000, 'https://website.local/')
554 );
555
556 $this->expectExceptionCode(1518472189);
557 $this->expectException(PageNotFoundException::class);
558
559 $this->executeFrontendRequest(
560 new InternalRequest($uri),
561 $this->internalRequestContext
562 );
563 }
564
565 /**
566 * @param string $uri
567 *
568 * @test
569 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
570 */
571 public function pageRequestSendsNotFoundResponseWithInvalidCacheHash(string $uri)
572 {
573 $response = $this->executeFrontendRequest(
574 new InternalRequest($uri),
575 $this->internalRequestContext->withMergedGlobalSettings([
576 'TYPO3_CONF_VARS' => [
577 'FE' => [
578 'pageNotFound_handling' => 'READFILE:typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/PageError.txt',
579 ]
580 ]
581 ])
582 );
583
584 static::assertSame(
585 404,
586 $response->getStatusCode()
587 );
588 static::assertThat(
589 (string)$response->getBody(),
590 static::logicalOr(
591 static::stringContains('reason: Request parameters could not be validated (&amp;cHash empty)'),
592 static::stringContains('reason: Request parameters could not be validated (&amp;cHash comparison failed)')
593 )
594 );
595 }
596
597 /**
598 * @param string $uri
599 *
600 * @test
601 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
602 */
603 public function pageRequestSendsNotFoundResponseWithInvalidCacheHashWithHavingFluidErrorHandling(string $uri)
604 {
605 $this->writeSiteConfiguration(
606 'website-local',
607 $this->buildSiteConfiguration(1000, 'https://website.local/'),
608 [],
609 $this->buildErrorHandlingConfiguration('Fluid', [404])
610 );
611
612 $response = $this->executeFrontendRequest(
613 new InternalRequest($uri),
614 $this->internalRequestContext
615 );
616
617 static::assertSame(
618 404,
619 $response->getStatusCode()
620 );
621 static::assertThat(
622 (string)$response->getBody(),
623 static::logicalOr(
624 static::stringContains('message: Request parameters could not be validated (&amp;cHash empty)'),
625 static::stringContains('message: Request parameters could not be validated (&amp;cHash comparison failed)')
626 )
627 );
628 }
629
630 /**
631 * @param string $uri
632 *
633 * @test
634 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
635 * @todo Response body cannot be asserted since PageContentErrorHandler::handlePageError executes request via HTTP (not internally)
636 */
637 public function pageRequestSendsNotFoundResponseWithInvalidCacheHashWithHavingPageErrorHandling(string $uri)
638 {
639 $this->markTestSkipped('Skipped until PageContentErrorHandler::handlePageError does not use HTTP anymore');
640
641 $this->writeSiteConfiguration(
642 'website-local',
643 $this->buildSiteConfiguration(1000, 'https://website.local/'),
644 [],
645 $this->buildErrorHandlingConfiguration('Page', [404])
646 );
647
648 $response = $this->executeFrontendRequest(
649 new InternalRequest($uri),
650 $this->internalRequestContext
651 );
652
653 static::assertSame(
654 404,
655 $response->getStatusCode()
656 );
657 }
658
659 /**
660 * @param string $uri
661 *
662 * @test
663 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
664 */
665 public function pageRequestSendsNotFoundResponseWithInvalidCacheHashWithHavingPhpErrorHandling(string $uri)
666 {
667 $this->writeSiteConfiguration(
668 'website-local',
669 $this->buildSiteConfiguration(1000, 'https://website.local/'),
670 [],
671 $this->buildErrorHandlingConfiguration('PHP', [404])
672 );
673
674 $response = $this->executeFrontendRequest(
675 new InternalRequest($uri),
676 $this->internalRequestContext
677 );
678 $json = json_decode((string)$response->getBody(), true);
679
680 static::assertSame(
681 404,
682 $response->getStatusCode()
683 );
684 static::assertThat(
685 $json['message'] ?? null,
686 static::logicalOr(
687 static::identicalTo('Request parameters could not be validated (&cHash empty)'),
688 static::identicalTo('Request parameters could not be validated (&cHash comparison failed)')
689 )
690 );
691 }
692
693 /**
694 * @return array
695 */
696 public function pageIsRenderedWithValidCacheHashDataProvider(): array
697 {
698 $domainPaths = [
699 '/',
700 'https://localhost/',
701 'https://website.local/',
702 ];
703
704 // cHash has been calculated with encryption key set to
705 // '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6'
706 $queries = [
707 // @todo Currently fails since cHash is verified after(!) redirect to page 1100
708 // '?&cHash=7d1f13fa91159dac7feb3c824936b39d&id=1000',
709 // '?&cHash=7d1f13fa91159dac7feb3c824936b39d=acme-root',
710 '?&cHash=f42b850e435f0cedd366f5db749fc1af&id=1100',
711 '?&cHash=f42b850e435f0cedd366f5db749fc1af&id=acme-first',
712 ];
713
714 $customQueries = [
715 '&testing[value]=1',
716 ];
717
718 $dataSet = $this->wrapInArray(
719 $this->keysFromValues(
720 $this->meltStrings([$domainPaths, $queries, $customQueries])
721 )
722 );
723
724 return $dataSet;
725 }
726
727 /**
728 * @param string $uri
729 *
730 * @test
731 * @dataProvider pageIsRenderedWithValidCacheHashDataProvider
732 */
733 public function pageIsRenderedWithValidCacheHash($uri)
734 {
735 $this->writeSiteConfiguration(
736 'website-local',
737 $this->buildSiteConfiguration(1000, 'https://website.local/')
738 );
739
740 $response = $this->executeFrontendRequest(
741 new InternalRequest($uri),
742 $this->internalRequestContext
743 );
744 $responseStructure = ResponseContent::fromString(
745 (string)$response->getBody()
746 );
747 static::assertSame(
748 '1',
749 $responseStructure->getScopePath('getpost/testing.value')
750 );
751 }
752 }