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