[BUGFIX] Fix functional tests for EXT:core on PostgreSQL
[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 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Doctrine\DBAL\DBALException;
18 use TYPO3\CMS\Core\Database\Connection;
19 use TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\DataSet;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\TestingFramework\Core\Testbase;
22
23 /**
24 * Functional test for the DataHandler
25 */
26 abstract class AbstractDataHandlerActionTestCase extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
27 {
28 const VALUE_BackendUserId = 1;
29
30 /**
31 * @var string
32 */
33 protected $scenarioDataSetDirectory;
34
35 /**
36 * @var string
37 */
38 protected $assertionDataSetDirectory;
39
40 /**
41 * If this value is NULL, log entries are not considered.
42 * If it's an integer value, the number of log entries is asserted.
43 *
44 * @var NULL|int
45 */
46 protected $expectedErrorLogEntries = 0;
47
48 /**
49 * @var array
50 */
51 protected $testExtensionsToLoad = [
52 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial',
53 ];
54
55 /**
56 * @var array
57 */
58 protected $pathsToLinkInTestInstance = [
59 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/AdditionalConfiguration.php' => 'typo3conf/AdditionalConfiguration.php',
60 ];
61
62 /**
63 * @var array
64 */
65 protected $recordIds = [];
66
67 /**
68 * @var \TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\ActionService
69 */
70 protected $actionService;
71
72 /**
73 * @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
74 */
75 protected $backendUser;
76
77 protected function setUp()
78 {
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 protected function tearDown()
90 {
91 $this->assertErrorLogEntries();
92 unset($this->actionService);
93 unset($this->recordIds);
94 parent::tearDown();
95 }
96
97 /**
98 * @return \TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\ActionService
99 */
100 protected function getActionService()
101 {
102 return GeneralUtility::makeInstance(
103 \TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\ActionService::class
104 );
105 }
106
107 /**
108 * @param string $dataSetName
109 */
110 protected function importScenarioDataSet($dataSetName)
111 {
112 $fileName = rtrim($this->scenarioDataSetDirectory, '/') . '/' . $dataSetName . '.csv';
113 $fileName = GeneralUtility::getFileAbsFileName($fileName);
114
115 $dataSet = DataSet::read($fileName, true);
116
117 foreach ($dataSet->getTableNames() as $tableName) {
118 $connection = $this->getConnectionPool()->getConnectionForTable($tableName);
119 foreach ($dataSet->getElements($tableName) as $element) {
120 try {
121 $connection->insert($tableName, $element);
122 } catch (DBALException $e) {
123 $this->fail('SQL Error for table "' . $tableName . '": ' . LF . $e->getMessage());
124 }
125 }
126 Testbase::resetTableSequences($connection, $tableName);
127 }
128 }
129
130 protected function assertAssertionDataSet($dataSetName)
131 {
132 $fileName = rtrim($this->assertionDataSetDirectory, '/') . '/' . $dataSetName . '.csv';
133 $fileName = GeneralUtility::getFileAbsFileName($fileName);
134
135 $dataSet = DataSet::read($fileName);
136 $failMessages = [];
137
138 foreach ($dataSet->getTableNames() as $tableName) {
139 $hasUidField = ($dataSet->getIdIndex($tableName) !== null);
140 $records = $this->getAllRecords($tableName, $hasUidField);
141 foreach ($dataSet->getElements($tableName) as $assertion) {
142 $result = $this->assertInRecords($assertion, $records);
143 if ($result === false) {
144 if ($hasUidField && empty($records[$assertion['uid']])) {
145 $failMessages[] = 'Record "' . $tableName . ':' . $assertion['uid'] . '" not found in database';
146 continue;
147 }
148 $recordIdentifier = $tableName . ($hasUidField ? ':' . $assertion['uid'] : '');
149 $additionalInformation = ($hasUidField ? $this->renderRecords($assertion, $records[$assertion['uid']]) : $this->arrayToString($assertion));
150 $failMessages[] = 'Assertion in data-set failed for "' . $recordIdentifier . '":' . LF . $additionalInformation;
151 // Unset failed asserted record
152 if ($hasUidField) {
153 unset($records[$assertion['uid']]);
154 }
155 } else {
156 // Unset asserted record
157 unset($records[$result]);
158 // Increase assertion counter
159 $this->assertTrue($result !== false);
160 }
161 }
162 if (!empty($records)) {
163 foreach ($records as $record) {
164 $recordIdentifier = $tableName . ':' . $record['uid'];
165 $emptyAssertion = array_fill_keys($dataSet->getFields($tableName), '[none]');
166 $reducedRecord = array_intersect_key($record, $emptyAssertion);
167 $additionalInformation = ($hasUidField ? $this->renderRecords($emptyAssertion, $reducedRecord) : $this->arrayToString($reducedRecord));
168 $failMessages[] = 'Not asserted record found for "' . $recordIdentifier . '":' . LF . $additionalInformation;
169 }
170 }
171 }
172
173 if (!empty($failMessages)) {
174 $this->fail(implode(LF, $failMessages));
175 }
176 }
177
178 /**
179 * @param array $assertion
180 * @param array $records
181 * @return bool|int|string
182 */
183 protected function assertInRecords(array $assertion, array $records)
184 {
185 foreach ($records as $index => $record) {
186 $differentFields = $this->getDifferentFields($assertion, $record);
187
188 if (empty($differentFields)) {
189 return $index;
190 }
191 }
192
193 return false;
194 }
195
196 /**
197 * Asserts correct number of warning and error log entries.
198 *
199 * @return void
200 */
201 protected function assertErrorLogEntries()
202 {
203 if ($this->expectedErrorLogEntries === null) {
204 return;
205 }
206
207 $queryBuilder = $this->getConnectionPool()
208 ->getQueryBuilderForTable('sys_log');
209 $queryBuilder->getRestrictions()->removeAll();
210 $statement = $queryBuilder
211 ->select('*')
212 ->from('sys_log')
213 ->where(
214 $queryBuilder->expr()->in(
215 'error',
216 $queryBuilder->createNamedParameter([1, 2], Connection::PARAM_INT_ARRAY)
217 )
218 )
219 ->execute();
220
221 $actualErrorLogEntries = $statement->rowCount();
222 if ($actualErrorLogEntries === $this->expectedErrorLogEntries) {
223 $this->assertSame($this->expectedErrorLogEntries, $actualErrorLogEntries);
224 } else {
225 $failureMessage = 'Expected ' . $this->expectedErrorLogEntries . ' entries in sys_log, but got ' . $actualErrorLogEntries . LF;
226 while ($entry = $statement->fetch()) {
227 $entryData = unserialize($entry['log_data']);
228 $entryMessage = vsprintf($entry['details'], $entryData);
229 $failureMessage .= '* ' . $entryMessage . LF;
230 }
231 $this->fail($failureMessage);
232 }
233 }
234
235 /**
236 * @param string $tableName
237 * @param bool $hasUidField
238 * @return array
239 */
240 protected function getAllRecords($tableName, $hasUidField = false)
241 {
242 $queryBuilder = $this->getConnectionPool()
243 ->getQueryBuilderForTable($tableName);
244 $queryBuilder->getRestrictions()->removeAll();
245 $statement = $queryBuilder
246 ->select('*')
247 ->from($tableName)
248 ->execute();
249
250 if (!$hasUidField) {
251 return $statement->fetchAll();
252 }
253
254 $allRecords = [];
255 while ($record = $statement->fetch()) {
256 $index = $record['uid'];
257 $allRecords[$index] = $record;
258 }
259
260 return $allRecords;
261 }
262
263 /**
264 * @param array $array
265 * @return string
266 */
267 protected function arrayToString(array $array)
268 {
269 $elements = [];
270 foreach ($array as $key => $value) {
271 if (is_array($value)) {
272 $value = $this->arrayToString($value);
273 }
274 $elements[] = "'" . $key . "' => '" . $value . "'";
275 }
276 return 'array(' . PHP_EOL . ' ' . implode(', ' . PHP_EOL . ' ', $elements) . PHP_EOL . ')' . PHP_EOL;
277 }
278
279 /**
280 * @param array $assertion
281 * @param array $record
282 * @return string
283 */
284 protected function renderRecords(array $assertion, array $record)
285 {
286 $differentFields = $this->getDifferentFields($assertion, $record);
287 $columns = [
288 'fields' => ['Fields'],
289 'assertion' => ['Assertion'],
290 'record' => ['Record'],
291 ];
292 $lines = [];
293 $linesFromXmlValues = [];
294 $result = '';
295
296 foreach ($differentFields as $differentField) {
297 $columns['fields'][] = $differentField;
298 $columns['assertion'][] = ($assertion[$differentField] === null ? 'NULL' : $assertion[$differentField]);
299 $columns['record'][] = ($record[$differentField] === null ? 'NULL' : $record[$differentField]);
300 }
301
302 foreach ($columns as $columnIndex => $column) {
303 $columnLength = null;
304 foreach ($column as $value) {
305 if (strpos($value, '<?xml') === 0) {
306 $value = '[see diff]';
307 }
308 $valueLength = strlen($value);
309 if (empty($columnLength) || $valueLength > $columnLength) {
310 $columnLength = $valueLength;
311 }
312 }
313 foreach ($column as $valueIndex => $value) {
314 if (strpos($value, '<?xml') === 0) {
315 if ($columnIndex === 'assertion') {
316 try {
317 $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$columns['fields'][$valueIndex]]);
318 } catch (\PHPUnit_Framework_ExpectationFailedException $e) {
319 $linesFromXmlValues[] = 'Diff for field "' . $columns['fields'][$valueIndex] . '":' . PHP_EOL . $e->getComparisonFailure()->getDiff();
320 }
321 }
322 $value = '[see diff]';
323 }
324 $lines[$valueIndex][$columnIndex] = str_pad($value, $columnLength, ' ');
325 }
326 }
327
328 foreach ($lines as $line) {
329 $result .= implode('|', $line) . PHP_EOL;
330 }
331
332 foreach ($linesFromXmlValues as $lineFromXmlValues) {
333 $result .= PHP_EOL . $lineFromXmlValues . PHP_EOL;
334 }
335
336 return $result;
337 }
338
339 /**
340 * @param array $assertion
341 * @param array $record
342 * @return array
343 */
344 protected function getDifferentFields(array $assertion, array $record)
345 {
346 $differentFields = [];
347
348 foreach ($assertion as $field => $value) {
349 if (strpos($value, '\\*') === 0) {
350 continue;
351 } elseif (strpos($value, '<?xml') === 0) {
352 try {
353 $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$field]);
354 } catch (\PHPUnit_Framework_ExpectationFailedException $e) {
355 $differentFields[] = $field;
356 }
357 } elseif ($value === null && $record[$field] !== $value) {
358 $differentFields[] = $field;
359 } elseif ((string)$record[$field] !== (string)$value) {
360 $differentFields[] = $field;
361 }
362 }
363
364 return $differentFields;
365 }
366
367 /**
368 * @return \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\HasRecordConstraint
369 */
370 protected function getRequestSectionHasRecordConstraint()
371 {
372 return new \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\HasRecordConstraint();
373 }
374
375 /**
376 * @return \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\DoesNotHaveRecordConstraint
377 */
378 protected function getRequestSectionDoesNotHaveRecordConstraint()
379 {
380 return new \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\DoesNotHaveRecordConstraint();
381 }
382
383 /**
384 * @return \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\StructureHasRecordConstraint
385 */
386 protected function getRequestSectionStructureHasRecordConstraint()
387 {
388 return new \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\StructureHasRecordConstraint();
389 }
390
391 /**
392 * @return \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\StructureDoesNotHaveRecordConstraint
393 */
394 protected function getRequestSectionStructureDoesNotHaveRecordConstraint()
395 {
396 return new \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\StructureDoesNotHaveRecordConstraint();
397 }
398 }