[TASK] Reduce complexity in frontend functional tests
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / FunctionalTestCase.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 use \TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
28
29 /**
30 * Base test case class for functional tests, all TYPO3 CMS
31 * functional tests should extend from this class!
32 *
33 * If functional tests need additional setUp() and tearDown() code,
34 * they *must* call parent::setUp() and parent::tearDown() to properly
35 * set up and destroy the test system.
36 *
37 * The functional test system creates a full new TYPO3 CMS instance
38 * within typo3temp/ of the base system and the bootstraps this TYPO3 instance.
39 * This abstract class takes care of creating this instance with its
40 * folder structure and a LocalConfiguration, creates an own database
41 * for each test run and imports tables of loaded extensions.
42 *
43 * Functional tests must be run standalone (calling native phpunit
44 * directly) and can not be executed by eg. the ext:phpunit backend module.
45 * Additionally, the script must be called from the document root
46 * of the instance, otherwise path calculation is not successfully.
47 *
48 * Call whole functional test suite, example:
49 * - cd /var/www/t3master/foo # Document root of CMS instance, here is index.php of frontend
50 * - ./typo3conf/ext/phpunit/Composer/vendor/bin/phpunit -c typo3/sysext/core/Build/FunctionalTests.xml
51 *
52 * Call single test case, example:
53 * - cd /var/www/t3master/foo # Document root of CMS instance, here is index.php of frontend
54 * - ./typo3conf/ext/phpunit/Composer/vendor/bin/phpunit \
55 * --process-isolation \
56 * --bootstrap typo3/sysext/core/Build/FunctionalTestsBootstrap.php \
57 * typo3/sysext/core/Tests/Functional/DataHandling/DataHandlerTest.php
58 */
59 abstract class FunctionalTestCase extends BaseTestCase {
60 /**
61 * Core extensions to load.
62 *
63 * If the test case needs additional core extensions as requirement,
64 * they can be noted here and will be added to LocalConfiguration
65 * extension list and ext_tables.sql of those extensions will be applied.
66 *
67 * This property will stay empty in this abstract, so it is possible
68 * to just overwrite it in extending classes. Extensions noted here will
69 * be loaded for every test of a test case and it is not possible to change
70 * the list of loaded extensions between single tests of a test case.
71 *
72 * A default list of core extensions is always loaded.
73 *
74 * @see FunctionalTestCaseUtility $defaultActivatedCoreExtensions
75 * @var array
76 */
77 protected $coreExtensionsToLoad = array();
78
79 /**
80 * Array of test/fixture extensions paths that should be loaded for a test.
81 *
82 * This property will stay empty in this abstract, so it is possible
83 * to just overwrite it in extending classes. Extensions noted here will
84 * be loaded for every test of a test case and it is not possible to change
85 * the list of loaded extensions between single tests of a test case.
86 *
87 * Given path is expected to be relative to your document root, example:
88 *
89 * array(
90 * 'typo3conf/ext/some_extension/Tests/Functional/Fixtures/Extensions/test_extension',
91 * 'typo3conf/ext/base_extension',
92 * );
93 *
94 * Extensions in this array are linked to the test instance, loaded
95 * and their ext_tables.sql will be applied.
96 *
97 * @var array
98 */
99 protected $testExtensionsToLoad = array();
100
101 /**
102 * Array of test/fixture folder or file paths that should be linked for a test.
103 *
104 * This property will stay empty in this abstract, so it is possible
105 * to just overwrite it in extending classes. Path noted here will
106 * be linked for every test of a test case and it is not possible to change
107 * the list of folders between single tests of a test case.
108 *
109 * array(
110 * 'link-source' => 'link-destination'
111 * );
112 *
113 * Given paths are expected to be relative to the test instance root.
114 * The array keys are the source paths and the array values are the destination
115 * paths, example:
116 *
117 * array(
118 * 'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' =>
119 * 'fileadmin/user_upload',
120 * 'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' =>
121 * 'uploads/tx_myownext'
122 * );
123 *
124 * To be able to link from my_own_ext the extension path needs also to be registered in
125 * property $testExtensionsToLoad
126 *
127 * @var array
128 */
129 protected $pathsToLinkInTestInstance = array();
130
131 /**
132 * This configuration array is merged with TYPO3_CONF_VARS
133 * that are set in default configuration and factory configuration
134 *
135 * @var array
136 */
137 protected $configurationToUseInTestInstance = array();
138
139 /**
140 * Array of folders that should be created inside the test instance document root.
141 *
142 * This property will stay empty in this abstract, so it is possible
143 * to just overwrite it in extending classes. Path noted here will
144 * be linked for every test of a test case and it is not possible to change
145 * the list of folders between single tests of a test case.
146 *
147 * Per default the following folder are created
148 * /fileadmin
149 * /typo3temp
150 * /typo3conf
151 * /typo3conf/ext
152 * /uploads
153 *
154 * To create additional folders add the paths to this array. Given paths are expected to be
155 * relative to the test instance root and have to begin with a slash. Example:
156 *
157 * array(
158 * 'fileadmin/user_upload'
159 * );
160 *
161 * @var array
162 */
163 protected $additionalFoldersToCreate = array();
164
165 /**
166 * Private utility class used in setUp() and tearDown(). Do NOT use in test cases!
167 *
168 * @var \TYPO3\CMS\Core\Tests\FunctionalTestCaseBootstrapUtility
169 */
170 private $bootstrapUtility = NULL;
171
172 /**
173 * Path to TYPO3 CMS test installation for this test case
174 *
175 * @var string
176 */
177 private $instancePath;
178
179 /**
180 * Set up creates a test instance and database.
181 *
182 * This method should be called with parent::setUp() in your test cases!
183 *
184 * @return void
185 */
186 public function setUp() {
187 if (!defined('ORIGINAL_ROOT')) {
188 $this->markTestSkipped('Functional tests must be called through phpunit on CLI');
189 }
190 $this->bootstrapUtility = new FunctionalTestCaseBootstrapUtility();
191 $this->instancePath = $this->bootstrapUtility->setUp(
192 get_class($this),
193 $this->coreExtensionsToLoad,
194 $this->testExtensionsToLoad,
195 $this->pathsToLinkInTestInstance,
196 $this->configurationToUseInTestInstance,
197 $this->additionalFoldersToCreate
198 );
199 }
200
201 /**
202 * Get DatabaseConnection instance - $GLOBALS['TYPO3_DB']
203 *
204 * This method should be used instead of direct access to
205 * $GLOBALS['TYPO3_DB'] for easy IDE auto completion.
206 *
207 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
208 */
209 protected function getDatabaseConnection() {
210 return $GLOBALS['TYPO3_DB'];
211 }
212
213 /**
214 * Initialize backend user
215 *
216 * @param int $userUid uid of the user we want to initialize. This user must exist in the fixture file
217 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
218 * @throws Exception
219 */
220 protected function setUpBackendUserFromFixture($userUid) {
221 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/be_users.xml');
222 $database = $this->getDatabaseConnection();
223 $userRow = $database->exec_SELECTgetSingleRow('*', 'be_users', 'uid = ' . $userUid);
224
225 /** @var $backendUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
226 $backendUser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Authentication\\BackendUserAuthentication');
227 $sessionId = $backendUser->createSessionId();
228 $_SERVER['HTTP_COOKIE'] = 'be_typo_user=' . $sessionId . '; path=/';
229 $backendUser->id = $sessionId;
230 $backendUser->sendNoCacheHeaders = FALSE;
231 $backendUser->dontSetCookie = TRUE;
232 $backendUser->createUserSession($userRow);
233
234 $GLOBALS['BE_USER'] = $backendUser;
235 $GLOBALS['BE_USER']->start();
236 if (!is_array($GLOBALS['BE_USER']->user) || !$GLOBALS['BE_USER']->user['uid']) {
237 throw new Exception(
238 'Can not initialize backend user',
239 1377095807
240 );
241 }
242 $GLOBALS['BE_USER']->backendCheckLogin();
243
244 return $backendUser;
245 }
246
247 /**
248 * Imports a data set represented as XML into the test database,
249 *
250 * @param string $path Absolute path to the XML file containing the data set to load
251 * @return void
252 * @throws Exception
253 */
254 protected function importDataSet($path) {
255 if (!is_file($path)) {
256 throw new Exception(
257 'Fixture file ' . $path . ' not found',
258 1376746261
259 );
260 }
261
262 $database = $this->getDatabaseConnection();
263
264 $xml = simplexml_load_file($path);
265 $foreignKeys = array();
266
267 /** @var $table \SimpleXMLElement */
268 foreach ($xml->children() as $table) {
269 $insertArray = array();
270
271 /** @var $column \SimpleXMLElement */
272 foreach ($table->children() as $column) {
273 $columnName = $column->getName();
274 $columnValue = NULL;
275
276 if (isset($column['ref'])) {
277 list($tableName, $elementId) = explode('#', $column['ref']);
278 $columnValue = $foreignKeys[$tableName][$elementId];
279 } elseif (isset($column['is-NULL']) && ($column['is-NULL'] === 'yes')) {
280 $columnValue = NULL;
281 } else {
282 $columnValue = (string) $table->$columnName;
283 }
284
285 $insertArray[$columnName] = $columnValue;
286 }
287
288 $tableName = $table->getName();
289 $result = $database->exec_INSERTquery($tableName, $insertArray);
290 if ($result === FALSE) {
291 throw new Exception(
292 'Error when processing fixture file: ' . $path . ' Can not insert data to table ' . $tableName,
293 1376746262
294 );
295 }
296 if (isset($table['id'])) {
297 $elementId = (string) $table['id'];
298 $foreignKeys[$tableName][$elementId] = $database->sql_insert_id();
299 }
300 }
301 }
302
303 /**
304 * @param int $pageId
305 * @param array $typoScriptFiles
306 */
307 protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = array()) {
308 $pageId = (int)$pageId;
309 $page = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $pageId);
310
311 if (empty($page)) {
312 $this->fail('Cannot set up frontend root page "' . $pageId . '"');
313 }
314
315 $pagesFields = array(
316 'is_siteroot' => 1
317 );
318
319 $this->getDatabaseConnection()->exec_UPDATEquery('pages', 'uid=' . $pageId, $pagesFields);
320
321 $templateFields = array(
322 'pid' => $pageId,
323 'title' => '',
324 'config' => '',
325 'clear' => 3,
326 'root' => 1,
327 );
328
329 foreach ($typoScriptFiles as $typoScriptFile) {
330 $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF;
331 }
332
333 $this->getDatabaseConnection()->exec_INSERTquery('sys_template', $templateFields);
334 }
335
336 /**
337 * @param int $pageId
338 * @param int $languageId
339 * @param int $backendUserId
340 * @param int $workspaceId
341 * @param bool $failOnFailure
342 * @return Response
343 */
344 protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = TRUE) {
345 $pageId = (int)$pageId;
346 $languageId = (int)$languageId;
347
348 $additionalParameter = '';
349 if (!empty($backendUserId)) {
350 $additionalParameter .= '&backendUserId=' . (int)$backendUserId;
351 }
352 if (!empty($workspaceId)) {
353 $additionalParameter .= '&workspaceId=' . (int)$workspaceId;
354 }
355
356 $arguments = array(
357 'documentRoot' => $this->instancePath,
358 'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter,
359 );
360
361 $template = new \Text_Template(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/request.tpl');
362 $template->setVar(
363 array(
364 'arguments' => var_export($arguments, TRUE),
365 'originalRoot' => ORIGINAL_ROOT,
366 )
367 );
368
369 $php = \PHPUnit_Util_PHP::factory();
370 $response = $php->runJob($template->render());
371 $result = json_decode($response['stdout'], TRUE);
372
373 if ($result === FALSE) {
374 $this->fail('Frontend Response is empty');
375 }
376
377 if ($failOnFailure && $result['status'] === Response::STATUS_Failure) {
378 $this->fail('Frontend Response has failure:' . LF . $result['error']);
379 }
380
381 $response = new Response($result['status'], $result['content'], $result['error']);
382 return $response;
383 }
384
385 }