08d3b1d368e1dbaee0dd72a144aa4ec2b7617ea6
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Configuration / ConfigurationManagerTest.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\Tests\Unit\Configuration;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
20 use TYPO3\CMS\Core\Core\Environment;
21 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
22
23 /**
24 * Test case
25 */
26 class ConfigurationManagerTest extends UnitTestCase
27 {
28 /**
29 * @var ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject
30 */
31 protected $subject;
32
33 protected function setUp(): void
34 {
35 $this->createSubjectWithMockedMethods(
36 [
37 'getDefaultConfigurationFileLocation',
38 'getLocalConfigurationFileLocation',
39 ]
40 );
41 }
42
43 /**
44 * @param array $methods
45 */
46 protected function createSubjectWithMockedMethods(array $methods): void
47 {
48 $this->subject = $this->getMockBuilder(ConfigurationManager::class)
49 ->setMethods($methods)
50 ->getMock();
51 }
52
53 /**
54 * @test
55 */
56 public function getDefaultConfigurationExecutesDefinedDefaultConfigurationFile(): void
57 {
58 $this->expectException(\RuntimeException::class);
59 $this->expectExceptionCode(1310203814);
60
61 $defaultConfigurationFile = Environment::getVarPath() . '/tests/' . $this->getUniqueId('defaultConfiguration');
62 file_put_contents(
63 $defaultConfigurationFile,
64 '<?php throw new \RuntimeException(\'foo\', 1310203814); ?>'
65 );
66 $this->testFilesToDelete[] = $defaultConfigurationFile;
67
68 $this->subject
69 ->expects($this->once())
70 ->method('getDefaultConfigurationFileLocation')
71 ->will($this->returnValue($defaultConfigurationFile));
72 $this->subject->getDefaultConfiguration();
73 }
74
75 /**
76 * @test
77 */
78 public function getLocalConfigurationExecutesDefinedConfigurationFile(): void
79 {
80 $this->expectException(\RuntimeException::class);
81
82 $configurationFile = Environment::getVarPath() . '/tests/' . $this->getUniqueId('localConfiguration');
83 file_put_contents(
84 $configurationFile,
85 '<?php throw new \RuntimeException(\'foo\', 1310203815); ?>'
86 );
87 $this->testFilesToDelete[] = $configurationFile;
88
89 $this->subject
90 ->expects($this->once())
91 ->method('getLocalConfigurationFileLocation')
92 ->will($this->returnValue($configurationFile));
93 $this->subject->getLocalConfiguration();
94 }
95
96 /**
97 * @test
98 */
99 public function updateLocalConfigurationWritesNewMergedLocalConfigurationArray(): void
100 {
101 $currentLocalConfiguration = [
102 'notChanged' => 23,
103 'changed' => 'unChanged',
104 ];
105 $overrideConfiguration = [
106 'changed' => 'changed',
107 'new' => 'new'
108 ];
109 $expectedConfiguration = [
110 'notChanged' => 23,
111 'changed' => 'changed',
112 'new' => 'new',
113 ];
114
115 $this->createSubjectWithMockedMethods(
116 [
117 'getLocalConfiguration',
118 'writeLocalConfiguration',
119 ]
120 );
121 $this->subject->expects($this->once())
122 ->method('getLocalConfiguration')
123 ->will($this->returnValue($currentLocalConfiguration));
124 $this->subject->expects($this->once())
125 ->method('writeLocalConfiguration')
126 ->with($expectedConfiguration);
127
128 $this->subject->updateLocalConfiguration($overrideConfiguration);
129 }
130
131 /**
132 * @test
133 */
134 public function getDefaultConfigurationValueByPathReturnsCorrectValue(): void
135 {
136 $this->createSubjectWithMockedMethods(
137 [
138 'getDefaultConfiguration',
139 ]
140 );
141 $this->subject->expects($this->once())
142 ->method('getDefaultConfiguration')
143 ->will($this->returnValue(
144 [
145 'path' => 'value',
146 ]
147 ));
148
149 $this->assertSame('value', $this->subject->getDefaultConfigurationValueByPath('path'));
150 }
151
152 /**
153 * @test
154 */
155 public function getLocalConfigurationValueByPathReturnsCorrectValue(): void
156 {
157 $this->createSubjectWithMockedMethods(
158 [
159 'getLocalConfiguration',
160 ]
161 );
162 $this->subject->expects($this->once())
163 ->method('getLocalConfiguration')
164 ->will($this->returnValue(
165 [
166 'path' => 'value',
167 ]
168 ));
169
170 $this->assertSame('value', $this->subject->getLocalConfigurationValueByPath('path'));
171 }
172
173 /**
174 * @test
175 */
176 public function getConfigurationValueByPathReturnsCorrectValue(): void
177 {
178 $this->createSubjectWithMockedMethods(
179 [
180 'getDefaultConfiguration',
181 'getLocalConfiguration',
182 ]
183 );
184 $this->subject->expects($this->once())
185 ->method('getDefaultConfiguration')
186 ->will($this->returnValue(
187 [
188 'path' => 'value',
189 ]
190 ));
191 $this->subject->expects($this->once())
192 ->method('getLocalConfiguration')
193 ->will($this->returnValue(
194 [
195 'path' => 'valueOverride',
196 ]
197 ));
198
199 $this->assertSame('valueOverride', $this->subject->getConfigurationValueByPath('path'));
200 }
201
202 /**
203 * @test
204 */
205 public function setLocalConfigurationValueByPathReturnFalseIfPathIsNotValid(): void
206 {
207 $this->createSubjectWithMockedMethods([
208 'isValidLocalConfigurationPath',
209 ]);
210 $this->subject->expects($this->once())
211 ->method('isValidLocalConfigurationPath')
212 ->will($this->returnValue(false));
213
214 $this->assertFalse($this->subject->setLocalConfigurationValueByPath('path', 'value'));
215 }
216
217 /**
218 * @test
219 */
220 public function setLocalConfigurationValueByPathUpdatesValueDefinedByPath(): void
221 {
222 $currentLocalConfiguration = [
223 'notChanged' => 23,
224 'toUpdate' => 'notUpdated',
225 ];
226 $expectedConfiguration = [
227 'notChanged' => 23,
228 'toUpdate' => 'updated',
229 ];
230
231 $this->createSubjectWithMockedMethods(
232 [
233 'isValidLocalConfigurationPath',
234 'getLocalConfiguration',
235 'writeLocalConfiguration',
236 ]
237 );
238 $this->subject->expects($this->once())
239 ->method('isValidLocalConfigurationPath')
240 ->will($this->returnValue(true));
241 $this->subject->expects($this->once())
242 ->method('getLocalConfiguration')
243 ->will($this->returnValue($currentLocalConfiguration));
244 $this->subject->expects($this->once())
245 ->method('writeLocalConfiguration')
246 ->with($expectedConfiguration);
247
248 $this->subject->setLocalConfigurationValueByPath('toUpdate', 'updated');
249 }
250
251 /**
252 * @test
253 */
254 public function setLocalConfigurationValuesByPathValuePairsSetsPathValuePairs(): void
255 {
256 $currentLocalConfiguration = [
257 'notChanged' => 23,
258 'toUpdate' => 'notUpdated',
259 ];
260 $expectedConfiguration = [
261 'notChanged' => 23,
262 'toUpdate' => 'updated',
263 'new' => 'new',
264 ];
265
266 $this->createSubjectWithMockedMethods(
267 [
268 'isValidLocalConfigurationPath',
269 'getLocalConfiguration',
270 'writeLocalConfiguration',
271 ]
272 );
273 $this->subject->expects($this->any())
274 ->method('isValidLocalConfigurationPath')
275 ->will($this->returnValue(true));
276 $this->subject->expects($this->once())
277 ->method('getLocalConfiguration')
278 ->will($this->returnValue($currentLocalConfiguration));
279 $this->subject->expects($this->once())
280 ->method('writeLocalConfiguration')
281 ->with($expectedConfiguration);
282
283 $pairs = [
284 'toUpdate' => 'updated',
285 'new' => 'new'
286 ];
287 $this->subject->setLocalConfigurationValuesByPathValuePairs($pairs);
288 }
289
290 /**
291 * @test
292 */
293 public function removeLocalConfigurationKeysByPathRemovesGivenPathsFromConfigurationAndReturnsTrue(): void
294 {
295 $currentLocalConfiguration = [
296 'toRemove1' => 'foo',
297 'notChanged' => 23,
298 'toRemove2' => 'bar',
299 ];
300 $expectedConfiguration = [
301 'notChanged' => 23,
302 ];
303
304 $this->createSubjectWithMockedMethods(
305 [
306 'getLocalConfiguration',
307 'writeLocalConfiguration',
308 ]
309 );
310 $this->subject->expects($this->once())
311 ->method('getLocalConfiguration')
312 ->will($this->returnValue($currentLocalConfiguration));
313 $this->subject->expects($this->once())
314 ->method('writeLocalConfiguration')
315 ->with($expectedConfiguration);
316
317 $removePaths = [
318 'toRemove1',
319 'toRemove2',
320 ];
321 $this->assertTrue($this->subject->removeLocalConfigurationKeysByPath($removePaths));
322 }
323
324 /**
325 * @test
326 */
327 public function removeLocalConfigurationKeysByPathReturnsFalseIfNothingIsRemoved(): void
328 {
329 $currentLocalConfiguration = [
330 'notChanged' => 23,
331 ];
332 $this->createSubjectWithMockedMethods(
333 [
334 'getLocalConfiguration',
335 'writeLocalConfiguration',
336 ]
337 );
338 $this->subject->expects($this->once())
339 ->method('getLocalConfiguration')
340 ->will($this->returnValue($currentLocalConfiguration));
341 $this->subject->expects($this->never())
342 ->method('writeLocalConfiguration');
343
344 $removeNothing = [];
345 $this->assertFalse($this->subject->removeLocalConfigurationKeysByPath($removeNothing));
346 }
347
348 /**
349 * @test
350 */
351 public function removeLocalConfigurationKeysByPathReturnsFalseIfSomethingInexistentIsRemoved(): void
352 {
353 $currentLocalConfiguration = [
354 'notChanged' => 23,
355 ];
356 $this->createSubjectWithMockedMethods(
357 [
358 'getLocalConfiguration',
359 'writeLocalConfiguration',
360 ]
361 );
362 $this->subject->expects($this->once())
363 ->method('getLocalConfiguration')
364 ->will($this->returnValue($currentLocalConfiguration));
365 $this->subject->expects($this->never())
366 ->method('writeLocalConfiguration');
367
368 $removeNonExisting = ['notPresent'];
369 $this->assertFalse($this->subject->removeLocalConfigurationKeysByPath($removeNonExisting));
370 }
371
372 /**
373 * @test
374 */
375 public function canWriteConfigurationReturnsFalseIfLocalConfigurationFileIsNotWritable(): void
376 {
377 if (\function_exists('posix_getegid') && posix_getegid() === 0) {
378 $this->markTestSkipped('Test skipped if run on linux as root');
379 }
380 /** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
381 $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
382
383 $file = '../typo3temp/var/tests/' . $this->getUniqueId('test_');
384 $absoluteFile = Environment::getLegacyConfigPath() . '/' . $file;
385 touch($absoluteFile);
386 $this->testFilesToDelete[] = $absoluteFile;
387 chmod($absoluteFile, 0444);
388 clearstatcache();
389
390 $subject->_set('localConfigurationFile', $file);
391
392 $result = $subject->canWriteConfiguration();
393
394 chmod($absoluteFile, 0644);
395
396 $this->assertFalse($result);
397 }
398
399 /**
400 * @test
401 */
402 public function canWriteConfigurationReturnsTrueIfDirectoryAndFilesAreWritable(): void
403 {
404 /** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
405 $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
406
407 $directory = 'typo3temp/var/tests/' . $this->getUniqueId('test_');
408 $absoluteDirectory = Environment::getPublicPath() . '/' . $directory;
409 mkdir($absoluteDirectory);
410
411 $file = 'typo3temp/var/tests/' . $this->getUniqueId('test_');
412 $absoluteFile1 = Environment::getPublicPath() . '/' . $file;
413 touch($absoluteFile1);
414 $this->testFilesToDelete[] = $absoluteFile1;
415 $subject->_set('localConfigurationFile', $absoluteFile1);
416
417 clearstatcache();
418
419 $result = $subject->canWriteConfiguration();
420
421 $this->assertTrue($result);
422 $this->testFilesToDelete[] = $absoluteDirectory;
423 }
424
425 /**
426 * @test
427 */
428 public function writeLocalConfigurationWritesSortedContentToConfigurationFile(): void
429 {
430 $configurationFile = Environment::getVarPath() . '/tests/' . $this->getUniqueId('localConfiguration');
431 if (!is_file($configurationFile)) {
432 if (!$fh = fopen($configurationFile, 'wb')) {
433 $this->markTestSkipped('Can not create file ' . $configurationFile . '. Please check your write permissions.');
434 }
435 fclose($fh);
436 }
437
438 if (!@is_file($configurationFile)) {
439 throw new \RuntimeException(
440 'File ' . $configurationFile . ' could not be found. Please check your write permissions',
441 1346364362
442 );
443 }
444 $this->testFilesToDelete[] = $configurationFile;
445
446 $this->subject
447 ->expects($this->any())
448 ->method('getLocalConfigurationFileLocation')
449 ->will($this->returnValue($configurationFile));
450
451 $pairs = [
452 'foo' => 42,
453 'bar' => 23
454 ];
455 $expectedContent =
456 '<?php' . LF .
457 'return [' . LF .
458 ' \'bar\' => 23,' . LF .
459 ' \'foo\' => 42,' . LF .
460 '];' . LF;
461
462 $this->subject->writeLocalConfiguration($pairs);
463 $this->assertSame($expectedContent, file_get_contents($configurationFile));
464 }
465
466 /**
467 * @test
468 */
469 public function createLocalConfigurationFromFactoryConfigurationThrowsExceptionIfFileExists(): void
470 {
471 $this->expectException(\RuntimeException::class);
472
473 /** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
474 $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
475
476 $file = '../typo3temp/var/tests/' . $this->getUniqueId('test_');
477 $absoluteFile = Environment::getLegacyConfigPath() . '/' . $file;
478 touch($absoluteFile);
479 $this->testFilesToDelete[] = $absoluteFile;
480 $subject->_set('localConfigurationFile', $file);
481
482 $subject->createLocalConfigurationFromFactoryConfiguration();
483 }
484
485 /**
486 * @test
487 */
488 public function createLocalConfigurationFromFactoryConfigurationWritesContentFromFactoryFile(): void
489 {
490 /** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
491 $subject = $this->getAccessibleMock(ConfigurationManager::class, ['writeLocalConfiguration']);
492 $subject->_set('localConfigurationFile', '../../typo3temp/var/tests/' . $this->getUniqueId('dummy_'));
493
494 $factoryConfigurationFile = '../../typo3temp/var/tests/' . $this->getUniqueId('test_') . '.php';
495 $factoryConfigurationAbsoluteFile = Environment::getFrameworkBasePath() . '/' . $factoryConfigurationFile;
496 $uniqueContentString = $this->getUniqueId('string_');
497 $validFactoryConfigurationFileContent =
498 '<?php' . LF .
499 'return [' . LF .
500 '\'' . $uniqueContentString . '\' => \'foo\',' . LF .
501 '];' . LF;
502 file_put_contents(
503 $factoryConfigurationAbsoluteFile,
504 $validFactoryConfigurationFileContent
505 );
506 $this->testFilesToDelete[] = $factoryConfigurationAbsoluteFile;
507
508 $subject->_set('factoryConfigurationFile', $factoryConfigurationFile);
509
510 $subject
511 ->expects($this->once())
512 ->method('writeLocalConfiguration')
513 ->with($this->arrayHasKey($uniqueContentString));
514 $subject->createLocalConfigurationFromFactoryConfiguration();
515 }
516
517 /**
518 * @test
519 */
520 public function createLocalConfigurationFromFactoryConfigurationMergesConfigurationWithAdditionalFactoryFile(): void
521 {
522 /** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
523 $subject = $this->getAccessibleMock(ConfigurationManager::class, ['writeLocalConfiguration']);
524 $subject->_set('localConfigurationFile', '../../typo3temp/var/tests/' . $this->getUniqueId('dummy_'));
525
526 $factoryConfigurationFile = '../../typo3temp/var/tests/' . $this->getUniqueId('test_') . '.php';
527 $factoryConfigurationAbsoluteFile = Environment::getFrameworkBasePath() . '/' . $factoryConfigurationFile;
528 $validFactoryConfigurationFileContent =
529 '<?php' . LF .
530 'return [];' . LF;
531 file_put_contents(
532 $factoryConfigurationAbsoluteFile,
533 $validFactoryConfigurationFileContent
534 );
535 $this->testFilesToDelete[] = $factoryConfigurationAbsoluteFile;
536 $subject->_set('factoryConfigurationFile', $factoryConfigurationFile);
537
538 $additionalFactoryConfigurationFile = 'typo3temp/var/tests/' . $this->getUniqueId('test_') . '.php';
539 $additionalFactoryConfigurationAbsoluteFile = Environment::getPublicPath() . '/' . $additionalFactoryConfigurationFile;
540 $uniqueContentString = $this->getUniqueId('string_');
541 $validAdditionalFactoryConfigurationFileContent =
542 '<?php' . LF .
543 'return [' . LF .
544 '\'' . $uniqueContentString . '\' => \'foo\',' . LF .
545 '];' . LF;
546 file_put_contents(
547 $additionalFactoryConfigurationAbsoluteFile,
548 $validAdditionalFactoryConfigurationFileContent
549 );
550 $this->testFilesToDelete[] = $additionalFactoryConfigurationAbsoluteFile;
551 $subject->_set('additionalFactoryConfigurationFile', $additionalFactoryConfigurationFile);
552
553 $subject
554 ->expects($this->once())
555 ->method('writeLocalConfiguration')
556 ->with($this->arrayHasKey($uniqueContentString));
557 $subject->createLocalConfigurationFromFactoryConfiguration();
558 }
559
560 /**
561 * @test
562 */
563 public function isValidLocalConfigurationPathAcceptsWhitelistedPath(): void
564 {
565 /** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
566 $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
567 $subject->_set('whiteListedLocalConfigurationPaths', ['foo/bar']);
568 $this->assertTrue($subject->_call('isValidLocalConfigurationPath', 'foo/bar/baz'));
569 }
570
571 /**
572 * @test
573 */
574 public function isValidLocalConfigurationPathDeniesNotWhitelistedPath(): void
575 {
576 /** @var $subject ConfigurationManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
577 $subject = $this->getAccessibleMock(ConfigurationManager::class, ['dummy']);
578 $subject->_set('whiteListedLocalConfigurationPaths', ['foo/bar']);
579 $this->assertFalse($subject->_call('isValidLocalConfigurationPath', 'bar/baz'));
580 }
581 }