aaa99d3a743efad827baab15346937dfe7d7c43b
[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\ConnectionPool;
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\CMS\Core\Tests\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 = array(
51 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial',
52 );
53
54 /**
55 * @var array
56 */
57 protected $pathsToLinkInTestInstance = array(
58 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/AdditionalConfiguration.php' => 'typo3conf/AdditionalConfiguration.php',
59 );
60
61 /**
62 * @var array
63 */
64 protected $recordIds = array();
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 = (new ConnectionPool())->getConnectionForTable($tableName);
119 try {
120 $connection->insert(
121 $tableName,
122 $element
123 );
124 } catch (DBALException $e) {
125 $this->fail('SQL Error for table "' . $tableName . '": ' . LF . $e->getMessage());
126 }
127 }
128 }
129 }
130
131 protected function assertAssertionDataSet($dataSetName)
132 {
133 $fileName = rtrim($this->assertionDataSetDirectory, '/') . '/' . $dataSetName . '.csv';
134 $fileName = GeneralUtility::getFileAbsFileName($fileName);
135
136 $dataSet = DataSet::read($fileName);
137 $failMessages = array();
138
139 foreach ($dataSet->getTableNames() as $tableName) {
140 $hasUidField = ($dataSet->getIdIndex($tableName) !== null);
141 $records = $this->getAllRecords($tableName, $hasUidField);
142 foreach ($dataSet->getElements($tableName) as $assertion) {
143 $result = $this->assertInRecords($assertion, $records);
144 if ($result === false) {
145 if ($hasUidField && empty($records[$assertion['uid']])) {
146 $failMessages[] = 'Record "' . $tableName . ':' . $assertion['uid'] . '" not found in database';
147 continue;
148 }
149 $recordIdentifier = $tableName . ($hasUidField ? ':' . $assertion['uid'] : '');
150 $additionalInformation = ($hasUidField ? $this->renderRecords($assertion, $records[$assertion['uid']]) : $this->arrayToString($assertion));
151 $failMessages[] = 'Assertion in data-set failed for "' . $recordIdentifier . '":' . LF . $additionalInformation;
152 // Unset failed asserted record
153 if ($hasUidField) {
154 unset($records[$assertion['uid']]);
155 }
156 } else {
157 // Unset asserted record
158 unset($records[$result]);
159 // Increase assertion counter
160 $this->assertTrue($result !== false);
161 }
162 }
163 if (!empty($records)) {
164 foreach ($records as $record) {
165 $recordIdentifier = $tableName . ':' . $record['uid'];
166 $emptyAssertion = array_fill_keys($dataSet->getFields($tableName), '[none]');
167 $reducedRecord = array_intersect_key($record, $emptyAssertion);
168 $additionalInformation = ($hasUidField ? $this->renderRecords($emptyAssertion, $reducedRecord) : $this->arrayToString($reducedRecord));
169 $failMessages[] = 'Not asserted record found for "' . $recordIdentifier . '":' . LF . $additionalInformation;
170 }
171 }
172 }
173
174 if (!empty($failMessages)) {
175 $this->fail(implode(LF, $failMessages));
176 }
177 }
178
179 /**
180 * @param array $assertion
181 * @param array $records
182 * @return bool|int|string
183 */
184 protected function assertInRecords(array $assertion, array $records)
185 {
186 foreach ($records as $index => $record) {
187 $differentFields = $this->getDifferentFields($assertion, $record);
188
189 if (empty($differentFields)) {
190 return $index;
191 }
192 }
193
194 return false;
195 }
196
197 /**
198 * Asserts correct number of warning and error log entries.
199 *
200 * @return void
201 */
202 protected function assertErrorLogEntries()
203 {
204 if ($this->expectedErrorLogEntries === null) {
205 return;
206 }
207 $errorLogEntries = $this->getDatabaseConnection()->exec_SELECTgetRows('*', 'sys_log', 'error IN (1,2)');
208 $actualErrorLogEntries = count($errorLogEntries);
209 if ($actualErrorLogEntries === $this->expectedErrorLogEntries) {
210 $this->assertSame($this->expectedErrorLogEntries, $actualErrorLogEntries);
211 } else {
212 $failureMessage = 'Expected ' . $this->expectedErrorLogEntries . ' entries in sys_log, but got ' . $actualErrorLogEntries . LF;
213 foreach ($errorLogEntries as $entry) {
214 $entryData = unserialize($entry['log_data']);
215 $entryMessage = vsprintf($entry['details'], $entryData);
216 $failureMessage .= '* ' . $entryMessage . LF;
217 }
218 $this->fail($failureMessage);
219 }
220 }
221
222 /**
223 * @param string $tableName
224 * @param bool $hasUidField
225 * @return array
226 */
227 protected function getAllRecords($tableName, $hasUidField = false)
228 {
229 $allRecords = array();
230
231 $records = $this->getDatabaseConnection()->exec_SELECTgetRows(
232 '*',
233 $tableName,
234 '1=1',
235 '',
236 '',
237 '',
238 ($hasUidField ? 'uid' : '')
239 );
240
241 if (!empty($records)) {
242 $allRecords = $records;
243 }
244
245 return $allRecords;
246 }
247
248 /**
249 * @param array $array
250 * @return string
251 */
252 protected function arrayToString(array $array)
253 {
254 $elements = array();
255 foreach ($array as $key => $value) {
256 if (is_array($value)) {
257 $value = $this->arrayToString($value);
258 }
259 $elements[] = "'" . $key . "' => '" . $value . "'";
260 }
261 return 'array(' . PHP_EOL . ' ' . implode(', ' . PHP_EOL . ' ', $elements) . PHP_EOL . ')' . PHP_EOL;
262 }
263
264 /**
265 * @param array $assertion
266 * @param array $record
267 * @return string
268 */
269 protected function renderRecords(array $assertion, array $record)
270 {
271 $differentFields = $this->getDifferentFields($assertion, $record);
272 $columns = array(
273 'fields' => array('Fields'),
274 'assertion' => array('Assertion'),
275 'record' => array('Record'),
276 );
277 $lines = array();
278 $linesFromXmlValues = array();
279 $result = '';
280
281 foreach ($differentFields as $differentField) {
282 $columns['fields'][] = $differentField;
283 $columns['assertion'][] = ($assertion[$differentField] === null ? 'NULL' : $assertion[$differentField]);
284 $columns['record'][] = ($record[$differentField] === null ? 'NULL' : $record[$differentField]);
285 }
286
287 foreach ($columns as $columnIndex => $column) {
288 $columnLength = null;
289 foreach ($column as $value) {
290 if (strpos($value, '<?xml') === 0) {
291 $value = '[see diff]';
292 }
293 $valueLength = strlen($value);
294 if (empty($columnLength) || $valueLength > $columnLength) {
295 $columnLength = $valueLength;
296 }
297 }
298 foreach ($column as $valueIndex => $value) {
299 if (strpos($value, '<?xml') === 0) {
300 if ($columnIndex === 'assertion') {
301 try {
302 $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$columns['fields'][$valueIndex]]);
303 } catch (\PHPUnit_Framework_ExpectationFailedException $e) {
304 $linesFromXmlValues[] = 'Diff for field "' . $columns['fields'][$valueIndex] . '":' . PHP_EOL . $e->getComparisonFailure()->getDiff();
305 }
306 }
307 $value = '[see diff]';
308 }
309 $lines[$valueIndex][$columnIndex] = str_pad($value, $columnLength, ' ');
310 }
311 }
312
313 foreach ($lines as $line) {
314 $result .= implode('|', $line) . PHP_EOL;
315 }
316
317 foreach ($linesFromXmlValues as $lineFromXmlValues) {
318 $result .= PHP_EOL . $lineFromXmlValues . PHP_EOL;
319 }
320
321 return $result;
322 }
323
324 /**
325 * @param array $assertion
326 * @param array $record
327 * @return array
328 */
329 protected function getDifferentFields(array $assertion, array $record)
330 {
331 $differentFields = array();
332
333 foreach ($assertion as $field => $value) {
334 if (strpos($value, '\\*') === 0) {
335 continue;
336 } elseif (strpos($value, '<?xml') === 0) {
337 try {
338 $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$field]);
339 } catch (\PHPUnit_Framework_ExpectationFailedException $e) {
340 $differentFields[] = $field;
341 }
342 } elseif ($value === null && $record[$field] !== $value) {
343 $differentFields[] = $field;
344 } elseif ((string)$record[$field] !== (string)$value) {
345 $differentFields[] = $field;
346 }
347 }
348
349 return $differentFields;
350 }
351
352 /**
353 * @return \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\HasRecordConstraint
354 */
355 protected function getRequestSectionHasRecordConstraint()
356 {
357 return new \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\HasRecordConstraint();
358 }
359
360 /**
361 * @return \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\DoesNotHaveRecordConstraint
362 */
363 protected function getRequestSectionDoesNotHaveRecordConstraint()
364 {
365 return new \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\DoesNotHaveRecordConstraint();
366 }
367
368 /**
369 * @return \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\StructureHasRecordConstraint
370 */
371 protected function getRequestSectionStructureHasRecordConstraint()
372 {
373 return new \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\StructureHasRecordConstraint();
374 }
375
376 /**
377 * @return \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\StructureDoesNotHaveRecordConstraint
378 */
379 protected function getRequestSectionStructureDoesNotHaveRecordConstraint()
380 {
381 return new \TYPO3\CMS\Core\Tests\Functional\Framework\Constraint\RequestSection\StructureDoesNotHaveRecordConstraint();
382 }
383 }