3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
13 * Class CRM_Extension_ManagerTest
16 class CRM_Extension_ManagerTest
extends CiviUnitTestCase
{
17 const TESTING_TYPE
= 'report';
18 const OTHER_TESTING_TYPE
= 'module';
20 public function setUp() {
22 list ($this->basedir
, $this->container
) = $this->_createContainer();
23 $this->mapper
= new CRM_Extension_Mapper($this->container
);
26 public function tearDown() {
31 * Install an extension with an invalid type name.
33 * @expectedException CRM_Extension_Exception
35 public function testInstallInvalidType() {
36 $mockFunction = $this->mockMethod
;
37 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
38 $testingTypeManager->expects($this->never())
39 ->method('onPreInstall');
40 $manager = $this->_createManager([
41 self
::OTHER_TESTING_TYPE
=> $testingTypeManager,
43 $manager->install(['test.foo.bar']);
47 * Install an extension with a valid type name.
49 * Note: We initially install two extensions but then toggle only
50 * the second. This controls for bad SQL queries which hit either
51 * "the first row" or "all rows".
53 public function testInstall_Disable_Uninstall() {
54 $mockFunction = $this->mockMethod
;
55 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
56 $manager = $this->_createManager([
57 self
::TESTING_TYPE
=> $testingTypeManager,
59 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
60 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
63 ->expects($this->exactly(2))
64 ->method('onPreInstall');
66 ->expects($this->exactly(2))
67 ->method('onPostInstall');
68 $manager->install(['test.whiz.bang', 'test.foo.bar']);
69 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
70 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
73 ->expects($this->once())
74 ->method('onPreDisable');
76 ->expects($this->once())
77 ->method('onPostDisable');
78 $manager->disable(['test.foo.bar']);
79 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
81 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
84 ->expects($this->once())
85 ->method('onPreUninstall');
87 ->expects($this->once())
88 ->method('onPostUninstall');
89 $manager->uninstall(['test.foo.bar']);
90 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
92 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
96 * This is the same as testInstall_Disable_Uninstall, but we also install and remove a dependency.
98 * @throws \CRM_Extension_Exception
100 public function test_InstallAuto_DisableDownstream_UninstallDownstream() {
101 $mockFunction = $this->mockMethod
;
102 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
103 $manager = $this->_createManager([
104 self
::TESTING_TYPE
=> $testingTypeManager,
106 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
107 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
108 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
110 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
111 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
112 $this->assertEquals(['test.foo.bar', 'test.foo.downstream'],
113 $manager->findInstallRequirements(['test.foo.downstream']));
115 $manager->findInstallRequirements(['test.foo.downstream']));
116 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
117 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
118 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
120 $testingTypeManager->expects($this->once())->method('onPreDisable');
121 $testingTypeManager->expects($this->once())->method('onPostDisable');
122 $this->assertEquals(['test.foo.downstream'],
123 $manager->findDisableRequirements(['test.foo.downstream']));
124 $manager->disable(['test.foo.downstream']);
125 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
126 $this->assertEquals('disabled', $manager->getStatus('test.foo.downstream'));
127 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
129 $testingTypeManager->expects($this->once())->method('onPreUninstall');
130 $testingTypeManager->expects($this->once())->method('onPostUninstall');
131 $manager->uninstall(['test.foo.downstream']);
132 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
133 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
134 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
138 * This is the same as testInstallAuto_Twice
140 * @throws \CRM_Extension_Exception
142 public function testInstallAuto_Twice() {
143 $mockFunction = $this->mockMethod
;
144 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
145 $manager = $this->_createManager([
146 self
::TESTING_TYPE
=> $testingTypeManager,
148 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
149 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
150 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
152 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
153 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
154 $this->assertEquals(['test.foo.bar', 'test.foo.downstream'],
155 $manager->findInstallRequirements(['test.foo.downstream']));
157 $manager->findInstallRequirements(['test.foo.downstream']));
158 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
159 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
160 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
162 // And install a second time...
163 $testingTypeManager->expects($this->exactly(0))->method('onPreInstall');
164 $testingTypeManager->expects($this->exactly(0))->method('onPostInstall');
166 $manager->findInstallRequirements(['test.foo.downstream']));
167 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
168 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
169 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
172 public function test_InstallAuto_DisableUpstream() {
173 $mockFunction = $this->mockMethod
;
174 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
175 $manager = $this->_createManager([
176 self
::TESTING_TYPE
=> $testingTypeManager,
178 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
179 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
180 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
182 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
183 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
184 $this->assertEquals(['test.foo.bar', 'test.foo.downstream'],
185 $manager->findInstallRequirements(['test.foo.downstream']));
187 $manager->findInstallRequirements(['test.foo.downstream']));
188 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
189 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
190 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
192 $testingTypeManager->expects($this->never())->method('onPreDisable');
193 $testingTypeManager->expects($this->never())->method('onPostDisable');
194 $this->assertEquals(['test.foo.downstream', 'test.foo.bar'],
195 $manager->findDisableRequirements(['test.foo.bar']));
198 $manager->disable(['test.foo.bar']);
199 $this->fail('Expected disable to fail due to dependency');
201 catch (CRM_Extension_Exception
$e) {
202 $this->assertRegExp('/test.foo.downstream/', $e->getMessage());
206 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
207 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
208 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
212 * Install an extension and then harshly remove the underlying source.
213 * Subseuently disable and uninstall.
215 public function testInstall_DirtyRemove_Disable_Uninstall() {
216 $mockFunction = $this->mockMethod
;
217 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
218 $manager = $this->_createManager([
219 self
::TESTING_TYPE
=> $testingTypeManager,
221 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
223 $manager->install(['test.foo.bar']);
224 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
226 $this->assertTrue(file_exists("{$this->basedir}/weird/foobar/info.xml"));
227 CRM_Utils_File
::cleanDir("{$this->basedir}/weird/foobar", TRUE, FALSE);
228 $this->assertFalse(file_exists("{$this->basedir}/weird/foobar/info.xml"));
230 $this->assertEquals('installed-missing', $manager->getStatus('test.foo.bar'));
233 ->expects($this->once())
234 ->method('onPreDisable');
236 ->expects($this->once())
237 ->method('onPostDisable');
238 $manager->disable(['test.foo.bar']);
239 $this->assertEquals('disabled-missing', $manager->getStatus('test.foo.bar'));
242 ->expects($this->once())
243 ->method('onPreUninstall');
245 ->expects($this->once())
246 ->method('onPostUninstall');
247 $manager->uninstall(['test.foo.bar']);
248 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar'));
252 * Install an extension with a valid type name.
254 public function testInstall_Disable_Enable() {
255 $mockFunction = $this->mockMethod
;
256 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
257 $manager = $this->_createManager([
258 self
::TESTING_TYPE
=> $testingTypeManager,
260 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
261 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
264 ->expects($this->exactly(2))
265 ->method('onPreInstall');
267 ->expects($this->exactly(2))
268 ->method('onPostInstall');
269 $manager->install(['test.whiz.bang', 'test.foo.bar']);
270 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
271 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
274 ->expects($this->once())
275 ->method('onPreDisable');
277 ->expects($this->once())
278 ->method('onPostDisable');
279 $manager->disable(['test.foo.bar']);
280 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
281 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
284 ->expects($this->once())
285 ->method('onPreEnable');
287 ->expects($this->once())
288 ->method('onPostEnable');
289 $manager->enable(['test.foo.bar']);
290 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
291 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
295 * Performing 'install' on a 'disabled' extension performs an 'enable'
297 public function testInstall_Disable_Install() {
298 $mockFunction = $this->mockMethod
;
299 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
300 $manager = $this->_createManager([
301 self
::TESTING_TYPE
=> $testingTypeManager,
303 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
306 ->expects($this->once())
307 ->method('onPreInstall');
309 ->expects($this->once())
310 ->method('onPostInstall');
311 $manager->install(['test.foo.bar']);
312 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
315 ->expects($this->once())
316 ->method('onPreDisable');
318 ->expects($this->once())
319 ->method('onPostDisable');
320 $manager->disable(['test.foo.bar']);
321 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
324 ->expects($this->once())
325 ->method('onPreEnable');
327 ->expects($this->once())
328 ->method('onPostEnable');
329 // install() instead of enable()
330 $manager->install(['test.foo.bar']);
331 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
335 * Install an extension with a valid type name.
337 public function testEnableBare() {
338 $mockFunction = $this->mockMethod
;
339 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
340 $manager = $this->_createManager([
341 self
::TESTING_TYPE
=> $testingTypeManager,
343 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
346 ->expects($this->once())
347 ->method('onPreInstall');
349 ->expects($this->once())
350 ->method('onPostInstall');
352 ->expects($this->never())
353 ->method('onPreEnable');
355 ->expects($this->never())
356 ->method('onPostEnable');
357 // enable not install
358 $manager->enable(['test.foo.bar']);
359 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
363 * Get the status of an unknown extension.
365 public function testStatusUnknownKey() {
366 $mockFunction = $this->mockMethod
;
367 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
368 $testingTypeManager->expects($this->never())
369 ->method('onPreInstall');
370 $manager = $this->_createManager([
371 self
::TESTING_TYPE
=> $testingTypeManager,
373 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar.whiz.bang'));
377 * Replace code for an extension that doesn't exist in the container
379 public function testReplace_Unknown() {
380 $mockFunction = $this->mockMethod
;
381 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
382 $manager = $this->_createManager([
383 self
::TESTING_TYPE
=> $testingTypeManager,
385 $this->assertEquals('unknown', $manager->getStatus('test.newextension'));
387 $this->download
= $this->_createDownload('test.newextension', 'newextension');
390 // no data to replace
391 ->expects($this->never())
392 ->method('onPreReplace');
394 // no data to replace
395 ->expects($this->never())
396 ->method('onPostReplace');
397 $manager->replace($this->download
);
398 $this->assertEquals('uninstalled', $manager->getStatus('test.newextension'));
399 $this->assertTrue(file_exists("{$this->basedir}/test.newextension/info.xml"));
400 $this->assertTrue(file_exists("{$this->basedir}/test.newextension/newextension.php"));
401 $this->assertEquals(self
::TESTING_TYPE
, $this->mapper
->keyToInfo('test.newextension')->type
);
402 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.newextension')->file
);
406 * Replace code for an extension that doesn't exist in the container
408 public function testReplace_Uninstalled() {
409 $mockFunction = $this->mockMethod
;
410 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
411 $manager = $this->_createManager([
412 self
::TESTING_TYPE
=> $testingTypeManager,
414 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
415 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
417 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
420 // no data to replace
421 ->expects($this->never())
422 ->method('onPreReplace');
424 // no data to replace
425 ->expects($this->never())
426 ->method('onPostReplace');
427 $manager->replace($this->download
);
428 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
429 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
430 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/newextension.php"));
431 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/oddball.php"));
432 $this->assertEquals(self
::TESTING_TYPE
, $this->mapper
->keyToInfo('test.whiz.bang')->type
);
433 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
437 * Install a module and then replace it with new code.
439 * Note that some metadata changes between versions -- the original has
440 * file="oddball", and the upgrade has file="newextension".
442 public function testReplace_Installed() {
443 $mockFunction = $this->mockMethod
;
444 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
445 $manager = $this->_createManager([
446 self
::TESTING_TYPE
=> $testingTypeManager,
448 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
449 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
451 $manager->install(['test.whiz.bang']);
452 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
453 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
454 $this->assertDBQuery('oddball', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
456 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
459 ->expects($this->once())
460 ->method('onPreReplace');
462 ->expects($this->once())
463 ->method('onPostReplace');
464 $manager->replace($this->download
);
465 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
466 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
467 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/newextension.php"));
468 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/oddball.php"));
469 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
470 $this->assertDBQuery('newextension', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
474 * Install a module and then delete (leaving stale DB info); restore
475 * the module by downloading new code.
477 * Note that some metadata changes between versions -- the original has
478 * file="oddball", and the upgrade has file="newextension".
480 public function testReplace_InstalledMissing() {
481 $mockFunction = $this->mockMethod
;
482 $testingTypeManager = $this->$mockFunction('CRM_Extension_Manager_Interface');
483 $manager = $this->_createManager([
484 self
::TESTING_TYPE
=> $testingTypeManager,
487 // initial installation
488 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
489 $manager->install(['test.whiz.bang']);
490 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
493 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
494 CRM_Utils_File
::cleanDir("{$this->basedir}/weird/whizbang", TRUE, FALSE);
495 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
497 $this->assertEquals('installed-missing', $manager->getStatus('test.whiz.bang'));
499 // download and reinstall
500 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
503 ->expects($this->once())
504 ->method('onPreReplace');
506 ->expects($this->once())
507 ->method('onPostReplace');
508 $manager->replace($this->download
);
509 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
510 $this->assertTrue(file_exists("{$this->basedir}/test.whiz.bang/info.xml"));
511 $this->assertTrue(file_exists("{$this->basedir}/test.whiz.bang/newextension.php"));
512 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
513 $this->assertDBQuery('newextension', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
517 * @param $typeManagers
519 * @return CRM_Extension_Manager
521 public function _createManager($typeManagers) {
522 //list ($basedir, $c) = $this->_createContainer();
523 $mapper = new CRM_Extension_Mapper($this->container
);
524 return new CRM_Extension_Manager($this->container
, $this->container
, $this->mapper
, $typeManagers);
528 * @param CRM_Utils_Cache_Interface $cache
529 * @param null $cacheKey
533 public function _createContainer(CRM_Utils_Cache_Interface
$cache = NULL, $cacheKey = NULL) {
534 $basedir = $this->createTempDir('ext-');
535 mkdir("$basedir/weird");
536 mkdir("$basedir/weird/foobar");
537 file_put_contents("$basedir/weird/foobar/info.xml", "<extension key='test.foo.bar' type='" . self
::TESTING_TYPE
. "'><file>oddball</file></extension>");
538 // not needed for now // file_put_contents("$basedir/weird/bar/oddball.php", "<?php\n");
539 mkdir("$basedir/weird/whizbang");
540 file_put_contents("$basedir/weird/whizbang/info.xml", "<extension key='test.whiz.bang' type='" . self
::TESTING_TYPE
. "'><file>oddball</file></extension>");
541 // not needed for now // file_put_contents("$basedir/weird/whizbang/oddball.php", "<?php\n");
542 mkdir("$basedir/weird/downstream");
543 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>");
544 // not needed for now // file_put_contents("$basedir/weird/downstream/oddball.php", "<?php\n");
545 $c = new CRM_Extension_Container_Basic($basedir, 'http://example/basedir', $cache, $cacheKey);
546 return [$basedir, $c];
555 public function _createDownload($key, $file) {
556 $basedir = $this->createTempDir('ext-dl-');
557 file_put_contents("$basedir/info.xml", "<extension key='$key' type='" . self
::TESTING_TYPE
. "'><file>$file</file></extension>");
558 file_put_contents("$basedir/$file.php", "<?php\n");