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