[BUGFIX] FollowUp to #56660
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Package / PackageManagerTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Package;
3
4 /* *
5 * This script belongs to the TYPO3 Flow framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 use TYPO3\Flow\Package\PackageInterface;
15 use org\bovigo\vfs\vfsStream;
16
17 /**
18 * Testcase for the default package manager
19 *
20 */
21 class PackageManagerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
22
23 /**
24 * @var \TYPO3\Flow\Package\PackageManager
25 */
26 protected $packageManager;
27
28 /**
29 * Sets up this test case
30 *
31 */
32 protected function setUp() {
33 vfsStream::setup('Test');
34 $mockBootstrap = $this->getMock('TYPO3\CMS\Core\Core\Bootstrap', array(), array(), '', FALSE);
35 $mockCache = $this->getMock('TYPO3\CMS\Core\Cache\Frontend\PhpFrontend', array('has', 'set', 'getBackend'), array(), '', FALSE);
36 $mockCacheBackend = $this->getMock('TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend', array('has', 'set', 'getBackend'), array(), '', FALSE);
37 $mockCache->expects($this->any())->method('has')->will($this->returnValue(FALSE));
38 $mockCache->expects($this->any())->method('set')->will($this->returnValue(TRUE));
39 $mockCache->expects($this->any())->method('getBackend')->will($this->returnValue($mockCacheBackend));
40 $mockCacheBackend->expects($this->any())->method('getCacheDirectory')->will($this->returnValue('vfs://Test/Cache'));
41 $this->packageManager = $this->getAccessibleMock('TYPO3\\CMS\\Core\\Package\\PackageManager', array('sortAndSavePackageStates'));
42
43 mkdir('vfs://Test/Packages/Application', 0700, TRUE);
44 mkdir('vfs://Test/Configuration');
45 file_put_contents('vfs://Test/Configuration/PackageStates.php', "<?php return array ('packages' => array(), 'version' => 4); ");
46
47 $mockClassLoader = $this->getMock('TYPO3\CMS\Core\Core\ClassLoader', array(), array(\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->getApplicationContext()));
48 $mockClassLoader->expects($this->any())->method('setCacheIdentifier')->will($this->returnSelf());
49
50 $composerNameToPackageKeyMap = array(
51 'typo3/flow' => 'TYPO3.Flow'
52 );
53
54 $this->packageManager->injectClassLoader($mockClassLoader);
55 $this->packageManager->injectCoreCache($mockCache);
56 $this->inject($this->packageManager, 'composerNameToPackageKeyMap', $composerNameToPackageKeyMap);
57 $this->packageManager->initialize($mockBootstrap);
58 $this->packageManager->_set('packagesBasePath', 'vfs://Test/Packages/');
59 $this->packageManager->_set('packageStatesPathAndFilename', 'vfs://Test/Configuration/PackageStates.php');
60 }
61
62 /**
63 * @test
64 */
65 public function getPackageReturnsTheSpecifiedPackage() {
66 $this->packageManager->createPackage('TYPO3.Flow');
67
68 $package = $this->packageManager->getPackage('TYPO3.Flow');
69 $this->assertInstanceOf('TYPO3\Flow\Package\PackageInterface', $package, 'The result of getPackage() was no valid package object.');
70 }
71
72 /**
73 * @test
74 * @expectedException \TYPO3\Flow\Package\Exception\UnknownPackageException
75 */
76 public function getPackageThrowsExceptionOnUnknownPackage() {
77 $this->packageManager->getPackage('PrettyUnlikelyThatThisPackageExists');
78 }
79
80
81 /**
82 * @test
83 */
84 public function getDependencyArrayForPackageReturnsCorrectResult() {
85 $mockFlowMetadata = $this->getMock('TYPO3\Flow\Package\MetaDataInterface');
86 $mockFlowMetadata->expects($this->any())->method('getConstraintsByType')->will($this->returnValue(array(
87 new \TYPO3\Flow\Package\MetaData\PackageConstraint('depends', 'TYPO3.Fluid'),
88 new \TYPO3\Flow\Package\MetaData\PackageConstraint('depends', 'Doctrine.ORM')
89 )));
90 $mockFlowPackage = $this->getMock('TYPO3\Flow\Package\PackageInterface');
91 $mockFlowPackage->expects($this->any())->method('getPackageMetaData')->will($this->returnValue($mockFlowMetadata));
92
93 $mockFluidMetadata = $this->getMock('TYPO3\Flow\Package\MetaDataInterface');
94 $mockFluidMetadata->expects($this->any())->method('getConstraintsByType')->will($this->returnValue(array(
95 new \TYPO3\Flow\Package\MetaData\PackageConstraint('depends', 'TYPO3.Flow')
96 )));
97 $mockFluidPackage = $this->getMock('TYPO3\Flow\Package\PackageInterface');
98 $mockFluidPackage->expects($this->any())->method('getPackageMetaData')->will($this->returnValue($mockFluidMetadata));
99
100 $mockOrmMetadata = $this->getMock('TYPO3\Flow\Package\MetaDataInterface');
101 $mockOrmMetadata->expects($this->any())->method('getConstraintsByType')->will($this->returnValue(array(
102 new \TYPO3\Flow\Package\MetaData\PackageConstraint('depends', 'Doctrine.DBAL')
103 )));
104 $mockOrmPackage = $this->getMock('TYPO3\Flow\Package\PackageInterface');
105 $mockOrmPackage->expects($this->any())->method('getPackageMetaData')->will($this->returnValue($mockOrmMetadata));
106
107 $mockDbalMetadata = $this->getMock('TYPO3\Flow\Package\MetaDataInterface');
108 $mockDbalMetadata->expects($this->any())->method('getConstraintsByType')->will($this->returnValue(array(
109 new \TYPO3\Flow\Package\MetaData\PackageConstraint('depends', 'Doctrine.Common')
110 )));
111 $mockDbalPackage = $this->getMock('TYPO3\Flow\Package\PackageInterface');
112 $mockDbalPackage->expects($this->any())->method('getPackageMetaData')->will($this->returnValue($mockDbalMetadata));
113
114 $mockCommonMetadata = $this->getMock('TYPO3\Flow\Package\MetaDataInterface');
115 $mockCommonMetadata->expects($this->any())->method('getConstraintsByType')->will($this->returnValue(array()));
116 $mockCommonPackage = $this->getMock('TYPO3\Flow\Package\PackageInterface');
117 $mockCommonPackage->expects($this->any())->method('getPackageMetaData')->will($this->returnValue($mockCommonMetadata));
118
119 $packages = array(
120 'TYPO3.Flow' => $mockFlowPackage,
121 'TYPO3.Fluid' => $mockFluidPackage,
122 'Doctrine.ORM' => $mockOrmPackage,
123 'Doctrine.DBAL' => $mockDbalPackage,
124 'Doctrine.Common' => $mockCommonPackage
125 );
126
127 $packageManager = $this->getAccessibleMock('\TYPO3\Flow\Package\PackageManager', array('dummy'));
128 $packageManager->_set('packages', $packages);
129 $dependencyArray = $packageManager->_call('getDependencyArrayForPackage', 'TYPO3.Flow');
130
131 $this->assertEquals(array('Doctrine.Common', 'Doctrine.DBAL', 'Doctrine.ORM', 'TYPO3.Fluid'), $dependencyArray);
132 }
133
134 /**
135 * @test
136 */
137 public function getCaseSensitivePackageKeyReturnsTheUpperCamelCaseVersionOfAGivenPackageKeyIfThePackageIsRegistered() {
138 $packageManager = $this->getAccessibleMock('TYPO3\Flow\Package\PackageManager', array('dummy'));
139 $packageManager->_set('packageKeys', array('acme.testpackage' => 'Acme.TestPackage'));
140 $this->assertEquals('Acme.TestPackage', $packageManager->getCaseSensitivePackageKey('acme.testpackage'));
141 }
142
143 /**
144 * @test
145 */
146 public function scanAvailablePackagesTraversesThePackagesDirectoryAndRegistersPackagesItFinds() {
147 $expectedPackageKeys = array(
148 'TYPO3.Flow' . md5(uniqid(mt_rand(), TRUE)),
149 'TYPO3.Flow.Test' . md5(uniqid(mt_rand(), TRUE)),
150 'TYPO3.YetAnotherTestPackage' . md5(uniqid(mt_rand(), TRUE)),
151 'RobertLemke.Flow.NothingElse' . md5(uniqid(mt_rand(), TRUE))
152 );
153
154 foreach ($expectedPackageKeys as $packageKey) {
155 $packagePath = 'vfs://Test/Packages/Application/' . $packageKey . '/';
156
157 mkdir($packagePath, 0770, TRUE);
158 mkdir($packagePath . 'Classes');
159 file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "flow-test"}');
160 }
161
162 $packageManager = $this->getAccessibleMock('TYPO3\Flow\Package\PackageManager', array('dummy'));
163 $packageManager->_set('packagesBasePath', 'vfs://Test/Packages/');
164 $packageManager->_set('packageStatesPathAndFilename', 'vfs://Test/Configuration/PackageStates.php');
165
166 $packageFactory = new \TYPO3\Flow\Package\PackageFactory($packageManager);
167 $this->inject($packageManager, 'packageFactory', $packageFactory);
168
169 $packageManager->_set('packages', array());
170 $packageManager->_call('scanAvailablePackages');
171
172 $packageStates = require('vfs://Test/Configuration/PackageStates.php');
173 $actualPackageKeys = array_keys($packageStates['packages']);
174 $this->assertEquals(sort($expectedPackageKeys), sort($actualPackageKeys));
175 }
176
177 /**
178 * @test
179 */
180 public function scanAvailablePackagesKeepsExistingPackageConfiguration() {
181 $expectedPackageKeys = array(
182 'TYPO3.Flow' . md5(uniqid(mt_rand(), TRUE)),
183 'TYPO3.Flow.Test' . md5(uniqid(mt_rand(), TRUE)),
184 'TYPO3.YetAnotherTestPackage' . md5(uniqid(mt_rand(), TRUE)),
185 'RobertLemke.Flow.NothingElse' . md5(uniqid(mt_rand(), TRUE))
186 );
187
188 foreach ($expectedPackageKeys as $packageKey) {
189 $packagePath = 'vfs://Test/Packages/Application/' . $packageKey . '/';
190
191 mkdir($packagePath, 0770, TRUE);
192 mkdir($packagePath . 'Classes');
193 file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "flow-test"}');
194 }
195
196 $packageManager = $this->getAccessibleMock('TYPO3\Flow\Package\PackageManager', array('dummy'));
197 $packageManager->_set('packagesBasePath', 'vfs://Test/Packages/');
198 $packageManager->_set('packageStatesPathAndFilename', 'vfs://Test/Configuration/PackageStates.php');
199
200 $packageFactory = new \TYPO3\Flow\Package\PackageFactory($packageManager);
201 $this->inject($packageManager, 'packageFactory', $packageFactory);
202
203 $packageManager->_set('packageStatesConfiguration', array(
204 'packages' => array(
205 $packageKey => array(
206 'state' => 'inactive',
207 'frozen' => FALSE,
208 'packagePath' => 'Application/' . $packageKey . '/',
209 'classesPath' => 'Classes/'
210 )
211 ),
212 'version' => 2
213 ));
214 $packageManager->_call('scanAvailablePackages');
215 $packageManager->_call('sortAndsavePackageStates');
216
217 $packageStates = require('vfs://Test/Configuration/PackageStates.php');
218 $this->assertEquals('inactive', $packageStates['packages'][$packageKey]['state']);
219 }
220
221
222 /**
223 * @test
224 */
225 public function packageStatesConfigurationContainsRelativePaths() {
226 $packageKeys = array(
227 'RobertLemke.Flow.NothingElse' . md5(uniqid(mt_rand(), TRUE)),
228 'TYPO3.Flow' . md5(uniqid(mt_rand(), TRUE)),
229 'TYPO3.YetAnotherTestPackage' . md5(uniqid(mt_rand(), TRUE)),
230 );
231
232 foreach ($packageKeys as $packageKey) {
233 $packagePath = 'vfs://Test/Packages/Application/' . $packageKey . '/';
234
235 mkdir($packagePath, 0770, TRUE);
236 mkdir($packagePath . 'Classes');
237 file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "flow-test"}');
238 }
239
240 $packageManager = $this->getAccessibleMock('TYPO3\Flow\Package\PackageManager', array('updateShortcuts'), array(), '', FALSE);
241 $packageManager->_set('packagesBasePath', 'vfs://Test/Packages/');
242 $packageManager->_set('packageStatesPathAndFilename', 'vfs://Test/Configuration/PackageStates.php');
243
244 $packageFactory = new \TYPO3\Flow\Package\PackageFactory($packageManager);
245 $this->inject($packageManager, 'packageFactory', $packageFactory);
246
247 $packageManager->_set('packages', array());
248 $packageManager->_call('scanAvailablePackages');
249
250 $expectedPackageStatesConfiguration = array();
251 foreach ($packageKeys as $packageKey) {
252 $expectedPackageStatesConfiguration[$packageKey] = array(
253 'state' => 'active',
254 'packagePath' => 'Application/' . $packageKey . '/',
255 'classesPath' => 'Classes/',
256 'manifestPath' => '',
257 'composerName' => $packageKey
258 );
259 }
260
261 $actualPackageStatesConfiguration = $packageManager->_get('packageStatesConfiguration');
262 $this->assertEquals($expectedPackageStatesConfiguration, $actualPackageStatesConfiguration['packages']);
263 }
264
265 /**
266 * Data Provider returning valid package keys and the corresponding path
267 *
268 * @return array
269 */
270 public function packageKeysAndPaths() {
271 return array(
272 array('TYPO3.YetAnotherTestPackage', 'vfs://Test/Packages/Application/TYPO3.YetAnotherTestPackage/'),
273 array('RobertLemke.Flow.NothingElse', 'vfs://Test/Packages/Application/RobertLemke.Flow.NothingElse/')
274 );
275 }
276
277 /**
278 * @test
279 * @dataProvider packageKeysAndPaths
280 */
281 public function createPackageCreatesPackageFolderAndReturnsPackage($packageKey, $expectedPackagePath) {
282 $actualPackage = $this->packageManager->createPackage($packageKey);
283 $actualPackagePath = $actualPackage->getPackagePath();
284
285 $this->assertEquals($expectedPackagePath, $actualPackagePath);
286 $this->assertTrue(is_dir($actualPackagePath), 'Package path should exist after createPackage()');
287 $this->assertEquals($packageKey, $actualPackage->getPackageKey());
288 $this->assertTrue($this->packageManager->isPackageAvailable($packageKey));
289 }
290
291 /**
292 * @test
293 */
294 public function createPackageWritesAComposerManifestUsingTheGivenMetaObject() {
295 $metaData = new \TYPO3\Flow\Package\MetaData('Acme.YetAnotherTestPackage');
296 $metaData->setDescription('Yet Another Test Package');
297
298 $package = $this->packageManager->createPackage('Acme.YetAnotherTestPackage', $metaData);
299
300 $json = file_get_contents($package->getPackagePath() . '/composer.json');
301 $composerManifest = json_decode($json);
302
303 $this->assertEquals('acme/yetanothertestpackage', $composerManifest->name);
304 $this->assertEquals('Yet Another Test Package', $composerManifest->description);
305 }
306
307 /**
308 * @test
309 */
310 public function createPackageCanChangePackageTypeInComposerManifest() {
311 $metaData = new \TYPO3\Flow\Package\MetaData('Acme.YetAnotherTestPackage2');
312 $metaData->setDescription('Yet Another Test Package');
313 $metaData->setPackageType('flow-custom-package');
314
315 $package = $this->packageManager->createPackage('Acme.YetAnotherTestPackage2', $metaData);
316
317 $json = file_get_contents($package->getPackagePath() . '/composer.json');
318 $composerManifest = json_decode($json);
319
320 $this->assertEquals('flow-custom-package', $composerManifest->type);
321 }
322
323 /**
324 * Checks if createPackage() creates the folders for classes, configuration, documentation, resources and tests.
325 *
326 * @test
327 */
328 public function createPackageCreatesCommonFolders() {
329 $package = $this->packageManager->createPackage('Acme.YetAnotherTestPackage');
330 $packagePath = $package->getPackagePath();
331
332 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_CLASSES), "Classes directory was not created");
333 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_CONFIGURATION), "Configuration directory was not created");
334 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_DOCUMENTATION), "Documentation directory was not created");
335 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_RESOURCES), "Resources directory was not created");
336 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_TESTS_UNIT), "Tests/Unit directory was not created");
337 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_TESTS_FUNCTIONAL), "Tests/Functional directory was not created");
338 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_METADATA), "Metadata directory was not created");
339 }
340
341 /**
342 * Makes sure that an exception is thrown and no directory is created on passing invalid package keys.
343 *
344 * @test
345 */
346 public function createPackageThrowsExceptionOnInvalidPackageKey() {
347 try {
348 $this->packageManager->createPackage('Invalid_PackageKey');
349 } catch (\TYPO3\Flow\Package\Exception\InvalidPackageKeyException $exception) {
350 }
351 $this->assertFalse(is_dir('vfs://Test/Packages/Application/Invalid_PackageKey'), 'Package folder with invalid package key was created');
352 }
353
354 /**
355 * Makes sure that duplicate package keys are detected.
356 *
357 * @test
358 * @expectedException \TYPO3\Flow\Package\Exception\PackageKeyAlreadyExistsException
359 */
360 public function createPackageThrowsExceptionForExistingPackageKey() {
361 $this->packageManager->createPackage('Acme.YetAnotherTestPackage');
362 $this->packageManager->createPackage('Acme.YetAnotherTestPackage');
363 }
364
365 /**
366 * @test
367 */
368 public function createPackageActivatesTheNewlyCreatedPackage() {
369 $this->packageManager->createPackage('Acme.YetAnotherTestPackage');
370 $this->assertTrue($this->packageManager->isPackageActive('Acme.YetAnotherTestPackage'));
371 }
372
373 /**
374 * @test
375 */
376 public function activatePackageAndDeactivatePackageActivateAndDeactivateTheGivenPackage() {
377 $packageKey = 'Acme.YetAnotherTestPackage';
378
379 $this->packageManager->createPackage($packageKey);
380
381 $this->packageManager->deactivatePackage($packageKey);
382 $this->assertFalse($this->packageManager->isPackageActive($packageKey));
383
384 $this->packageManager->activatePackage($packageKey);
385 $this->assertTrue($this->packageManager->isPackageActive($packageKey));
386 }
387
388 /**
389 * @test
390 * @expectedException \TYPO3\Flow\Package\Exception\ProtectedPackageKeyException
391 */
392 public function deactivatePackageThrowsAnExceptionIfPackageIsProtected() {
393 $package = $this->packageManager->createPackage('Acme.YetAnotherTestPackage');
394 $package->setProtected(TRUE);
395 $this->packageManager->deactivatePackage('Acme.YetAnotherTestPackage');
396 }
397
398 /**
399 * @test
400 * @expectedException \TYPO3\Flow\Package\Exception\UnknownPackageException
401 */
402 public function deletePackageThrowsErrorIfPackageIsNotAvailable() {
403 $this->packageManager->deletePackage('PrettyUnlikelyThatThisPackageExists');
404 }
405
406 /**
407 * @test
408 * @expectedException \TYPO3\Flow\Package\Exception\ProtectedPackageKeyException
409 */
410 public function deletePackageThrowsAnExceptionIfPackageIsProtected() {
411 $package = $this->packageManager->createPackage('Acme.YetAnotherTestPackage');
412 $package->setProtected(TRUE);
413 $this->packageManager->deletePackage('Acme.YetAnotherTestPackage');
414 }
415
416 /**
417 * @test
418 */
419 public function deletePackageRemovesPackageFromAvailableAndActivePackagesAndDeletesThePackageDirectory() {
420 $package = $this->packageManager->createPackage('Acme.YetAnotherTestPackage');
421 $packagePath = $package->getPackagePath();
422
423 $this->assertTrue(is_dir($packagePath . PackageInterface::DIRECTORY_METADATA));
424 $this->assertTrue($this->packageManager->isPackageActive('Acme.YetAnotherTestPackage'));
425 $this->assertTrue($this->packageManager->isPackageAvailable('Acme.YetAnotherTestPackage'));
426
427 $this->packageManager->deletePackage('Acme.YetAnotherTestPackage');
428
429 $this->assertFalse(is_dir($packagePath . PackageInterface::DIRECTORY_METADATA));
430 $this->assertFalse($this->packageManager->isPackageActive('Acme.YetAnotherTestPackage'));
431 $this->assertFalse($this->packageManager->isPackageAvailable('Acme.YetAnotherTestPackage'));
432 }
433
434 /**
435 * @return array
436 */
437 public function composerNamesAndPackageKeys() {
438 return array(
439 array('imagine/Imagine', 'imagine.Imagine'),
440 array('imagine/imagine', 'imagine.Imagine'),
441 array('typo3/flow', 'TYPO3.Flow'),
442 array('TYPO3/Flow', 'TYPO3.Flow')
443 );
444 }
445
446 /**
447 * @test
448 * @dataProvider composerNamesAndPackageKeys
449 */
450 public function getPackageKeyFromComposerNameIgnoresCaseDifferences($composerName, $packageKey) {
451 $packageStatesConfiguration = array('packages' =>
452 array(
453 'TYPO3.Flow' => array(
454 'composerName' => 'typo3/flow'
455 ),
456 'imagine.Imagine' => array(
457 'composerName' => 'imagine/Imagine'
458 )
459 )
460 );
461
462 $packageManager = $this->getAccessibleMock('\TYPO3\Flow\Package\PackageManager', array('resolvePackageDependencies'));
463 $packageManager->_set('packageStatesConfiguration', $packageStatesConfiguration);
464
465 $this->assertEquals($packageKey, $packageManager->_call('getPackageKeyFromComposerName', $composerName));
466 }
467 }