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(): void
{
22 list ($this->basedir
, $this->container
) = $this->_createContainer();
23 $this->mapper
= new CRM_Extension_Mapper($this->container
);
27 * Install an extension with an invalid type name.
29 * @expectedException CRM_Extension_Exception
31 public function testInstallInvalidType() {
32 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
33 $testingTypeManager->expects($this->never())
34 ->method('onPreInstall');
35 $manager = $this->_createManager([
36 self
::OTHER_TESTING_TYPE
=> $testingTypeManager,
38 $manager->install(['test.foo.bar']);
42 * Install an extension with a valid type name.
44 * Note: We initially install two extensions but then toggle only
45 * the second. This controls for bad SQL queries which hit either
46 * "the first row" or "all rows".
48 public function testInstall_Disable_Uninstall() {
49 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
50 $manager = $this->_createManager([
51 self
::TESTING_TYPE
=> $testingTypeManager,
53 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
54 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
57 ->expects($this->exactly(2))
58 ->method('onPreInstall');
60 ->expects($this->exactly(2))
61 ->method('onPostInstall');
62 $manager->install(['test.whiz.bang', 'test.foo.bar']);
63 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
64 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
67 ->expects($this->once())
68 ->method('onPreDisable');
70 ->expects($this->once())
71 ->method('onPostDisable');
72 $manager->disable(['test.foo.bar']);
73 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
75 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
78 ->expects($this->once())
79 ->method('onPreUninstall');
81 ->expects($this->once())
82 ->method('onPostUninstall');
83 $manager->uninstall(['test.foo.bar']);
84 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
86 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
90 * This is the same as testInstall_Disable_Uninstall, but we also install and remove a dependency.
92 * @throws \CRM_Extension_Exception
94 public function test_InstallAuto_DisableDownstream_UninstallDownstream() {
95 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
96 $manager = $this->_createManager([
97 self
::TESTING_TYPE
=> $testingTypeManager,
99 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
100 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
101 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
103 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
104 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
105 $this->assertEquals(['test.foo.bar', 'test.foo.downstream'],
106 $manager->findInstallRequirements(['test.foo.downstream']));
108 $manager->findInstallRequirements(['test.foo.downstream']));
109 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
110 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
111 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
113 $testingTypeManager->expects($this->once())->method('onPreDisable');
114 $testingTypeManager->expects($this->once())->method('onPostDisable');
115 $this->assertEquals(['test.foo.downstream'],
116 $manager->findDisableRequirements(['test.foo.downstream']));
117 $manager->disable(['test.foo.downstream']);
118 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
119 $this->assertEquals('disabled', $manager->getStatus('test.foo.downstream'));
120 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
122 $testingTypeManager->expects($this->once())->method('onPreUninstall');
123 $testingTypeManager->expects($this->once())->method('onPostUninstall');
124 $manager->uninstall(['test.foo.downstream']);
125 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
126 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
127 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
131 * This is the same as testInstallAuto_Twice
133 * @throws \CRM_Extension_Exception
135 public function testInstallAuto_Twice() {
136 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
137 $manager = $this->_createManager([
138 self
::TESTING_TYPE
=> $testingTypeManager,
140 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
141 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
142 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
144 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
145 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
146 $this->assertEquals(['test.foo.bar', 'test.foo.downstream'],
147 $manager->findInstallRequirements(['test.foo.downstream']));
149 $manager->findInstallRequirements(['test.foo.downstream']));
150 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
151 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
152 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
154 // And install a second time...
155 $testingTypeManager->expects($this->exactly(0))->method('onPreInstall');
156 $testingTypeManager->expects($this->exactly(0))->method('onPostInstall');
158 $manager->findInstallRequirements(['test.foo.downstream']));
159 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
160 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
161 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
164 public function test_InstallAuto_DisableUpstream() {
165 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
166 $manager = $this->_createManager([
167 self
::TESTING_TYPE
=> $testingTypeManager,
169 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
170 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
171 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
173 $testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
174 $testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
175 $this->assertEquals(['test.foo.bar', 'test.foo.downstream'],
176 $manager->findInstallRequirements(['test.foo.downstream']));
178 $manager->findInstallRequirements(['test.foo.downstream']));
179 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
180 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
181 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
183 $testingTypeManager->expects($this->never())->method('onPreDisable');
184 $testingTypeManager->expects($this->never())->method('onPostDisable');
185 $this->assertEquals(['test.foo.downstream', 'test.foo.bar'],
186 $manager->findDisableRequirements(['test.foo.bar']));
189 $manager->disable(['test.foo.bar']);
190 $this->fail('Expected disable to fail due to dependency');
192 catch (CRM_Extension_Exception
$e) {
193 $this->assertRegExp('/test.foo.downstream/', $e->getMessage());
197 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
198 $this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
199 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
203 * Install an extension and then harshly remove the underlying source.
204 * Subseuently disable and uninstall.
206 public function testInstall_DirtyRemove_Disable_Uninstall() {
207 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
208 $manager = $this->_createManager([
209 self
::TESTING_TYPE
=> $testingTypeManager,
211 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
213 $manager->install(['test.foo.bar']);
214 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
216 $this->assertTrue(file_exists("{$this->basedir}/weird/foobar/info.xml"));
217 CRM_Utils_File
::cleanDir("{$this->basedir}/weird/foobar", TRUE, FALSE);
218 $this->assertFalse(file_exists("{$this->basedir}/weird/foobar/info.xml"));
220 $this->assertEquals('installed-missing', $manager->getStatus('test.foo.bar'));
223 ->expects($this->once())
224 ->method('onPreDisable');
226 ->expects($this->once())
227 ->method('onPostDisable');
228 $manager->disable(['test.foo.bar']);
229 $this->assertEquals('disabled-missing', $manager->getStatus('test.foo.bar'));
232 ->expects($this->once())
233 ->method('onPreUninstall');
235 ->expects($this->once())
236 ->method('onPostUninstall');
237 $manager->uninstall(['test.foo.bar']);
238 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar'));
242 * Install an extension with a valid type name.
244 public function testInstall_Disable_Enable() {
245 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
246 $manager = $this->_createManager([
247 self
::TESTING_TYPE
=> $testingTypeManager,
249 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
250 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
253 ->expects($this->exactly(2))
254 ->method('onPreInstall');
256 ->expects($this->exactly(2))
257 ->method('onPostInstall');
258 $manager->install(['test.whiz.bang', 'test.foo.bar']);
259 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
260 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
263 ->expects($this->once())
264 ->method('onPreDisable');
266 ->expects($this->once())
267 ->method('onPostDisable');
268 $manager->disable(['test.foo.bar']);
269 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
270 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
273 ->expects($this->once())
274 ->method('onPreEnable');
276 ->expects($this->once())
277 ->method('onPostEnable');
278 $manager->enable(['test.foo.bar']);
279 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
280 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
284 * Performing 'install' on a 'disabled' extension performs an 'enable'
286 public function testInstall_Disable_Install() {
287 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
288 $manager = $this->_createManager([
289 self
::TESTING_TYPE
=> $testingTypeManager,
291 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
294 ->expects($this->once())
295 ->method('onPreInstall');
297 ->expects($this->once())
298 ->method('onPostInstall');
299 $manager->install(['test.foo.bar']);
300 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
303 ->expects($this->once())
304 ->method('onPreDisable');
306 ->expects($this->once())
307 ->method('onPostDisable');
308 $manager->disable(['test.foo.bar']);
309 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
312 ->expects($this->once())
313 ->method('onPreEnable');
315 ->expects($this->once())
316 ->method('onPostEnable');
317 // install() instead of enable()
318 $manager->install(['test.foo.bar']);
319 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
323 * Install an extension with a valid type name.
325 public function testEnableBare() {
326 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
327 $manager = $this->_createManager([
328 self
::TESTING_TYPE
=> $testingTypeManager,
330 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
333 ->expects($this->once())
334 ->method('onPreInstall');
336 ->expects($this->once())
337 ->method('onPostInstall');
339 ->expects($this->never())
340 ->method('onPreEnable');
342 ->expects($this->never())
343 ->method('onPostEnable');
344 // enable not install
345 $manager->enable(['test.foo.bar']);
346 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
350 * Get the status of an unknown extension.
352 public function testStatusUnknownKey() {
353 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
354 $testingTypeManager->expects($this->never())
355 ->method('onPreInstall');
356 $manager = $this->_createManager([
357 self
::TESTING_TYPE
=> $testingTypeManager,
359 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar.whiz.bang'));
363 * Replace code for an extension that doesn't exist in the container
365 public function testReplace_Unknown() {
366 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
367 $manager = $this->_createManager([
368 self
::TESTING_TYPE
=> $testingTypeManager,
370 $this->assertEquals('unknown', $manager->getStatus('test.newextension'));
372 $this->download
= $this->_createDownload('test.newextension', 'newextension');
375 // no data to replace
376 ->expects($this->never())
377 ->method('onPreReplace');
379 // no data to replace
380 ->expects($this->never())
381 ->method('onPostReplace');
382 $manager->replace($this->download
);
383 $this->assertEquals('uninstalled', $manager->getStatus('test.newextension'));
384 $this->assertTrue(file_exists("{$this->basedir}/test.newextension/info.xml"));
385 $this->assertTrue(file_exists("{$this->basedir}/test.newextension/newextension.php"));
386 $this->assertEquals(self
::TESTING_TYPE
, $this->mapper
->keyToInfo('test.newextension')->type
);
387 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.newextension')->file
);
391 * Replace code for an extension that doesn't exist in the container
393 public function testReplace_Uninstalled() {
394 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
395 $manager = $this->_createManager([
396 self
::TESTING_TYPE
=> $testingTypeManager,
398 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
399 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
401 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
404 // no data to replace
405 ->expects($this->never())
406 ->method('onPreReplace');
408 // no data to replace
409 ->expects($this->never())
410 ->method('onPostReplace');
411 $manager->replace($this->download
);
412 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
413 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
414 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/newextension.php"));
415 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/oddball.php"));
416 $this->assertEquals(self
::TESTING_TYPE
, $this->mapper
->keyToInfo('test.whiz.bang')->type
);
417 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
421 * Install a module and then replace it with new code.
423 * Note that some metadata changes between versions -- the original has
424 * file="oddball", and the upgrade has file="newextension".
426 public function testReplace_Installed() {
427 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
428 $manager = $this->_createManager([
429 self
::TESTING_TYPE
=> $testingTypeManager,
431 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
432 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
434 $manager->install(['test.whiz.bang']);
435 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
436 $this->assertEquals('oddball', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
437 $this->assertDBQuery('oddball', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
439 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
442 ->expects($this->once())
443 ->method('onPreReplace');
445 ->expects($this->once())
446 ->method('onPostReplace');
447 $manager->replace($this->download
);
448 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
449 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
450 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/newextension.php"));
451 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/oddball.php"));
452 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
453 $this->assertDBQuery('newextension', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
457 * Install a module and then delete (leaving stale DB info); restore
458 * the module by downloading new code.
460 * Note that some metadata changes between versions -- the original has
461 * file="oddball", and the upgrade has file="newextension".
463 public function testReplace_InstalledMissing() {
464 $testingTypeManager = $this->getMockBuilder('CRM_Extension_Manager_Interface')->getMock();
465 $manager = $this->_createManager([
466 self
::TESTING_TYPE
=> $testingTypeManager,
469 // initial installation
470 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
471 $manager->install(['test.whiz.bang']);
472 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
475 $this->assertTrue(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
476 CRM_Utils_File
::cleanDir("{$this->basedir}/weird/whizbang", TRUE, FALSE);
477 $this->assertFalse(file_exists("{$this->basedir}/weird/whizbang/info.xml"));
479 $this->assertEquals('installed-missing', $manager->getStatus('test.whiz.bang'));
481 // download and reinstall
482 $this->download
= $this->_createDownload('test.whiz.bang', 'newextension');
485 ->expects($this->once())
486 ->method('onPreReplace');
488 ->expects($this->once())
489 ->method('onPostReplace');
490 $manager->replace($this->download
);
491 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
492 $this->assertTrue(file_exists("{$this->basedir}/test.whiz.bang/info.xml"));
493 $this->assertTrue(file_exists("{$this->basedir}/test.whiz.bang/newextension.php"));
494 $this->assertEquals('newextension', $this->mapper
->keyToInfo('test.whiz.bang')->file
);
495 $this->assertDBQuery('newextension', 'SELECT file FROM civicrm_extension WHERE full_name ="test.whiz.bang"');
499 * @param $typeManagers
501 * @return CRM_Extension_Manager
503 public function _createManager($typeManagers) {
504 //list ($basedir, $c) = $this->_createContainer();
505 $mapper = new CRM_Extension_Mapper($this->container
);
506 return new CRM_Extension_Manager($this->container
, $this->container
, $this->mapper
, $typeManagers);
510 * @param CRM_Utils_Cache_Interface $cache
511 * @param null $cacheKey
515 public function _createContainer(CRM_Utils_Cache_Interface
$cache = NULL, $cacheKey = NULL) {
516 $basedir = $this->createTempDir('ext-');
517 mkdir("$basedir/weird");
518 mkdir("$basedir/weird/foobar");
519 file_put_contents("$basedir/weird/foobar/info.xml", "<extension key='test.foo.bar' type='" . self
::TESTING_TYPE
. "'><file>oddball</file></extension>");
520 // not needed for now // file_put_contents("$basedir/weird/bar/oddball.php", "<?php\n");
521 mkdir("$basedir/weird/whizbang");
522 file_put_contents("$basedir/weird/whizbang/info.xml", "<extension key='test.whiz.bang' type='" . self
::TESTING_TYPE
. "'><file>oddball</file></extension>");
523 // not needed for now // file_put_contents("$basedir/weird/whizbang/oddball.php", "<?php\n");
524 mkdir("$basedir/weird/downstream");
525 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>");
526 // not needed for now // file_put_contents("$basedir/weird/downstream/oddball.php", "<?php\n");
527 $c = new CRM_Extension_Container_Basic($basedir, 'http://example/basedir', $cache, $cacheKey);
528 return [$basedir, $c];
537 public function _createDownload($key, $file) {
538 $basedir = $this->createTempDir('ext-dl-');
539 file_put_contents("$basedir/info.xml", "<extension key='$key' type='" . self
::TESTING_TYPE
. "'><file>$file</file></extension>");
540 file_put_contents("$basedir/$file.php", "<?php\n");