From 4231f415e510fb64d18545944598fc3c4358a47b Mon Sep 17 00:00:00 2001 From: Oliver Hader <oliver@typo3.org> Date: Wed, 19 Feb 2014 10:04:28 +0100 Subject: [PATCH] [TASK] Add framework for frontend rendering functional tests To ensure that actions in the backend have an accordant and correct impact in the frontend, a frontend request needs to be triggered from the functional test execution. The response of that simulated frontend request shall be delivered as JSON, exceptions need to be caught and forwarded to the test suite as well. Besides that, it is required to have a possibility to set up TypoScript configuration, modify TCA and TYPO3_CONF_VARS for further hook processing. Resolves: #55882 Releases: 6.2 Change-Id: I54d475e8f1ce01fd7cb8c64b68c2318b1e9f7bbe Reviewed-on: https://review.typo3.org/27413 Reviewed-by: Oliver Hader Tested-by: Oliver Hader --- .../AbstractDataHandlerActionTestCase.php | 139 ++++++++++ .../Frontend/Hook/BackendUserHandler.php | 71 ++++++ .../Hook/ContentObjectRendererWatcher.php | 191 ++++++++++++++ .../Hook/DatabaseConnectionWatcher.php | 143 +++++++++++ .../Framework/Frontend/RenderElement.php | 240 ++++++++++++++++++ .../Framework/Frontend/RenderLevel.php | 206 +++++++++++++++ .../Framework/Frontend/RequestBootstrap.php | 111 ++++++++ .../Framework/Frontend/Response.php | 97 +++++++ .../Framework/Frontend/ResponseContent.php | 136 ++++++++++ .../Functional/Framework/Scripts/Request.php | 8 + .../sysext/core/Tests/FunctionalTestCase.php | 99 +++++++- .../FunctionalTestCaseBootstrapUtility.php | 4 +- 12 files changed, 1443 insertions(+), 2 deletions(-) create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php create mode 100644 typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php index 5fd0e3ce34db..3ff6907baf14 100644 --- a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php +++ b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php @@ -26,6 +26,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\DataSet; +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response; +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\ResponseContent; /** * Functional test for the DataHandler @@ -265,4 +267,141 @@ abstract class AbstractDataHandlerActionTestCase extends \TYPO3\CMS\Core\Tests\F return $differentFields; } + /** + * @param ResponseContent $responseContent + * @param string $structureRecordIdentifier + * @param string $structureFieldName + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentStructureHasRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) { + $nonMatchingVariants = array(); + + foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) { + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values); + + if (empty($nonMatchingValues)) { + // Increase assertion counter + $this->assertEmpty($nonMatchingValues); + return; + } + + $nonMatchingVariants[$path] = $nonMatchingValues; + } + + $nonMatchingMessage = ''; + foreach ($nonMatchingVariants as $path => $nonMatchingValues) { + $nonMatchingMessage .= '* ' . $path . ': ' . implode(', ', $nonMatchingValues); + } + + $this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '"' . LF . $nonMatchingMessage); + } + + /** + * @param ResponseContent $responseContent + * @param string $structureRecordIdentifier + * @param string $structureFieldName + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentStructureDoesNotHaveRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) { + if (is_string($values)) { + $values = array($values); + } + + $matchingVariants = array(); + + foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) { + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values); + $matchingValues = array_diff($values, $nonMatchingValues); + + if (!empty($matchingValues)) { + $matchingVariants[$path] = $matchingValues; + } + } + + if (empty($matchingVariants)) { + // Increase assertion counter + $this->assertEmpty($matchingVariants); + return; + } + + $matchingMessage = ''; + foreach ($matchingVariants as $path => $matchingValues) { + $matchingMessage .= '* ' . $path . ': ' . implode(', ', $matchingValues); + } + + $this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '"' . LF . $matchingMessage); + } + + /** + * @param ResponseContent $responseContent + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentHasRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) { + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values); + + if (!empty($nonMatchingValues)) { + $this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $nonMatchingValues)); + } + + // Increase assertion counter + $this->assertEmpty($nonMatchingValues); + } + + /** + * @param ResponseContent $responseContent + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentDoesNotHaveRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) { + if (is_string($values)) { + $values = array($values); + } + + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values); + $matchingValues = array_diff($values, $nonMatchingValues); + + if (!empty($matchingValues)) { + $this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $matchingValues)); + } + + // Increase assertion counter + $this->assertTrue(TRUE); + } + + /** + * @param string|array $data + * @param string $tableName + * @param string $fieldName + * @param string|array $values + * @return array + */ + protected function getNonMatchingValuesFrontendResponseRecords($data, $tableName, $fieldName, $values) { + if (empty($data) || !is_array($data)) { + $this->fail('Frontend Response data does not have any records'); + } + + if (is_string($values)) { + $values = array($values); + } + + foreach ($data as $recordIdentifier => $recordData) { + if (strpos($recordIdentifier, $tableName . ':') !== 0) { + continue; + } + + if (($foundValueIndex = array_search($recordData[$fieldName], $values)) !== FALSE) { + unset($values[$foundValueIndex]); + } + } + + return $values; + } + } diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php new file mode 100644 index 000000000000..59202ed3595e --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php @@ -0,0 +1,71 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use \TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Handler for backend user + */ +class BackendUserHandler implements \TYPO3\CMS\Core\SingletonInterface { + + /** + * @param array $parameters + * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController + */ + public function initialize(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController) { + $backendUserId = (int)GeneralUtility::_GP('backendUserId'); + $workspaceId = (int)GeneralUtility::_GP('workspaceId'); + + if (empty($backendUserId) || empty($workspaceId)) { + return; + } + + $backendUser = $this->createBackendUser(); + $backendUser->user = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'be_users', 'uid=' . $backendUserId); + $backendUser->setTemporaryWorkspace($workspaceId); + $frontendController->beUserLogin = 1; + + $parameters['BE_USER'] = $backendUser; + $GLOBALS['BE_USER'] = $backendUser; + } + + /** + * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication + */ + protected function createBackendUser() { + return GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Backend\\FrontendBackendUserAuthentication' + ); + } + + /** + * @return \TYPO3\CMS\Core\Database\DatabaseConnection + */ + protected function getDatabaseConnection() { + return $GLOBALS['TYPO3_DB']; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php new file mode 100644 index 000000000000..e628a24d645b --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php @@ -0,0 +1,191 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RenderLevel; +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RenderElement; + +/** + * Watcher for the content object rendering process + */ +class ContentObjectRendererWatcher implements \TYPO3\CMS\Frontend\ContentObject\ContentObjectPostInitHookInterface, \TYPO3\CMS\Core\SingletonInterface { + + /** + * @var RenderLevel + */ + protected $renderLevel; + + /** + * @var RenderElement + */ + protected $nextParentRenderElement; + + /** + * @var array + */ + protected $nextParentConfiguration; + + /** + * Holds parent objects (cObj) locally + * to avoid spl_object_hash() reassignments. + * + * @var array + */ + protected $localParentObjects = array(); + + /** + * @param string $name + * @param NULL|array $configuration + * @param string $typoScriptKey + * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject + * @return string + */ + public function cObjGetSingleExt($name, $configuration, $typoScriptKey, \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject) { + $this->localParentObjects[] = $parentObject; + $this->nextParentRenderElement = NULL; + $this->nextParentConfiguration = NULL; + + if (($foundRenderElement = $this->renderLevel->findRenderElement($parentObject)) !== NULL) { + $this->nextParentRenderElement = $foundRenderElement; + $this->nextParentConfiguration = $configuration; + if (!empty($configuration['table'])) { + $this->nextParentRenderElement->addExpectedTableName($configuration['table']); + } + } + + $contentObject = $parentObject->getContentObject($name); + if ($contentObject) { + $contentObject->render($configuration); + } + + return ''; + } + + /** + * Hook for post processing the initialization of ContentObjectRenderer + * + * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object + */ + public function postProcessContentObjectInitialization(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer &$parentObject) { + $this->localParentObjects[] = $parentObject; + + if (!isset($this->renderLevel)) { + $this->renderLevel = RenderLevel::create($parentObject); + $this->renderLevel->add($parentObject); + } elseif (($foundRenderLevel = $this->renderLevel->findRenderLevel($parentObject)) !== NULL) { + $foundRenderLevel->add($parentObject); + } elseif ($this->nextParentRenderElement !== NULL) { + $level = $this->nextParentRenderElement->add($parentObject); + $level->add($parentObject); + if (!empty($this->nextParentConfiguration['watcher.']['parentRecordField'])) { + $level->setParentRecordField($this->nextParentConfiguration['watcher.']['parentRecordField']); + } + $this->nextParentRenderElement = NULL; + $this->nextParentConfiguration = NULL; + } + } + + /** + * @param string $query + * @param string $fromTable + */ + public function addQuery($query, $fromTable) { + if ($this->nextParentRenderElement === NULL) { + return; + } + + $this->nextParentRenderElement->addQuery($query, $fromTable); + } + + /** + * @param array $parameters + * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController + */ + public function show(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController) { + if (!isset($this->renderLevel) || empty($parameters['enableOutput']) || !empty($frontendController->content)) { + return; + } + + $tableFields = NULL; + if (!empty($this->getFrontendController()->tmpl->setup['watcher.']['tableFields.'])) { + $tableFields = $this->getFrontendController()->tmpl->setup['watcher.']['tableFields.']; + foreach ($tableFields as &$fieldList) { + $fieldList = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $fieldList, TRUE); + } + unset($fieldList); + } + + $structureData = $this->renderLevel->structureData($tableFields); + + $result = array( + 'structure' => $structureData, + 'structurePaths' => $this->getStructurePaths($structureData), + 'records' => $this->renderLevel->mergeData($tableFields), + 'queries' => $this->renderLevel->mergeQueries(), + ); + + $frontendController->content = json_encode($result); + } + + /** + * @param array $structureData + * @param array $currentStructurePaths + * @return array + */ + protected function getStructurePaths(array $structureData, array $currentStructurePaths = array()) { + $structurePaths = array(); + + foreach ($structureData as $recordIdentifier => $recordData) { + $structurePaths[$recordIdentifier][] = $currentStructurePaths; + foreach ($recordData as $fieldName => $fieldValue) { + if (!is_array($fieldValue)) { + continue; + } + + $nestedStructurePaths = $this->getStructurePaths( + $fieldValue, + array_merge($currentStructurePaths, array($recordIdentifier, $fieldName)) + ); + + foreach ($nestedStructurePaths as $nestedRecordIdentifier => $nestedStructurePathDetails) { + $structurePaths[$nestedRecordIdentifier] = array_merge( + (array)$structurePaths[$nestedRecordIdentifier], + $nestedStructurePathDetails + ); + } + } + } + + return $structurePaths; + } + + /** + * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController + */ + protected function getFrontendController() { + return $GLOBALS['TSFE']; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php new file mode 100644 index 000000000000..02f0d7f76949 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php @@ -0,0 +1,143 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Watcher for executed database queries + */ +class DatabaseConnectionWatcher implements \TYPO3\CMS\Core\Database\PostProcessQueryHookInterface, \TYPO3\CMS\Core\SingletonInterface { + + /** + * @var array + */ + protected $queries = array(); + + /** + * Constructs this object and ensures that full database + * queries are stored locally in the DatabaseConnection. + */ + public function __construct() { + $this->getDatabaseConnection()->store_lastBuiltQuery = TRUE; + } + + public function getQueries() { + return $this->queries; + } + + /** + * Post-processor for the SELECTquery method. + * + * @param string $select_fields Fields to be selected + * @param string $from_table Table to select data from + * @param string $where_clause Where clause + * @param string $groupBy Group by statement + * @param string $orderBy Order by statement + * @param integer $limit Database return limit + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_SELECTquery_postProcessAction(&$select_fields, &$from_table, &$where_clause, &$groupBy, &$orderBy, &$limit, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + $this->getContentObjectRendererWatcher()->addQuery( + $parentObject->debug_lastBuiltQuery, + $from_table + ); + } + + /** + * Post-processor for the exec_INSERTquery method. + * + * @param string $table Database table name + * @param array $fieldsValues Field values as key => value pairs + * @param string /array $noQuoteFields List/array of keys NOT to quote + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_INSERTquery_postProcessAction(&$table, array &$fieldsValues, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_INSERTmultipleRows method. + * + * @param string $table Database table name + * @param array $fields Field names + * @param array $rows Table rows + * @param string /array $noQuoteFields List/array of keys NOT to quote + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_INSERTmultipleRows_postProcessAction(&$table, array &$fields, array &$rows, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_UPDATEquery method. + * + * @param string $table Database table name + * @param string $where WHERE clause + * @param array $fieldsValues Field values as key => value pairs + * @param string /array $noQuoteFields List/array of keys NOT to quote + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_UPDATEquery_postProcessAction(&$table, &$where, array &$fieldsValues, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_DELETEquery method. + * + * @param string $table Database table name + * @param string $where WHERE clause + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_DELETEquery_postProcessAction(&$table, &$where, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_TRUNCATEquery method. + * + * @param string $table Database table name + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_TRUNCATEquery_postProcessAction(&$table, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * @return ContentObjectRendererWatcher + */ + protected function getContentObjectRendererWatcher() { + return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\Hook\\ContentObjectRendererWatcher' + ); + } + + /** + * @return \TYPO3\CMS\Core\Database\DatabaseConnection + */ + protected function getDatabaseConnection() { + return $GLOBALS['TYPO3_DB']; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php new file mode 100644 index 000000000000..fd6c8a5b0e2f --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php @@ -0,0 +1,240 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +/** + * Model of rendered content elements + */ +class RenderElement { + + /** + * @var array + */ + protected $recordData; + + /** + * @var string + */ + protected $recordIdentifier; + + /** + * @var string + */ + protected $recordTableName; + + /** + * @var array|RenderLevel[] + */ + protected $levels; + + /** + * @var array + */ + protected $expectedTableNames = array(); + + /** + * @var array + */ + protected $queries = array(); + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderElement + */ + public static function create(ContentObjectRenderer $contentObjectRenderer) { + return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\RenderElement', + $contentObjectRenderer + ); + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + */ + public function __construct(ContentObjectRenderer $contentObjectRenderer) { + $this->recordIdentifier = $contentObjectRenderer->currentRecord; + list($this->recordTableName) = explode(':', $this->recordIdentifier); + $this->recordData = $contentObjectRenderer->data; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderLevel + */ + public function add(ContentObjectRenderer $contentObjectRenderer) { + $level = RenderLevel::create($contentObjectRenderer); + $level->setParentRecordIdentifier($this->recordIdentifier); + $this->levels[] = $level; + return $level; + } + + /** + * @return string + */ + public function getRecordIdentifier() { + return $this->recordIdentifier; + } + + /** + * @param string $expectedTableName + */ + public function addExpectedTableName($expectedTableName) { + if (!$this->hasExpectedTableName($expectedTableName)) { + $this->expectedTableNames[] = $expectedTableName; + } + } + + /** + * @param string $tableName + * @return bool + */ + public function hasExpectedTableName($tableName) { + if (in_array($tableName, $this->expectedTableNames)) { + return TRUE; + } + // Handling JOIN constructions + // e.g. "sys_category JOIN sys_category_record_mm ON sys_category_record_mm.uid_local = sys_category.uid" + foreach ($this->getExpectedTableNames() as $expectedTableName) { + if (strpos($tableName, $expectedTableName . ' ') === 0) { + return TRUE; + } + } + return FALSE; + } + + /** + * @return array + */ + public function getExpectedTableNames() { + return $this->expectedTableNames; + } + + /** + * @param string $query + * @param string $fromTable + */ + public function addQuery($query, $fromTable) { + if (empty($this->expectedTableNames) || $this->hasExpectedTableName($fromTable)) { + $this->queries[] = $query; + } + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return NULL|RenderLevel + */ + public function findRenderLevel(ContentObjectRenderer $contentObjectRenderer) { + if (empty($this->levels)) { + return NULL; + } + + foreach ($this->levels as $level) { + $result = $level->findRenderLevel($contentObjectRenderer); + if ($result !== NULL) { + return $result; + } + } + + return NULL; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function getRecordData(array $tableFields = NULL) { + $recordData = $this->recordData; + + if (!empty($tableFields[$this->recordTableName])) { + $recordData = array_intersect_key( + $recordData, + array_flip($tableFields[$this->recordTableName]) + ); + } + + return $recordData; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function structureData(array $tableFields = NULL) { + $data = array( + $this->recordIdentifier => $this->getRecordData($tableFields) + ); + + foreach ($this->levels as $level) { + $parentRecordIdentifier = $level->getParentRecordIdentifier(); + $parentRecordField = $level->getParentRecordField(); + + foreach ($level->getElements() as $element) { + if (empty($parentRecordIdentifier) || empty($parentRecordField) || !isset($data[$parentRecordIdentifier])) { + $data = array_merge($data, $element->structureData($tableFields)); + continue; + } + + if (!isset($data[$parentRecordIdentifier][$parentRecordField]) || !is_array($data[$parentRecordIdentifier][$parentRecordField])) { + $data[$parentRecordIdentifier][$parentRecordField] = array(); + } + + $data[$parentRecordIdentifier][$parentRecordField] = array_merge( + $data[$parentRecordIdentifier][$parentRecordField], + $element->structureData($tableFields) + ); + } + } + + return $data; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function mergeData(array $tableFields = NULL) { + $data = array( + $this->recordIdentifier => $this->getRecordData($tableFields), + ); + foreach ($this->levels as $level) { + $data = array_merge($data, $level->mergeData($tableFields)); + } + return $data; + } + + /** + * @return array + */ + public function mergeQueries() { + $queries = $this->queries; + foreach ($this->levels as $level) { + $queries = array_merge($queries, $level->mergeQueries()); + } + return $queries; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php new file mode 100644 index 000000000000..545e3180bf18 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php @@ -0,0 +1,206 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +/** + * Model of rendered content levels + */ +class RenderLevel { + + /** + * @var string + */ + protected $identifier; + + /** + * @var string + */ + protected $parentRecordIdentifier; + + /** + * @var string + */ + protected $parentRecordField; + + /** + * @var array|RenderElement[] + */ + protected $elements = array(); + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderLevel + */ + public static function create(ContentObjectRenderer $contentObjectRenderer) { + return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\RenderLevel', + $contentObjectRenderer + ); + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + */ + public function __construct(ContentObjectRenderer $contentObjectRenderer) { + $this->identifier = spl_object_hash($contentObjectRenderer); + } + + /** + * @return string + */ + public function getIdentifier() { + return $this->identifier; + } + + /** + * @param string $parentRecordIdentifier + */ + public function setParentRecordIdentifier($parentRecordIdentifier) { + $this->parentRecordIdentifier = $parentRecordIdentifier; + } + + public function getParentRecordIdentifier() { + return $this->parentRecordIdentifier; + } + + /** + * @param string $parentRecordField + */ + public function setParentRecordField($parentRecordField) { + $this->parentRecordField = $parentRecordField; + } + + /** + * @return string + */ + public function getParentRecordField() { + return $this->parentRecordField; + } + + /** + * @return array|RenderElement[] + */ + public function getElements() { + return $this->elements; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderElement + */ + public function add(ContentObjectRenderer $contentObjectRenderer) { + $element = RenderElement::create($contentObjectRenderer); + $this->elements[] = $element; + return $element; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return NULL|RenderLevel + */ + public function findRenderLevel(ContentObjectRenderer $contentObjectRenderer) { + if (spl_object_hash($contentObjectRenderer) === $this->identifier) { + return $this; + } + + foreach ($this->elements as $element) { + $result = $element->findRenderLevel($contentObjectRenderer); + if ($result !== NULL) { + return $result; + } + } + + return NULL; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return NULL|RenderElement + */ + public function findRenderElement(ContentObjectRenderer $contentObjectRenderer) { + $foundRenderLevel = $this->findRenderLevel($contentObjectRenderer); + + if ($foundRenderLevel === NULL) { + return NULL; + } + + if ($foundRenderLevel !== $this) { + return $foundRenderLevel->findRenderElement($contentObjectRenderer); + } + + + foreach ($this->elements as $element) { + if ($element->getRecordIdentifier() === $contentObjectRenderer->currentRecord) { + return $element; + } + } + + return NULL; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function structureData(array $tableFields = NULL) { + $data = array(); + + foreach ($this->elements as $element) { + $data = array_merge($data, $element->structureData($tableFields)); + } + + return $data; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function mergeData(array $tableFields = NULL) { + $data = array(); + + foreach ($this->elements as $element) { + $data = array_merge($data, $element->mergeData($tableFields)); + } + + return $data; + } + + /** + * @return array + */ + public function mergeQueries() { + $queries = array(); + + foreach ($this->elements as $element) { + $queries = array_merge($queries, $element->mergeQueries()); + } + + return $queries; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php new file mode 100644 index 000000000000..623a02357d87 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php @@ -0,0 +1,111 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Bootstrap for direct CLI Request + */ +class RequestBootstrap { + + /** + * @return void + */ + static public function setGlobalVariables() { + if (empty($_SERVER['argv'][1]) || ($requestArguments = json_decode($_SERVER['argv'][1], TRUE)) === FALSE) { + die('No JSON encoded arguments given'); + } + + if (empty($requestArguments['documentRoot'])) { + die('No documentRoot given'); + } + + if (empty($requestArguments['requestUrl']) || ($requestUrlParts = parse_url($requestArguments['requestUrl'])) === FALSE) { + die('No valid request URL given'); + } + + // Populating $_GET and $_REQUEST is query part is set: + if (isset($requestUrlParts['query'])) { + parse_str($requestUrlParts['query'], $_GET); + parse_str($requestUrlParts['query'], $_REQUEST); + } + + // Populating $_POST + $_POST = array(); + // Populating $_COOKIE + $_COOKIE = array(); + + // Setting up the server environment + $_SERVER = array(); + $_SERVER['DOCUMENT_ROOT'] = $requestArguments['documentRoot']; + $_SERVER['HTTP_USER_AGENT'] = 'TYPO3 Functional Test Request'; + $_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'] = $requestUrlParts['host']; + $_SERVER['SERVER_ADDR'] = $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'] = '/index.php'; + $_SERVER['SCRIPT_FILENAME'] = $_SERVER['_'] = $_SERVER['PATH_TRANSLATED'] = $requestArguments['documentRoot'] . '/index.php'; + $_SERVER['QUERY_STRING'] = (isset($requestUrlParts['query']) ? $requestUrlParts['query'] : ''); + $_SERVER['REQUEST_URI'] = $requestUrlParts['path'] . (isset($requestUrlParts['query']) ? '?' . $requestUrlParts['query'] : ''); + $_SERVER['REQUEST_METHOD'] = 'GET'; + + // Define a port if used in the URL: + if (isset($requestUrlParts['port'])) { + $_SERVER['SERVER_PORT'] = $requestUrlParts['port']; + } + // Define HTTPS disposal: + if ($requestUrlParts['scheme'] === 'https') { + $_SERVER['HTTPS'] = 'on'; + } + + if (!is_dir($_SERVER['DOCUMENT_ROOT'])) { + die('Document root directory "' . $_SERVER['SCRIPT_FILENAME'] . '" does not exist'); + } + + if (!is_file($_SERVER['SCRIPT_FILENAME'])) { + die('Script file "' . $_SERVER['SCRIPT_FILENAME'] . '" does not exist'); + } + } + + /** + * @return void + */ + static public function executeAndOutput() { + global $TT, $TSFE, $TYPO3_CONF_VARS, $BE_USER, $TYPO3_MISC; + + $result = array('status' => 'failure', 'content' => NULL, 'error' => NULL); + + ob_start(); + try { + chdir($_SERVER['DOCUMENT_ROOT']); + include($_SERVER['SCRIPT_FILENAME']); + $result['status'] = 'success'; + $result['content'] = ob_get_contents(); + } catch(\Exception $exception) { + $result['error'] = $exception->__toString(); + } + ob_end_clean(); + + echo json_encode($result); + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php new file mode 100644 index 000000000000..b0e68392a4a1 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php @@ -0,0 +1,97 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Model of frontend response + */ +class Response { + + const STATUS_Success = 'success'; + const STATUS_Failure = 'failure'; + + /** + * @var string + */ + protected $status; + + /** + * @var NULL|string|array + */ + protected $content; + + /** + * @var string + */ + protected $error; + + /** + * @var ResponseContent + */ + protected $responseContent; + + /** + * @param string $status + * @param string $content + * @param string $error + */ + public function __construct($status, $content, $error) { + $this->status = $status; + $this->content = $content; + $this->error = $error; + } + + /** + * @return string + */ + public function getStatus() { + return $this->status; + } + + /** + * @return array|NULL|string + */ + public function getContent() { + return $this->content; + } + + /** + * @return string + */ + public function getError() { + return $this->error; + } + + /** + * @return ResponseContent + */ + public function getResponseContent() { + if (!isset($this->responseContent)) { + $this->responseContent = new ResponseContent($this); + } + return $this->responseContent; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php new file mode 100644 index 000000000000..93ccb36c078f --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php @@ -0,0 +1,136 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Model of frontend response content + */ +class ResponseContent { + + /** + * @var Response + */ + protected $response; + + /** + * @var array + */ + protected $structure; + + /** + * @var array + */ + protected $structurePaths; + + /** + * @var array + */ + protected $records; + + /** + * @var array + */ + protected $queries; + + /** + * @param Response $response + */ + public function __construct(Response $response) { + $this->response = $response; + $content = json_decode($response->getContent(), TRUE); + + if ($content !== NULL && is_array($content)) { + $this->structure = $content['structure']; + $this->structurePaths = $content['structurePaths']; + $this->records = $content['records']; + $this->queries = $content['queries']; + } + } + + /** + * @return array + */ + public function getStructure() { + return $this->structure; + } + + /** + * @return array + */ + public function getStructurePaths() { + return $this->structurePaths; + } + + /** + * @return array + */ + public function getRecords() { + return $this->records; + } + + /** + * @return array + */ + public function getQueries() { + return $this->queries; + } + + /** + * @param string $recordIdentifier + * @param string $fieldName + * @return array + */ + public function findStructures($recordIdentifier, $fieldName = '') { + $structures = array(); + + if (empty($this->structurePaths[$recordIdentifier])) { + return $structures; + } + + foreach ($this->structurePaths[$recordIdentifier] as $steps) { + $structure = $this->structure; + $steps[] = $recordIdentifier; + + if (!empty($fieldName)) { + $steps[] = $fieldName; + } + + foreach ($steps as $step) { + if (!isset($structure[$step])) { + $structure = NULL; + break; + } + $structure = $structure[$step]; + } + + if (!empty($structure)) { + $structures[implode('/', $steps)] = $structure; + } + } + + return $structures; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php b/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php new file mode 100644 index 000000000000..ff599a6a9ab6 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php @@ -0,0 +1,8 @@ +<?php +require dirname(dirname(dirname(dirname(__DIR__)))) . '/Classes/Core/CliBootstrap.php'; +\TYPO3\CMS\Core\Core\CliBootstrap::checkEnvironmentOrDie(); + +require dirname(__DIR__) . '/Frontend/RequestBootstrap.php'; +\TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RequestBootstrap::setGlobalVariables(); +\TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RequestBootstrap::executeAndOutput(); +?> \ No newline at end of file diff --git a/typo3/sysext/core/Tests/FunctionalTestCase.php b/typo3/sysext/core/Tests/FunctionalTestCase.php index aa9204970eed..f81624242ab5 100644 --- a/typo3/sysext/core/Tests/FunctionalTestCase.php +++ b/typo3/sysext/core/Tests/FunctionalTestCase.php @@ -24,6 +24,8 @@ namespace TYPO3\CMS\Core\Tests; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +use \TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response; + /** * Base test case class for functional tests, all TYPO3 CMS * functional tests should extend from this class! @@ -134,6 +136,13 @@ abstract class FunctionalTestCase extends BaseTestCase { */ private $bootstrapUtility = NULL; + /** + * Path to TYPO3 CMS test installation for this test case + * + * @var string + */ + private $instancePath; + /** * Set up creates a test instance and database. * @@ -146,7 +155,7 @@ abstract class FunctionalTestCase extends BaseTestCase { $this->markTestSkipped('Functional tests must be called through phpunit on CLI'); } $this->bootstrapUtility = new FunctionalTestCaseBootstrapUtility(); - $this->bootstrapUtility->setUp( + $this->instancePath = $this->bootstrapUtility->setUp( get_class($this), $this->coreExtensionsToLoad, $this->testExtensionsToLoad, @@ -273,4 +282,92 @@ abstract class FunctionalTestCase extends BaseTestCase { } } } + + /** + * @param int $pageId + * @param array $typoScriptFiles + */ + protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = array()) { + $pageId = (int)$pageId; + $page = $this->getDatabase()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $pageId); + + if (empty($page)) { + $this->fail('Cannot set up frontend root page "' . $pageId . '"'); + } + + $pagesFields = array( + 'is_siteroot' => 1 + ); + + $this->getDatabase()->exec_UPDATEquery('pages', 'uid=' . $pageId, $pagesFields); + + $templateFields = array( + 'pid' => $pageId, + 'title' => '', + 'config' => '', + 'clear' => 3, + 'root' => 1, + ); + + foreach ($typoScriptFiles as $typoScriptFile) { + $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF; + } + + $this->getDatabase()->exec_INSERTquery('sys_template', $templateFields); + } + + /** + * @param int $pageId + * @param int $languageId + * @param int $backendUserId + * @param int $workspaceId + * @param bool $failOnFailure + * @return Response + */ + protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = TRUE) { + $pageId = (int)$pageId; + $languageId = (int)$languageId; + + if (defined('PHP_BINARY')) { + $phpExecutable = PHP_BINARY; + } else { + $phpExecutable = rtrim(PHP_BINDIR, '/') . '/php'; + } + + $additionalParameter = ''; + + if (!empty($backendUserId)) { + $additionalParameter .= '&backendUserId=' . (int)$backendUserId; + } + if (!empty($workspaceId)) { + $additionalParameter .= '&workspaceId=' . (int)$workspaceId; + } + + $arguments = array( + 'documentRoot' => $this->instancePath, + 'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter, + ); + + $commandParts = array( + escapeshellcmd($phpExecutable), + escapeshellarg(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php'), + escapeshellarg(json_encode($arguments)), + ); + + $command = trim(implode(' ', $commandParts)); + $response = shell_exec($command); + $result = json_decode($response, TRUE); + + if ($result === FALSE) { + $this->fail('Frontend Response is empty'); + } + + if ($failOnFailure && $result['status'] === Response::STATUS_Failure) { + $this->fail('Frontend Response has failure:' . LF . $result['error']); + } + + $response = new Response($result['status'], $result['content'], $result['error']); + return $response; + } + } diff --git a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php index 069a8bf22cbd..36f7ce062644 100644 --- a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php +++ b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php @@ -75,7 +75,7 @@ class FunctionalTestCaseBootstrapUtility { * @param array $coreExtensionsToLoad Array of core extensions to load * @param array $testExtensionsToLoad Array of test extensions to load * @param array $pathsToLinkInTestInstance Array of source => destination path pairs to be linked - * @return void + * @return string Path to TYPO3 CMS test installation for this test case */ public function setUp( $testCaseClassName, @@ -96,6 +96,8 @@ class FunctionalTestCaseBootstrapUtility { $this->setUpTestDatabase(); \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(TRUE); $this->createDatabaseStructure(); + + return $this->instancePath; } /** -- GitLab