3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * Class CRM_Extension_ManagerTest
32 class CRM_Extension_ManagerTest
extends CiviUnitTestCase
{
33 const TESTING_TYPE
= 'report';
34 const OTHER_TESTING_TYPE
= 'module';
36 public function setUp() {
38 list ($this->basedir
, $this->container
) = $this->_createContainer();
39 $this->mapper
= new CRM_Extension_Mapper($this->container
);
42 public function tearDown() {
47 * Install an extension with an invalid type name.
49 * @expectedException CRM_Extension_Exception
51 public function testInstallInvalidType() {
52 $mockFunction = $this->mockMethod
;
53 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
54 $testingTypeManager->expects($this->never())
55 ->method('onPreInstall');
56 $manager = $this->_createManager(array(
57 self
::OTHER_TESTING_TYPE
=> $testingTypeManager,
59 $manager->install(array('test.foo.bar'));
63 * Install an extension with a valid type name.
65 * Note: We initially install two extensions but then toggle only
66 * the second. This controls for bad SQL queries which hit either
67 * "the first row" or "all rows".
69 public function testInstall_Disable_Uninstall() {
70 $mockFunction = $this->mockMethod
;
71 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
72 $manager = $this->_createManager(array(
73 self
::TESTING_TYPE
=> $testingTypeManager,
75 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
76 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
79 ->expects($this->exactly(2))
80 ->method('onPreInstall');
82 ->expects($this->exactly(2))
83 ->method('onPostInstall');
84 $manager->install(array('test.whiz.bang', 'test.foo.bar'));
85 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
86 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
89 ->expects($this->once())
90 ->method('onPreDisable');
92 ->expects($this->once())
93 ->method('onPostDisable');
94 $manager->disable(array('test.foo.bar'));
95 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
96 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang')); // no side-effect
99 ->expects($this->once())
100 ->method('onPreUninstall');
102 ->expects($this->once())
103 ->method('onPostUninstall');
104 $manager->uninstall(array('test.foo.bar'));
105 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
106 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang')); // no side-effect
110 * This is the same as testInstall_Disable_Uninstall, but we also install and remove a dependency.
112 * @throws \CRM_Extension_Exception
114 public function test_InstallAuto_DisableDownstream_UninstallDownstream() {
115 $mockFunction = $this->mockMethod
;
116 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
117 $manager = $this->_createManager(array(
118 self
::TESTING_TYPE
=> $testingTypeManager,
120 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
121 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
122 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
124 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
125 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
126 $this->assertEquals(array('test.foo.bar', 'test.foo.downstream'),
127 $manager->findInstallRequirements(array('test.foo.downstream')));
129 $manager->findInstallRequirements(array('test.foo.downstream')));
130 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
131 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
132 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
134 $testingTypeManager->expects($this->once())->method('onPreDisable');
135 $testingTypeManager->expects($this->once())->method('onPostDisable');
136 $this->assertEquals(array('test.foo.downstream'),
137 $manager->findDisableRequirements(array('test.foo.downstream')));
138 $manager->disable(array('test.foo.downstream'));
139 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
140 $this->assertEquals('disabled', $manager->getStatus('test.foo.downstream'));
141 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
143 $testingTypeManager->expects($this->once())->method('onPreUninstall');
144 $testingTypeManager->expects($this->once())->method('onPostUninstall');
145 $manager->uninstall(array('test.foo.downstream'));
146 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
147 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
148 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
152 * This is the same as testInstallAuto_Twice
154 * @throws \CRM_Extension_Exception
156 public function testInstallAuto_Twice() {
157 $mockFunction = $this->mockMethod
;
158 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
159 $manager = $this->_createManager(array(
160 self
::TESTING_TYPE
=> $testingTypeManager,
162 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
163 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
164 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
166 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
167 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
168 $this->assertEquals(array('test.foo.bar', 'test.foo.downstream'),
169 $manager->findInstallRequirements(array('test.foo.downstream')));
171 $manager->findInstallRequirements(array('test.foo.downstream')));
172 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
173 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
174 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
176 // And install a second time...
177 $testingTypeManager->expects($this->exactly(0))->method('onPreInstall');
178 $testingTypeManager->expects($this->exactly(0))->method('onPostInstall');
180 $manager->findInstallRequirements(array('test.foo.downstream')));
181 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
182 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
183 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
186 public function test_InstallAuto_DisableUpstream() {
187 $mockFunction = $this->mockMethod
;
188 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
189 $manager = $this->_createManager(array(
190 self
::TESTING_TYPE
=> $testingTypeManager,
192 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
193 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
194 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
196 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
197 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
198 $this->assertEquals(array('test.foo.bar', 'test.foo.downstream'),
199 $manager->findInstallRequirements(array('test.foo.downstream')));
201 $manager->findInstallRequirements(array('test.foo.downstream')));
202 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
203 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
204 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
206 $testingTypeManager->expects($this->never())->method('onPreDisable');
207 $testingTypeManager->expects($this->never())->method('onPostDisable');
208 $this->assertEquals(array('test.foo.downstream', 'test.foo.bar'),
209 $manager->findDisableRequirements(array('test.foo.bar')));
212 $manager->disable(array('test.foo.bar'));
213 $this->fail('Expected disable to fail due to dependency');
215 catch (CRM_Extension_Exception
$e) {
216 $this->assertRegExp('/test.foo.downstream/', $e->getMessage());
220 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
221 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
222 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
227 * Install an extension and then harshly remove the underlying source.
228 * Subseuently disable and uninstall.
230 public function testInstall_DirtyRemove_Disable_Uninstall() {
231 $mockFunction = $this->mockMethod
;
232 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
233 $manager = $this->_createManager(array(
234 self
::TESTING_TYPE
=> $testingTypeManager,
236 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
238 $manager->install(array('test.foo.bar'));
239 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
241 $this->assertTrue(file_exists("{$this->basedir}/weird/foobar/info.xml"));
242 CRM_Utils_File
::cleanDir("{$this->basedir}/weird/foobar", TRUE, FALSE);
243 $this->assertFalse(file_exists("{$this->basedir}/weird/foobar/info.xml"));
245 $this->assertEquals('installed-missing', $manager->getStatus('test.foo.bar'));
248 ->expects($this->once())
249 ->method('onPreDisable');
251 ->expects($this->once())
252 ->method('onPostDisable');
253 $manager->disable(array('test.foo.bar'));
254 $this->assertEquals('disabled-missing', $manager->getStatus('test.foo.bar'));
257 ->expects($this->once())
258 ->method('onPreUninstall');
260 ->expects($this->once())
261 ->method('onPostUninstall');
262 $manager->uninstall(array('test.foo.bar'));
263 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar'));
267 * Install an extension with a valid type name.
269 public function testInstall_Disable_Enable() {
270 $mockFunction = $this->mockMethod
;
271 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
272 $manager = $this->_createManager(array(
273 self
::TESTING_TYPE
=> $testingTypeManager,
275 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
276 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
279 ->expects($this->exactly(2))
280 ->method('onPreInstall');
282 ->expects($this->exactly(2))
283 ->method('onPostInstall');
284 $manager->install(array('test.whiz.bang', 'test.foo.bar'));
285 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
286 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
289 ->expects($this->once())
290 ->method('onPreDisable');
292 ->expects($this->once())
293 ->method('onPostDisable');
294 $manager->disable(array('test.foo.bar'));
295 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
296 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
299 ->expects($this->once())
300 ->method('onPreEnable');
302 ->expects($this->once())
303 ->method('onPostEnable');
304 $manager->enable(array('test.foo.bar'));
305 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
306 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
310 * Performing 'install' on a 'disabled' extension performs an 'enable'
312 public function testInstall_Disable_Install() {
313 $mockFunction = $this->mockMethod
;
314 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
315 $manager = $this->_createManager(array(
316 self
::TESTING_TYPE
=> $testingTypeManager,
318 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
321 ->expects($this->once())
322 ->method('onPreInstall');
324 ->expects($this->once())
325 ->method('onPostInstall');
326 $manager->install(array('test.foo.bar'));
327 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
330 ->expects($this->once())
331 ->method('onPreDisable');
333 ->expects($this->once())
334 ->method('onPostDisable');
335 $manager->disable(array('test.foo.bar'));
336 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
339 ->expects($this->once())
340 ->method('onPreEnable');
342 ->expects($this->once())
343 ->method('onPostEnable');
344 $manager->install(array('test.foo.bar')); // install() instead of enable()
345 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
349 * Install an extension with a valid type name.
351 public function testEnableBare() {
352 $mockFunction = $this->mockMethod
;
353 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
354 $manager = $this->_createManager(array(
355 self
::TESTING_TYPE
=> $testingTypeManager,
357 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
360 ->expects($this->once())
361 ->method('onPreInstall');
363 ->expects($this->once())
364 ->method('onPostInstall');
366 ->expects($this->never())
367 ->method('onPreEnable');
369 ->expects($this->never())
370 ->method('onPostEnable');
371 $manager->enable(array('test.foo.bar')); // enable not install
372 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
376 * Get the status of an unknown extension.
378 public function testStatusUnknownKey() {
379 $mockFunction = $this->mockMethod
;
380 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
381 $testingTypeManager->expects($this->never())
382 ->method('onPreInstall');
383 $manager = $this->_createManager(array(
384 self
::TESTING_TYPE
=> $testingTypeManager,
386 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar.whiz.bang'));
390 * Replace code for an extension that doesn't exist in the container
392 public function testReplace_Unknown() {
393 $mockFunction = $this->mockMethod
;
394 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
395 $manager = $this->_createManager(array(
396 self
::TESTING_TYPE
=> $testingTypeManager,
398 $this->assertEquals('unknown', $manager->getStatus('test.newextension'));
400 $this->download
= $this->_createDownload('test.newextension', 'newextension');
403 ->expects($this->never())// no data to replace
404 ->method('onPreReplace');
406 ->expects($this->never())// no data to replace
407 ->method('onPostReplace');
408 $manager->replace($this->download
);
409 $this->assertEquals('uninstalled', $manager->getStatus('test.newextension'));
410 $this->assertTrue(file_exists("{$this->basedir}/test.newextension/info.xml"));
411 $this->assertTrue(file_exists("{$this->basedir}/test.newextension/newextension.php"));
412 $this->assertEquals(self
::TESTING_TYPE
, $this->mapper
->keyToInfo('test.newextension')->type
);
413 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.newextension')->file
);
417 * Replace code for an extension that doesn't exist in the container
419 public function testReplace_Uninstalled() {
420 $mockFunction = $this->mockMethod
;
421 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
422 $manager = $this->_createManager(array(
423 self
::TESTING_TYPE
=> $testingTypeManager,
425 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
426 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
428 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
431 ->expects($this->never())// no data to replace
432 ->method('onPreReplace');
434 ->expects($this->never())// no data to replace
435 ->method('onPostReplace');
436 $manager->replace($this->download
);
437 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
438 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
439 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/newextension.php"));
440 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/oddball.php"));
441 $this->assertEquals(self
::TESTING_TYPE
, $this->mapper
->keyToInfo('test.whiz.bang')->type
);
442 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
446 * Install a module and then replace it with new code.
448 * Note that some metadata changes between versions -- the original has
449 * file="oddball", and the upgrade has file="newextension".
451 public function testReplace_Installed() {
452 $mockFunction = $this->mockMethod
;
453 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
454 $manager = $this->_createManager(array(
455 self
::TESTING_TYPE
=> $testingTypeManager,
457 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
458 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
460 $manager->install(array('test.whiz.bang'));
461 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
462 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
463 $this->assertDBQuery('oddball', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
465 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
468 ->expects($this->once())
469 ->method('onPreReplace');
471 ->expects($this->once())
472 ->method('onPostReplace');
473 $manager->replace($this->download
);
474 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
475 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
476 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/newextension.php"));
477 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/oddball.php"));
478 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
479 $this->assertDBQuery('newextension', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
483 * Install a module and then delete (leaving stale DB info); restore
484 * the module by downloading new code.
486 * Note that some metadata changes between versions -- the original has
487 * file="oddball", and the upgrade has file="newextension".
489 public function testReplace_InstalledMissing() {
490 $mockFunction = $this->mockMethod
;
491 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
492 $manager = $this->_createManager(array(
493 self
::TESTING_TYPE
=> $testingTypeManager,
496 // initial installation
497 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
498 $manager->install(array('test.whiz.bang'));
499 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
502 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
503 CRM_Utils_File
::cleanDir("{$this->basedir}/weird/whizbang", TRUE, FALSE);
504 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
506 $this->assertEquals('installed-missing', $manager->getStatus('test.whiz.bang'));
508 // download and reinstall
509 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
512 ->expects($this->once())
513 ->method('onPreReplace');
515 ->expects($this->once())
516 ->method('onPostReplace');
517 $manager->replace($this->download
);
518 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
519 $this->assertTrue(file_exists("{$this->basedir}/test.whiz.bang/info.xml"));
520 $this->assertTrue(file_exists("{$this->basedir}/test.whiz.bang/newextension.php"));
521 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
522 $this->assertDBQuery('newextension', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
526 * @param $typeManagers
528 * @return CRM_Extension_Manager
530 public function _createManager($typeManagers) {
531 //list ($basedir, $c) = $this->_createContainer();
532 $mapper = new CRM_Extension_Mapper($this->container
);
533 return new CRM_Extension_Manager($this->container
, $this->container
, $this->mapper
, $typeManagers);
537 * @param CRM_Utils_Cache_Interface $cache
538 * @param null $cacheKey
542 public function _createContainer(CRM_Utils_Cache_Interface
$cache = NULL, $cacheKey = NULL) {
543 $basedir = $this->createTempDir('ext-');
544 mkdir("$basedir/weird");
545 mkdir("$basedir/weird/foobar");
546 file_put_contents("$basedir/weird/foobar/info.xml", "<extension key='test.foo.bar' type='" . self
::TESTING_TYPE
. "'><file>oddball</file></extension>");
547 // not needed for now // file_put_contents("$basedir/weird/bar/oddball.php", "<?php\n");
548 mkdir("$basedir/weird/whizbang");
549 file_put_contents("$basedir/weird/whizbang/info.xml", "<extension key='test.whiz.bang' type='" . self
::TESTING_TYPE
. "'><file>oddball</file></extension>");
550 // not needed for now // file_put_contents("$basedir/weird/whizbang/oddball.php", "<?php\n");
551 mkdir("$basedir/weird/downstream");
552 file_put_contents("$basedir/weird/downstream/info.xml", "<extension key='test.foo.downstream' type='" . self
::TESTING_TYPE
. "'><file>oddball</file><requires><ext>test.foo.bar</ext></requires></extension>");
553 // not needed for now // file_put_contents("$basedir/weird/downstream/oddball.php", "<?php\n");
554 $c = new CRM_Extension_Container_Basic($basedir, 'http://example/basedir', $cache, $cacheKey);
555 return array($basedir, $c);
564 public function _createDownload($key, $file) {
565 $basedir = $this->createTempDir('ext-dl-');
566 file_put_contents("$basedir/info.xml", "<extension key='$key' type='" . self
::TESTING_TYPE
. "'><file>$file</file></extension>");
567 file_put_contents("$basedir/$file.php", "<?php\n");