[TASK] Speed up functional tests using database snapshots
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Functional / SiteHandling / PlainRequestTest.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 without having site handling configured
28 */
29 class PlainRequestTest 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 public static function setUpBeforeClass()
42 {
43 parent::setUpBeforeClass();
44 static::initializeDatabaseSnapshot();
45 }
46
47 public static function tearDownAfterClass()
48 {
49 static::destroyDatabaseSnapshot();
50 parent::tearDownAfterClass();
51 }
52
53 protected function setUp()
54 {
55 parent::setUp();
56
57 // these settings are forwarded to the frontend sub-request as well
58 $this->internalRequestContext = (new InternalRequestContext())
59 ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
60
61 $this->withDatabaseSnapshot(function () {
62 $this->setUpDatabase();
63 });
64 }
65
66 protected function setUpDatabase()
67 {
68 $backendUser = $this->setUpBackendUserFromFixture(1);
69 Bootstrap::initializeLanguageObject();
70
71 $scenarioFile = __DIR__ . '/Fixtures/scenario.yaml';
72 $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
73 $writer = DataHandlerWriter::withBackendUser($backendUser);
74 $writer->invokeFactory($factory);
75 static::failIfArrayIsNotEmpty(
76 $writer->getErrors()
77 );
78
79 $this->setUpFrontendRootPage(
80 1000,
81 [
82 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
83 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
84 ],
85 [
86 'title' => 'ACME Root',
87 'sitetitle' => $this->siteTitle,
88 ]
89 );
90 $this->setUpFrontendRootPage(
91 3000,
92 [
93 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
94 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
95 ],
96 [
97 'title' => 'ACME Archive',
98 'sitetitle' => $this->siteTitle,
99 ]
100 );
101 }
102
103 protected function tearDown()
104 {
105 unset($this->internalRequestContext);
106 parent::tearDown();
107 }
108
109 /**
110 * @return array
111 */
112 public function requestsAreRedirectedDataProvider(): array
113 {
114 $domainPaths = [
115 '/',
116 'https://localhost/',
117 'https://website.local/',
118 ];
119
120 $queries = [
121 '?',
122 '?id=1000',
123 '?id=acme-root'
124 ];
125
126 return $this->wrapInArray(
127 $this->keysFromValues(
128 $this->meltStrings([$domainPaths, $queries])
129 )
130 );
131 }
132
133 /**
134 * @param string $uri
135 *
136 * @test
137 * @dataProvider requestsAreRedirectedDataProvider
138 */
139 public function requestsAreRedirected(string $uri)
140 {
141 $expectedStatusCode = 307;
142 $expectedHeaders = ['location' => ['index.php?id=acme-first']];
143
144 $response = $this->executeFrontendRequest(
145 new InternalRequest($uri),
146 $this->internalRequestContext
147 );
148 static::assertSame($expectedStatusCode, $response->getStatusCode());
149 static::assertSame($expectedHeaders, $response->getHeaders());
150 }
151
152 /**
153 * @return array
154 */
155 public function pageIsRenderedDataProvider(): array
156 {
157 $domainPaths = [
158 '/',
159 'https://localhost/',
160 'https://website.local/',
161 ];
162
163 $queries = [
164 '?id=1100',
165 '?id=acme-first',
166 ];
167
168 $languageQueries = [
169 '',
170 '&L=0',
171 '&L=1',
172 '&L=2',
173 ];
174
175 return array_map(
176 function (string $uri) {
177 if (strpos($uri, '&L=1') !== false) {
178 $expectedPageTitle = 'FR: Welcome';
179 } elseif (strpos($uri, '&L=2') !== false) {
180 $expectedPageTitle = 'FR-CA: Welcome';
181 } else {
182 $expectedPageTitle = 'EN: Welcome';
183 }
184 return [$uri, $expectedPageTitle];
185 },
186 $this->keysFromValues(
187 $this->meltStrings([$domainPaths, $queries, $languageQueries])
188 )
189 );
190 }
191
192 /**
193 * @param string $uri
194 * @param string $expectedPageTitle
195 *
196 * @test
197 * @dataProvider pageIsRenderedDataProvider
198 */
199 public function pageIsRendered(string $uri, string $expectedPageTitle)
200 {
201 $response = $this->executeFrontendRequest(
202 new InternalRequest($uri),
203 $this->internalRequestContext
204 );
205 $responseStructure = ResponseContent::fromString(
206 (string)$response->getBody()
207 );
208
209 static::assertSame(
210 200,
211 $response->getStatusCode()
212 );
213 static::assertSame(
214 $this->siteTitle,
215 $responseStructure->getScopePath('template/sitetitle')
216 );
217 static::assertSame(
218 $expectedPageTitle,
219 $responseStructure->getScopePath('page/title')
220 );
221 }
222
223 /**
224 * @return array
225 */
226 public function pageIsRenderedWithDomainsDataProvider(): array
227 {
228 $instructions = [
229 ['https://archive.acme.com/?id=3100', 'EN: Statistics'],
230 ['https://archive.acme.com/?id=3110', 'EN: Markets'],
231 ['https://archive.acme.com/?id=3120', 'EN: Products'],
232 ['https://archive.acme.com/?id=3130', 'EN: Partners'],
233 ];
234
235 return $this->keysFromTemplate($instructions, '%1$s');
236 }
237
238 /**
239 * @param string $uri
240 * @param string $expectedPageTitle
241 *
242 * @test
243 * @dataProvider pageIsRenderedWithDomainsDataProvider
244 */
245 public function pageIsRenderedWithDomains(string $uri, string $expectedPageTitle)
246 {
247 $response = $this->executeFrontendRequest(
248 new InternalRequest($uri),
249 $this->internalRequestContext
250 );
251 $responseStructure = ResponseContent::fromString(
252 (string)$response->getBody()
253 );
254
255 static::assertSame(
256 200,
257 $response->getStatusCode()
258 );
259 static::assertSame(
260 $this->siteTitle,
261 $responseStructure->getScopePath('template/sitetitle')
262 );
263 static::assertSame(
264 $expectedPageTitle,
265 $responseStructure->getScopePath('page/title')
266 );
267 }
268
269 /**
270 * @return array
271 */
272 public function restrictedPageIsRenderedDataProvider(): array
273 {
274 $instructions = [
275 // frontend user 1
276 ['https://website.local/?id=1510', 1, 'Whitepapers'],
277 ['https://website.local/?id=1511', 1, 'Products'],
278 ['https://website.local/?id=1512', 1, 'Solutions'],
279 // frontend user 2
280 ['https://website.local/?id=1510', 2, 'Whitepapers'],
281 ['https://website.local/?id=1511', 2, 'Products'],
282 ['https://website.local/?id=1515', 2, 'Research'],
283 ['https://website.local/?id=1520', 2, 'Forecasts'],
284 ['https://website.local/?id=1521', 2, 'Current Year'],
285 // frontend user 3
286 ['https://website.local/?id=1510', 3, 'Whitepapers'],
287 ['https://website.local/?id=1511', 3, 'Products'],
288 ['https://website.local/?id=1512', 3, 'Solutions'],
289 ['https://website.local/?id=1515', 3, 'Research'],
290 ['https://website.local/?id=1520', 3, 'Forecasts'],
291 ['https://website.local/?id=1521', 3, 'Current Year'],
292 ];
293
294 return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
295 }
296
297 /**
298 * @param string $uri
299 * @param int $frontendUserId
300 * @param string $expectedPageTitle
301 *
302 * @test
303 * @dataProvider restrictedPageIsRenderedDataProvider
304 */
305 public function restrictedPageIsRendered(string $uri, int $frontendUserId, string $expectedPageTitle)
306 {
307 $response = $this->executeFrontendRequest(
308 new InternalRequest($uri),
309 $this->internalRequestContext
310 ->withFrontendUserId($frontendUserId)
311 );
312 $responseStructure = ResponseContent::fromString(
313 (string)$response->getBody()
314 );
315
316 static::assertSame(
317 200,
318 $response->getStatusCode()
319 );
320 static::assertSame(
321 $this->siteTitle,
322 $responseStructure->getScopePath('template/sitetitle')
323 );
324 static::assertSame(
325 $expectedPageTitle,
326 $responseStructure->getScopePath('page/title')
327 );
328 }
329
330 /**
331 * @return array
332 */
333 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider(): array
334 {
335 $instructions = [
336 // no frontend user given
337 ['https://website.local/?id=1510', 0],
338 ['https://website.local/?id=1511', 0],
339 ['https://website.local/?id=1512', 0],
340 ['https://website.local/?id=1515', 0],
341 ['https://website.local/?id=1520', 0],
342 ['https://website.local/?id=1521', 0],
343 // frontend user 1
344 ['https://website.local/?id=1515', 1],
345 ['https://website.local/?id=1520', 1],
346 ['https://website.local/?id=1521', 1],
347 // frontend user 2
348 ['https://website.local/?id=1512', 2],
349 ];
350
351 return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
352 }
353
354 /**
355 * @param string $uri
356 * @param int $frontendUserId
357 *
358 * @test
359 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
360 */
361 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorUsingDefaultErrorHandling(string $uri, int $frontendUserId)
362 {
363 $response = $this->executeFrontendRequest(
364 new InternalRequest($uri),
365 $this->internalRequestContext
366 ->withFrontendUserId($frontendUserId)
367 );
368
369 static::assertSame(
370 403,
371 $response->getStatusCode()
372 );
373 static::assertThat(
374 (string)$response->getBody(),
375 static::logicalOr(
376 static::stringContains('Reason: ID was not an accessible page'),
377 static::stringContains('Reason: Subsection was found and not accessible')
378 )
379 );
380 }
381
382 /**
383 * @param string $uri
384 * @param int $frontendUserId
385 *
386 * @test
387 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
388 */
389 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorUsingCustomErrorHandling(string $uri, int $frontendUserId)
390 {
391 $response = $this->executeFrontendRequest(
392 new InternalRequest($uri),
393 $this->internalRequestContext
394 ->withFrontendUserId($frontendUserId)
395 ->withMergedGlobalSettings([
396 'TYPO3_CONF_VARS' => [
397 'FE' => [
398 'pageNotFound_handling' => 'READFILE:typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/PageError.txt',
399 ]
400 ]
401 ])
402 );
403
404 static::assertSame(
405 403,
406 $response->getStatusCode()
407 );
408 static::assertThat(
409 (string)$response->getBody(),
410 static::logicalOr(
411 static::stringContains('reason: ID was not an accessible page'),
412 static::stringContains('reason: Subsection was found and not accessible')
413 )
414 );
415 }
416
417 /**
418 * @return array
419 */
420 public function pageRenderingStopsWithInvalidCacheHashDataProvider(): array
421 {
422 $domainPaths = [
423 '/',
424 'https://localhost/',
425 'https://website.local/',
426 ];
427
428 $queries = [
429 '?',
430 '?id=1000',
431 '?id=acme-root',
432 '?id=1100',
433 '?id=acme-first',
434 ];
435
436 $customQueries = [
437 '&testing[value]=1',
438 '&testing[value]=1&cHash=',
439 '&testing[value]=1&cHash=WRONG',
440 ];
441
442 return $this->wrapInArray(
443 $this->keysFromValues(
444 $this->meltStrings([$domainPaths, $queries, $customQueries])
445 )
446 );
447 }
448
449 /**
450 * @param string $uri
451 *
452 * @test
453 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
454 * @todo In TYPO3 v8 this seemed to be rendered, without throwing that exception
455 */
456 public function pageRequestThrowsExceptionWithInvalidCacheHash(string $uri)
457 {
458 $this->expectExceptionCode(1518472189);
459 $this->expectException(PageNotFoundException::class);
460
461 $this->executeFrontendRequest(
462 new InternalRequest($uri),
463 $this->internalRequestContext
464 );
465 }
466
467 /**
468 * @param string $uri
469 *
470 * @test
471 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
472 */
473 public function pageRequestSendsNotFoundResponseWithInvalidCacheHash(string $uri)
474 {
475 $response = $this->executeFrontendRequest(
476 new InternalRequest($uri),
477 $this->internalRequestContext->withMergedGlobalSettings([
478 'TYPO3_CONF_VARS' => [
479 'FE' => [
480 'pageNotFound_handling' => 'READFILE:typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/PageError.txt',
481 ]
482 ]
483 ])
484 );
485
486 static::assertSame(
487 404,
488 $response->getStatusCode()
489 );
490 static::assertThat(
491 (string)$response->getBody(),
492 static::logicalOr(
493 static::stringContains('reason: Request parameters could not be validated (&amp;cHash empty)'),
494 static::stringContains('reason: Request parameters could not be validated (&amp;cHash comparison failed)')
495 )
496 );
497 }
498
499 /**
500 * @return array
501 */
502 public function pageIsRenderedWithValidCacheHashDataProvider(): array
503 {
504 $domainPaths = [
505 '/',
506 'https://localhost/',
507 'https://website.local/',
508 ];
509
510 // cHash has been calculated with encryption key set to
511 // '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6'
512 $queries = [
513 // @todo Currently fails since cHash is verified after(!) redirect to page 1100
514 // '?&cHash=7d1f13fa91159dac7feb3c824936b39d&id=1000',
515 // '?&cHash=7d1f13fa91159dac7feb3c824936b39d=acme-root',
516 '?&cHash=f42b850e435f0cedd366f5db749fc1af&id=1100',
517 '?&cHash=f42b850e435f0cedd366f5db749fc1af&id=acme-first',
518 ];
519
520 $customQueries = [
521 '&testing[value]=1',
522 ];
523
524 $dataSet = $this->wrapInArray(
525 $this->keysFromValues(
526 $this->meltStrings([$domainPaths, $queries, $customQueries])
527 )
528 );
529
530 return $dataSet;
531 }
532
533 /**
534 * @param string $uri
535 *
536 * @test
537 * @dataProvider pageIsRenderedWithValidCacheHashDataProvider
538 */
539 public function pageIsRenderedWithValidCacheHash($uri)
540 {
541 $response = $this->executeFrontendRequest(
542 new InternalRequest($uri),
543 $this->internalRequestContext
544 );
545 $responseStructure = ResponseContent::fromString(
546 (string)$response->getBody()
547 );
548 static::assertSame(
549 '1',
550 $responseStructure->getScopePath('getpost/testing.value')
551 );
552 }
553 }