[TASK] Add possibility to assert error log entries
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Functional / DataHandling / AbstractDataHandlerActionTestCase.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Functional\DataHandling;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2014 Oliver Hader <oliver.hader@typo3.org>
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\Utility\GeneralUtility;
28 use TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\DataSet;
29 use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
30 use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\ResponseContent;
31
32 /**
33 * Functional test for the DataHandler
34 */
35 abstract class AbstractDataHandlerActionTestCase extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
36
37 const VALUE_BackendUserId = 1;
38
39 /**
40 * @var string
41 */
42 protected $dataSetDirectory;
43
44 /**
45 * If this value is NULL, log entries are not considered.
46 * If it's an integer value, the number of log entries is asserted.
47 *
48 * @var NULL|int
49 */
50 protected $expectedErrorLogEntries = 0;
51
52 /**
53 * @var array
54 */
55 protected $testExtensionsToLoad = array(
56 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial',
57 // 'typo3conf/ext/datahandler',
58 );
59
60 /**
61 * @var array
62 */
63 protected $pathsToLinkInTestInstance = array(
64 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/AdditionalConfiguration.php' => 'typo3conf/AdditionalConfiguration.php',
65 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/extTables.php' => 'typo3conf/extTables.php',
66 );
67
68 /**
69 * @var \TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\ActionService
70 */
71 protected $actionService;
72
73 /**
74 * @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
75 */
76 protected $backendUser;
77
78 public function setUp() {
79 parent::setUp();
80
81 $this->backendUser = $this->setUpBackendUserFromFixture(self::VALUE_BackendUserId);
82 // By default make tests on live workspace
83 $this->backendUser->workspace = 0;
84
85 $this->actionService = $this->getActionService();
86 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeLanguageObject();
87 }
88
89 public function tearDown() {
90 $this->assertErrorLogEntries();
91 unset($this->actionService);
92 parent::tearDown();
93 }
94
95 /**
96 * @return \TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\ActionService
97 */
98 protected function getActionService() {
99 return GeneralUtility::makeInstance(
100 'TYPO3\\CMS\\Core\\Tests\\Functional\\DataHandling\\Framework\\ActionService'
101 );
102 }
103
104 /**
105 * @param string $dataSetName
106 */
107 protected function importScenarioDataSet($dataSetName) {
108 $fileName = rtrim($this->dataSetDirectory, '/') . '/Scenario/' . $dataSetName . '.csv';
109 $fileName = GeneralUtility::getFileAbsFileName($fileName);
110
111 $dataSet = DataSet::read($fileName);
112
113 foreach ($dataSet->getTableNames() as $tableName) {
114 foreach ($dataSet->getElements($tableName) as $element) {
115 $this->getDatabase()->exec_INSERTquery(
116 $tableName,
117 $element
118 );
119 $sqlError = $this->getDatabase()->sql_error();
120 if (!empty($sqlError)) {
121 $this->fail('SQL Error for table "' . $tableName . '": ' . LF . $sqlError);
122 }
123 }
124 }
125 }
126
127 protected function assertAssertionDataSet($dataSetName) {
128 $fileName = rtrim($this->dataSetDirectory, '/') . '/Assertion/' . $dataSetName . '.csv';
129 $fileName = GeneralUtility::getFileAbsFileName($fileName);
130
131 $dataSet = DataSet::read($fileName);
132 $failMessages = array();
133
134 foreach ($dataSet->getTableNames() as $tableName) {
135 $hasUidField = ($dataSet->getIdIndex($tableName) !== NULL);
136 $records = $this->getAllRecords($tableName, $hasUidField);
137 foreach ($dataSet->getElements($tableName) as $assertion) {
138 $result = $this->assertInRecords($assertion, $records);
139 if ($result === FALSE) {
140 if ($hasUidField && empty($records[$assertion['uid']])) {
141 $failMessages[] = 'Record "' . $tableName . ':' . $assertion['uid'] . '" not found in database';
142 continue;
143 }
144 $recordIdentifier = $tableName . ($hasUidField ? ':' . $assertion['uid'] : '');
145 $additionalInformation = ($hasUidField ? $this->renderRecords($assertion, $records[$assertion['uid']]) : $this->arrayToString($assertion));
146 $failMessages[] = 'Assertion in data-set failed for "' . $recordIdentifier . '":' . LF . $additionalInformation;
147 // Unset failed asserted record
148 if ($hasUidField) {
149 unset($records[$assertion['uid']]);
150 }
151 } else {
152 // Unset asserted record
153 unset($records[$result]);
154 // Increase assertion counter
155 $this->assertTrue($result !== FALSE);
156 }
157 }
158 if (!empty($records)) {
159 foreach ($records as $record) {
160 $recordIdentifier = $tableName . ':' . $record['uid'];
161 $additionalInformation = $this->arrayToString($record);
162 $failMessages[] = 'Not asserted record found for "' . $recordIdentifier . '":' . LF . $additionalInformation;
163 }
164 }
165 }
166
167 if (!empty($failMessages)) {
168 $this->fail(implode(LF, $failMessages));
169 }
170 }
171
172 /**
173 * @param array $assertion
174 * @param array $records
175 * @return bool|int|string
176 */
177 protected function assertInRecords(array $assertion, array $records) {
178 foreach ($records as $index => $record) {
179 $differentFields = $this->getDifferentFields($assertion, $record);
180
181 if (empty($differentFields)) {
182 return $index;
183 }
184 }
185
186 return FALSE;
187 }
188
189 /**
190 * Asserts correct number of warning and error log entries.
191 *
192 * @return void
193 */
194 protected function assertErrorLogEntries() {
195 if ($this->expectedErrorLogEntries === NULL) {
196 return;
197 }
198 $errorLogEntries = $this->getDatabase()->exec_SELECTgetRows('*', 'sys_log', 'error IN (1,2)');
199 $actualErrorLogEntries = count($errorLogEntries);
200 if ($actualErrorLogEntries === $this->expectedErrorLogEntries) {
201 $this->assertSame($this->expectedErrorLogEntries, $actualErrorLogEntries);
202 } else {
203 $failureMessage = 'Expected ' . $this->expectedErrorLogEntries . ' entries in sys_log, but got ' . $actualErrorLogEntries . LF;
204 foreach ($errorLogEntries as $entry) {
205 $entryData = unserialize($entry['log_data']);
206 $entryMessage = vsprintf($entry['details'], $entryData);
207 $failureMessage .= '* ' . $entryMessage . LF;
208 }
209 $this->fail($failureMessage);
210 }
211 }
212
213 /**
214 * @param string $tableName
215 * @param bool $hasUidField
216 * @return array
217 */
218 protected function getAllRecords($tableName, $hasUidField = FALSE) {
219 $allRecords = array();
220
221 $records = $this->getDatabase()->exec_SELECTgetRows(
222 '*',
223 $tableName,
224 '1=1',
225 '',
226 '',
227 '',
228 ($hasUidField ? 'uid' : '')
229 );
230
231 if (!empty($records)) {
232 $allRecords = $records;
233 }
234
235 return $allRecords;
236 }
237
238 /**
239 * @param array $array
240 * @return string
241 */
242 protected function arrayToString(array $array) {
243 $elements = array();
244 foreach ($array as $key => $value) {
245 if (is_array($value)) {
246 $value = $this->arrayToString($value);
247 }
248 $elements[] = "'" . $key . "' => '" . $value . "'";
249 }
250 return 'array(' . PHP_EOL . ' ' . implode(', ' . PHP_EOL . ' ', $elements) . PHP_EOL . ')' . PHP_EOL;
251 }
252
253 /**
254 * @param array $assertion
255 * @param array $record
256 * @return string
257 */
258 protected function renderRecords(array $assertion, array $record) {
259 $differentFields = $this->getDifferentFields($assertion, $record);
260 $columns = array(
261 'fields' => array('Fields'),
262 'assertion' => array('Assertion'),
263 'record' => array('Record'),
264 );
265 $lines = array();
266 $linesFromXmlValues = array();
267 $result = '';
268
269 foreach ($differentFields as $differentField) {
270 $columns['fields'][] = $differentField;
271 $columns['assertion'][] = ($assertion[$differentField] === NULL ? 'NULL' : $assertion[$differentField]);
272 $columns['record'][] = ($record[$differentField] === NULL ? 'NULL' : $record[$differentField]);
273 }
274
275 foreach ($columns as $columnIndex => $column) {
276 $columnLength = NULL;
277 foreach ($column as $value) {
278 if (strpos($value, '<?xml') === 0) {
279 $value = '[see diff]';
280 }
281 $valueLength = strlen($value);
282 if (empty($columnLength) || $valueLength > $columnLength) {
283 $columnLength = $valueLength;
284 }
285 }
286 foreach ($column as $valueIndex => $value) {
287 if (strpos($value, '<?xml') === 0) {
288 if ($columnIndex === 'assertion') {
289 try {
290 $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$columns['fields'][$valueIndex]]);
291 } catch(\PHPUnit_Framework_ExpectationFailedException $e) {
292 $linesFromXmlValues[] = 'Diff for field "' . $columns['fields'][$valueIndex] . '":' . PHP_EOL . $e->getComparisonFailure()->getDiff();
293 }
294 }
295 $value = '[see diff]';
296 }
297 $lines[$valueIndex][$columnIndex] = str_pad($value, $columnLength, ' ');
298 }
299 }
300
301 foreach ($lines as $line) {
302 $result .= implode('|', $line) . PHP_EOL;
303 }
304
305 foreach ($linesFromXmlValues as $lineFromXmlValues) {
306 $result .= PHP_EOL . $lineFromXmlValues . PHP_EOL;
307 }
308
309 return $result;
310 }
311
312 /**
313 * @param array $assertion
314 * @param array $record
315 * @return array
316 */
317 protected function getDifferentFields(array $assertion, array $record) {
318 $differentFields = array();
319
320 foreach ($assertion as $field => $value) {
321 if (strpos($value, '\\*') === 0) {
322 continue;
323 } elseif (strpos($value, '<?xml') === 0) {
324 try {
325 $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$field]);
326 } catch (\PHPUnit_Framework_ExpectationFailedException $e) {
327 $differentFields[] = $field;
328 }
329 } elseif ($value === NULL && $record[$field] !== $value) {
330 $differentFields[] = $field;
331 } elseif ((string)$record[$field] !== (string)$value) {
332 $differentFields[] = $field;
333 }
334 }
335
336 return $differentFields;
337 }
338
339 /**
340 * @param ResponseContent $responseContent
341 * @param string $structureRecordIdentifier
342 * @param string $structureFieldName
343 * @param string $tableName
344 * @param string $fieldName
345 * @param string|array $values
346 */
347 protected function assertResponseContentStructureHasRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) {
348 $nonMatchingVariants = array();
349
350 foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) {
351 $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values);
352
353 if (empty($nonMatchingValues)) {
354 // Increase assertion counter
355 $this->assertEmpty($nonMatchingValues);
356 return;
357 }
358
359 $nonMatchingVariants[$path] = $nonMatchingValues;
360 }
361
362 $nonMatchingMessage = '';
363 foreach ($nonMatchingVariants as $path => $nonMatchingValues) {
364 $nonMatchingMessage .= '* ' . $path . ': ' . implode(', ', $nonMatchingValues);
365 }
366
367 $this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '"' . LF . $nonMatchingMessage);
368 }
369
370 /**
371 * @param ResponseContent $responseContent
372 * @param string $structureRecordIdentifier
373 * @param string $structureFieldName
374 * @param string $tableName
375 * @param string $fieldName
376 * @param string|array $values
377 */
378 protected function assertResponseContentStructureDoesNotHaveRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) {
379 if (is_string($values)) {
380 $values = array($values);
381 }
382
383 $matchingVariants = array();
384
385 foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) {
386 $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values);
387 $matchingValues = array_diff($values, $nonMatchingValues);
388
389 if (!empty($matchingValues)) {
390 $matchingVariants[$path] = $matchingValues;
391 }
392 }
393
394 if (empty($matchingVariants)) {
395 // Increase assertion counter
396 $this->assertEmpty($matchingVariants);
397 return;
398 }
399
400 $matchingMessage = '';
401 foreach ($matchingVariants as $path => $matchingValues) {
402 $matchingMessage .= '* ' . $path . ': ' . implode(', ', $matchingValues);
403 }
404
405 $this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '"' . LF . $matchingMessage);
406 }
407
408 /**
409 * @param ResponseContent $responseContent
410 * @param string $tableName
411 * @param string $fieldName
412 * @param string|array $values
413 */
414 protected function assertResponseContentHasRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) {
415 $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values);
416
417 if (!empty($nonMatchingValues)) {
418 $this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $nonMatchingValues));
419 }
420
421 // Increase assertion counter
422 $this->assertEmpty($nonMatchingValues);
423 }
424
425 /**
426 * @param ResponseContent $responseContent
427 * @param string $tableName
428 * @param string $fieldName
429 * @param string|array $values
430 */
431 protected function assertResponseContentDoesNotHaveRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) {
432 if (is_string($values)) {
433 $values = array($values);
434 }
435
436 $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values);
437 $matchingValues = array_diff($values, $nonMatchingValues);
438
439 if (!empty($matchingValues)) {
440 $this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $matchingValues));
441 }
442
443 // Increase assertion counter
444 $this->assertTrue(TRUE);
445 }
446
447 /**
448 * @param string|array $data
449 * @param string $tableName
450 * @param string $fieldName
451 * @param string|array $values
452 * @return array
453 */
454 protected function getNonMatchingValuesFrontendResponseRecords($data, $tableName, $fieldName, $values) {
455 if (empty($data) || !is_array($data)) {
456 $this->fail('Frontend Response data does not have any records');
457 }
458
459 if (is_string($values)) {
460 $values = array($values);
461 }
462
463 foreach ($data as $recordIdentifier => $recordData) {
464 if (strpos($recordIdentifier, $tableName . ':') !== 0) {
465 continue;
466 }
467
468 if (($foundValueIndex = array_search($recordData[$fieldName], $values)) !== FALSE) {
469 unset($values[$foundValueIndex]);
470 }
471 }
472
473 return $values;
474 }
475
476 }