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