Commit 58892e77 authored by Oliver Hader's avatar Oliver Hader Committed by Benni Mack
Browse files

[TEST] Extend site handling link generation tests

Current side handling functional test cases only support link
resolving and rendering. The opposite part of generating links
has been introduced with this change.

Besides that, existing rendering tests have been extended for
frontend restricted page rendering.

Resolves: #85922
Releases: master
Change-Id: Ie12c2883463e5560dd03e18dab3dc3a277076815
Reviewed-on: https://review.typo3.org/57923


Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 74612910
......@@ -68,7 +68,7 @@
"fiunchinho/phpunit-randomizer": "^4.0",
"friendsofphp/php-cs-fixer": "^2.12.2",
"typo3/cms-styleguide": "~9.2.0",
"typo3/testing-framework": "~4.4.1"
"typo3/testing-framework": "~4.6.0"
},
"suggest": {
"ext-gd": "GDlib/Freetype is required for building images with text (GIFBUILDER) and can also be used to scale images",
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "05058f1ae500b8d7826e1b616caaf3ed",
"content-hash": "a31065c1e94f6c986083f4986f01d285",
"packages": [
{
"name": "cogpowered/finediff",
......@@ -4752,16 +4752,16 @@
},
{
"name": "typo3/testing-framework",
"version": "4.4.1",
"version": "4.6.0",
"source": {
"type": "git",
"url": "https://github.com/TYPO3/testing-framework.git",
"reference": "b0a8b49939817cbbd14e67e98b1c6c9fc34a68da"
"reference": "5ff512494724b6700ec7309e8205dedde175716e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/b0a8b49939817cbbd14e67e98b1c6c9fc34a68da",
"reference": "b0a8b49939817cbbd14e67e98b1c6c9fc34a68da",
"url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/5ff512494724b6700ec7309e8205dedde175716e",
"reference": "5ff512494724b6700ec7309e8205dedde175716e",
"shasum": ""
},
"require": {
......@@ -4807,7 +4807,7 @@
"tests",
"typo3"
],
"time": "2018-08-20T11:20:22+00:00"
"time": "2018-08-22T09:29:31+00:00"
},
{
"name": "webmozart/assert",
......
......@@ -13,4 +13,6 @@ if (TYPO3_MODE === 'FE') {
\TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Hook\FrontendUserHandler::class . '->initialize';
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser']['FunctionalTest'] =
\TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Hook\BackendUserHandler::class . '->initialize';
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Core/TypoScript/TemplateService']['runThroughTemplatesPostProcessing']['FunctionalTest'] =
\TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Hook\TypoScriptInstructionModifier::class . '->apply';
}
......@@ -50,7 +50,7 @@
"fiunchinho/phpunit-randomizer": "^4.0",
"friendsofphp/php-cs-fixer": "^2.12.2",
"typo3/cms-styleguide": "~9.2.0",
"typo3/testing-framework": "~4.4.1"
"typo3/testing-framework": "~4.6.0"
},
"suggest": {
"ext-fileinfo": "Used for proper file type detection in the file abstraction layer",
......
......@@ -24,7 +24,7 @@ use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
/**
* Abstract test case for frontend requests
*/
abstract class AbstractRequestTest extends FunctionalTestCase
abstract class AbstractTestCase extends FunctionalTestCase
{
protected const ENCRYPTION_KEY = '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6';
......@@ -46,9 +46,16 @@ abstract class AbstractRequestTest extends FunctionalTestCase
];
/**
* @var array
* @var string[]
*/
protected $coreExtensionsToLoad = ['frontend'];
protected $coreExtensionsToLoad = ['frontend', 'workspaces'];
/**
* @var string[]
*/
protected $pathsToLinkInTestInstance = [
'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/AdditionalConfiguration.php' => 'typo3conf/AdditionalConfiguration.php',
];
/**
* Combines string values of multiple array as cross-product into flat items.
......@@ -98,7 +105,7 @@ abstract class AbstractRequestTest extends FunctionalTestCase
}
/**
* @param array $array
* @param string[] $array
* @return array
*/
protected function keysFromValues(array $array): array
......@@ -106,6 +113,45 @@ abstract class AbstractRequestTest extends FunctionalTestCase
return array_combine($array, $array);
}
/**
* Generates key names based on a template and array items as arguments.
*
* + keysFromTemplate([[1, 2, 3], [11, 22, 33]], '%1$d->%2$d (user:%3$d)')
* + returns the following array with generated keys
* [
* '1->2 (user:3)' => [1, 2, 3],
* '11->22 (user:33)' => [11, 22, 33],
* ]
*
* @param array $array
* @param string $template
* @param callable|null $callback
* @return array
*/
protected function keysFromTemplate(array $array, string $template, callable $callback = null): array
{
$keys = array_unique(
array_map(
function (array $values) use ($template, $callback) {
if ($callback !== null) {
$values = call_user_func($callback, $values);
}
return vsprintf($template, $values);
},
$array
)
);
if (count($keys) !== count($array)) {
throw new \LogicException(
'Amount of generated keys does not match to item count.',
1534682840
);
}
return array_combine($keys, $array);
}
/**
* @param array $items
*/
......
uri: {request.uri}
message: {message}
<f:for each="{reasons}" as="reason">
reason: {reason}
</f:for>
reasons: <f:for each="{reasons}" as="reason" key="key">{key},</f:for>
{results -> f:format.json() -> f:format.raw()}
\ No newline at end of file
config {
no_cache = 1
debug = 0
xhtml_cleaning = 0
admPanel = 0
disableAllHeaderCode = 1
sendCacheHeaders = 0
sys_language_uid = 0
sys_language_mode = ignore
sys_language_overlay = 1
additionalHeaders.10.header = Content-Type: application/json; charset=utf-8
additionalHeaders.10.replace = 1
}
page = PAGE
page {
10 = USER
10.userFunc = TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Fixtures\LinkGeneratorController->mainAction
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Fixtures;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\ArrayValueInstruction;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\RequestBootstrap;
/**
* Test case for frontend requests having site handling configured
*/
class LinkGeneratorController
{
/**
* @var ContentObjectRenderer
*/
public $cObj;
public function mainAction(): string
{
$instruction = RequestBootstrap::getInternalRequest()
->getInstruction(LinkGeneratorController::class);
if (!$instruction instanceof ArrayValueInstruction) {
return '';
}
return $this->cObj->cObjGet($instruction->getArray());
}
}
......@@ -28,6 +28,8 @@ entitySettings:
columnNames: {title: 'header', type: 'CType'}
domain:
tableName: 'sys_domain'
workspace:
tableName: 'sys_workspace'
language:
tableName: 'sys_language'
columnNames: {code: 'language_isocode'}
......@@ -43,6 +45,8 @@ entitySettings:
site: {root: 1, clear: 1}
entities:
workspace:
- self: {id: 1, title: 'Workspace'}
language:
- self: {id: 1, title: 'French', code: 'fr'}
- self: {id: 2, title: 'Franco-Canadian', code: 'fr'}
......@@ -53,6 +57,8 @@ entities:
languageVariants:
- self: {id: 1101, title: 'FR: Welcome', language: 1}
- self: {id: 1102, title: 'FR-CA: Welcome', language: 2}
versionVariants:
- version: {title: 'EN: Welcome to ACME Inc', workspace: 1}
entities:
content:
- self: {title: 'EN: Content Element #1', type: *contentText}
......@@ -64,7 +70,7 @@ entities:
- self: {title: 'EN: Content Element #2', type: *contentText}
- self: {id: 1200, title: 'EN: Features'}
children:
- self: {id: 1121, title: 'EN: Frontend Editing'}
- self: {id: 1210, title: 'EN: Frontend Editing'}
- self: {id: 1300, title: 'EN: Products', root: true}
children:
- self: {id: 1310, title: 'EN: Planets'}
......@@ -75,7 +81,7 @@ entities:
- self: {id: 1510, title: 'Whitepapers', visitorGroups: -2, extendToSubpages: true}
children:
- self: {id: 1511, title: 'Products'}
- self: {id: 1512, title: 'Solutions'}
- self: {id: 1512, title: 'Solutions', visitorGroups: 10}
- self: {id: 1515, title: 'Research', visitorGroups: 20}
- self: {id: 1520, title: 'Forecasts', visitorGroups: 20, extendToSubpages: true}
children:
......@@ -84,10 +90,12 @@ entities:
- self: {id: 1523, title: 'Five Years'}
- self: {id: 1600, title: 'About us'}
- self: {id: 1700, title: 'Announcements & News', type: *pageMount, mount: 7100}
- self: {id: 404, title: 'Page not found'}
entities:
content:
- self: {title: 'EN: Page not found', type: *contentText}
- self: {id: 404, title: 'Page not found'}
- self: {id: 1930, title: 'Our Blog', type: *pageShortcut, shortcut: 2000}
- version: {id: 1950, title: 'EN: Goodbye', workspace: 1}
- self: {id: 1990, title: 'Storage', type: *pageFolder}
entities:
visitorGroup:
......@@ -108,6 +116,7 @@ entities:
children:
- self: {id: 2121, title: 'About'}
- self: {id: 2700, title: 'Announcements & News', type: *pageMount, mount: 7100}
- self: {id: 2930, title: 'ACME Inc', type: *pageShortcut, shortcut: 1000}
- self: {id: 3000, title: 'ACME Archive', type: *pageShortcut, shortcut: 'first', root: true}
children:
- self: {id: 3100, title: 'EN: Statistics'}
......
......@@ -17,8 +17,8 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\ActionService;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataMapFactory;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
......@@ -26,7 +26,7 @@ use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
/**
* Test case for frontend requests without having site handling configured
*/
class PlainRequestTest extends AbstractRequestTest
class PlainRequestTest extends AbstractTestCase
{
/**
* @var string
......@@ -38,11 +38,6 @@ class PlainRequestTest extends AbstractRequestTest
*/
private $internalRequestContext;
/**
* @var ActionService
*/
private $actionService;
protected function setUp()
{
parent::setUp();
......@@ -51,19 +46,15 @@ class PlainRequestTest extends AbstractRequestTest
$this->internalRequestContext = (new InternalRequestContext())
->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
$this->setUpBackendUserFromFixture(1);
$this->actionService = new ActionService();
$backendUser = $this->setUpBackendUserFromFixture(1);
Bootstrap::initializeLanguageObject();
$scenarioFile = __DIR__ . '/Fixtures/scenario.yaml';
$factory = DataMapFactory::fromYamlFile($scenarioFile);
$this->actionService->invoke(
$factory->getDataMap(),
[],
$factory->getSuggestedIds()
);
$factory = DataHandlerFactory::fromYamlFile($scenarioFile);
$writer = DataHandlerWriter::withBackendUser($backendUser);
$writer->invokeFactory($factory);
static::failIfArrayIsNotEmpty(
$this->actionService->getDataHandler()->errorLog
$writer->getErrors()
);
$this->setUpFrontendRootPage(
......@@ -92,10 +83,7 @@ class PlainRequestTest extends AbstractRequestTest
protected function tearDown()
{
unset(
$this->actionService,
$this->internalRequestContext
);
unset($this->internalRequestContext);
parent::tearDown();
}
......@@ -222,18 +210,14 @@ class PlainRequestTest extends AbstractRequestTest
*/
public function pageIsRenderedWithDomainsDataProvider(): array
{
$uris = [
'https://archive.acme.com/?id=3100' => 'EN: Statistics',
'https://archive.acme.com/?id=3110' => 'EN: Markets',
'https://archive.acme.com/?id=3120' => 'EN: Products',
'https://archive.acme.com/?id=3130' => 'EN: Partners',
$instructions = [
['https://archive.acme.com/?id=3100', 'EN: Statistics'],
['https://archive.acme.com/?id=3110', 'EN: Markets'],
['https://archive.acme.com/?id=3120', 'EN: Products'],
['https://archive.acme.com/?id=3130', 'EN: Partners'],
];
$data = [];
foreach ($uris as $uri => $expectation) {
$data[$uri] = [$uri, $expectation];
}
return $data;
return $this->keysFromTemplate($instructions, '%1$s');
}
/**
......@@ -267,6 +251,154 @@ class PlainRequestTest extends AbstractRequestTest
);
}
/**
* @return array
*/
public function restrictedPageIsRenderedDataProvider(): array
{
$instructions = [
// frontend user 1
['https://website.local/?id=1510', 1, 'Whitepapers'],
['https://website.local/?id=1511', 1, 'Products'],
['https://website.local/?id=1512', 1, 'Solutions'],
// frontend user 2
['https://website.local/?id=1510', 2, 'Whitepapers'],
['https://website.local/?id=1511', 2, 'Products'],
['https://website.local/?id=1515', 2, 'Research'],
['https://website.local/?id=1520', 2, 'Forecasts'],
['https://website.local/?id=1521', 2, 'Current Year'],
// frontend user 3
['https://website.local/?id=1510', 3, 'Whitepapers'],
['https://website.local/?id=1511', 3, 'Products'],
['https://website.local/?id=1512', 3, 'Solutions'],
['https://website.local/?id=1515', 3, 'Research'],
['https://website.local/?id=1520', 3, 'Forecasts'],
['https://website.local/?id=1521', 3, 'Current Year'],
];
return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
}
/**
* @param string $uri
* @param int $frontendUserId
* @param string $expectedPageTitle
*
* @test
* @dataProvider restrictedPageIsRenderedDataProvider
*/
public function restrictedPageIsRendered(string $uri, int $frontendUserId, string $expectedPageTitle)
{
$response = $this->executeFrontendRequest(
new InternalRequest($uri),
$this->internalRequestContext
->withFrontendUserId($frontendUserId)
);
$responseStructure = ResponseContent::fromString(
(string)$response->getBody()
);
static::assertSame(
200,
$response->getStatusCode()
);
static::assertSame(
$this->siteTitle,
$responseStructure->getScopePath('template/sitetitle')
);
static::assertSame(
$expectedPageTitle,
$responseStructure->getScopePath('page/title')
);
}
/**
* @return array
*/
public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider(): array
{
$instructions = [
// no frontend user given
['https://website.local/?id=1510', 0],
['https://website.local/?id=1511', 0],
['https://website.local/?id=1512', 0],
['https://website.local/?id=1515', 0],
['https://website.local/?id=1520', 0],
['https://website.local/?id=1521', 0],
// frontend user 1
['https://website.local/?id=1515', 1],
['https://website.local/?id=1520', 1],
['https://website.local/?id=1521', 1],
// frontend user 2
['https://website.local/?id=1512', 2],
];
return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
}
/**
* @param string $uri
* @param int $frontendUserId
*
* @test
* @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
*/
public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorUsingDefaultErrorHandling(string $uri, int $frontendUserId)
{
$response = $this->executeFrontendRequest(
new InternalRequest($uri),
$this->internalRequestContext
->withFrontendUserId($frontendUserId)
);
static::assertSame(
403,
$response->getStatusCode()
);
static::assertThat(
(string)$response->getBody(),
static::logicalOr(
static::stringContains('Reason: ID was not an accessible page'),
static::stringContains('Reason: Subsection was found and not accessible')
)
);
}
/**
* @param string $uri
* @param int $frontendUserId
*
* @test
* @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
*/
public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorUsingCustomErrorHandling(string $uri, int $frontendUserId)
{
$response = $this->executeFrontendRequest(
new InternalRequest($uri),
$this->internalRequestContext
->withFrontendUserId($frontendUserId)
->withMergedGlobalSettings([
'TYPO3_CONF_VARS' => [
'FE' => [
'pageNotFound_handling' => 'READFILE:typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/PageError.txt',
]
]
])
);
static::assertSame(
403,
$response->getStatusCode()
);
static::assertThat(
(string)$response->getBody(),
static::logicalOr(
static::stringContains('reason: ID was not an accessible page'),
static::stringContains('reason: Subsection was found and not accessible')
)
);
}
/**
* @return array
*/
......
......@@ -17,8 +17,8 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\ActionService;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataMapFactory;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
......@@ -26,7 +26,7 @@ use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
/**
* Test case for frontend requests having site handling configured
*/
class SiteRequestTest extends AbstractRequestTest
class SiteRequestTest extends AbstractTestCase
{
/**
* @var string
......@@ -38,11 +38,6 @@ class SiteRequestTest extends AbstractRequestTest
*/
private $internalRequestContext;
/**
* @var ActionService
*/
private $actionService;
protected function setUp()
{
parent::setUp();
......@@ -51,19 +46,15 @@ class SiteRequestTest extends AbstractRequestTest
$this->internalRequestContext = (new InternalRequestContext())
->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
$this->setUpBackendUserFromFixture(1);
$this->actionService = new ActionService();
$backendUser = $this->setUpBackendUserFromFixture(1);
Bootstrap::initializeLanguageObject();
$scenarioFile = __DIR__ . '/Fixtures/scenario.yaml';
$factory = DataMapFactory::fromYamlFile($scenarioFile);
$this->actionService->invoke(
$factory->getDataMap(),
[],
$factory->getSuggestedIds()
);
$factory = DataHandlerFactory::fromYamlFile($scenarioFile);
$writer = DataHandlerWriter::withBackendUser($backendUser);
$writer->invokeFactory($factory);
static::failIfArrayIsNotEmpty(
$this->actionService->getDataHandler()->errorLog