Merge pull request #15881 from civicrm/5.20
[civicrm-core.git] / tests / phpunit / CRM / Extension / ManagerTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Class CRM_Extension_ManagerTest
14 * @group headless
15 */
16 class CRM_Extension_ManagerTest extends CiviUnitTestCase {
17 const TESTING_TYPE = 'report';
18 const OTHER_TESTING_TYPE = 'module';
19
20 public function setUp() {
21 parent::setUp();
22 list ($this->basedir, $this->container) = $this->_createContainer();
23 $this->mapper = new CRM_Extension_Mapper($this->container);
24 }
25
26 public function tearDown() {
27 parent::tearDown();
28 }
29
30 /**
31 * Install an extension with an invalid type name.
32 *
33 * @expectedException CRM_Extension_Exception
34 */
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,
42 ]);
43 $manager->install(['test.foo.bar']);
44 }
45
46 /**
47 * Install an extension with a valid type name.
48 *
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".
52 */
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,
58 ]);
59 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
60 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
61
62 $testingTypeManager
63 ->expects($this->exactly(2))
64 ->method('onPreInstall');
65 $testingTypeManager
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'));
71
72 $testingTypeManager
73 ->expects($this->once())
74 ->method('onPreDisable');
75 $testingTypeManager
76 ->expects($this->once())
77 ->method('onPostDisable');
78 $manager->disable(['test.foo.bar']);
79 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
80 // no side-effect
81 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
82
83 $testingTypeManager
84 ->expects($this->once())
85 ->method('onPreUninstall');
86 $testingTypeManager
87 ->expects($this->once())
88 ->method('onPostUninstall');
89 $manager->uninstall(['test.foo.bar']);
90 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
91 // no side-effect
92 $this->assertEquals('installed', $manager->getStatus('test.whiz.bang'));
93 }
94
95 /**
96 * This is the same as testInstall_Disable_Uninstall, but we also install and remove a dependency.
97 *
98 * @throws \CRM_Extension_Exception
99 */
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,
105 ]);
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'));
109
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']));
114 $manager->install(
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'));
119
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'));
128
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'));
135 }
136
137 /**
138 * This is the same as testInstallAuto_Twice
139 *
140 * @throws \CRM_Extension_Exception
141 */
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,
147 ]);
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'));
151
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']));
156 $manager->install(
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'));
161
162 // And install a second time...
163 $testingTypeManager->expects($this->exactly(0))->method('onPreInstall');
164 $testingTypeManager->expects($this->exactly(0))->method('onPostInstall');
165 $manager->install(
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'));
170 }
171
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,
177 ]);
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'));
181
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']));
186 $manager->install(
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'));
191
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']));
196
197 try {
198 $manager->disable(['test.foo.bar']);
199 $this->fail('Expected disable to fail due to dependency');
200 }
201 catch (CRM_Extension_Exception $e) {
202 $this->assertRegExp('/test.foo.downstream/', $e->getMessage());
203 }
204
205 // Status unchanged
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'));
209 }
210
211 /**
212 * Install an extension and then harshly remove the underlying source.
213 * Subseuently disable and uninstall.
214 */
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,
220 ]);
221 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
222
223 $manager->install(['test.foo.bar']);
224 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
225
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"));
229 $manager->refresh();
230 $this->assertEquals('installed-missing', $manager->getStatus('test.foo.bar'));
231
232 $testingTypeManager
233 ->expects($this->once())
234 ->method('onPreDisable');
235 $testingTypeManager
236 ->expects($this->once())
237 ->method('onPostDisable');
238 $manager->disable(['test.foo.bar']);
239 $this->assertEquals('disabled-missing', $manager->getStatus('test.foo.bar'));
240
241 $testingTypeManager
242 ->expects($this->once())
243 ->method('onPreUninstall');
244 $testingTypeManager
245 ->expects($this->once())
246 ->method('onPostUninstall');
247 $manager->uninstall(['test.foo.bar']);
248 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar'));
249 }
250
251 /**
252 * Install an extension with a valid type name.
253 */
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,
259 ]);
260 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
261 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
262
263 $testingTypeManager
264 ->expects($this->exactly(2))
265 ->method('onPreInstall');
266 $testingTypeManager
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'));
272
273 $testingTypeManager
274 ->expects($this->once())
275 ->method('onPreDisable');
276 $testingTypeManager
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'));
282
283 $testingTypeManager
284 ->expects($this->once())
285 ->method('onPreEnable');
286 $testingTypeManager
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'));
292 }
293
294 /**
295 * Performing 'install' on a 'disabled' extension performs an 'enable'
296 */
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,
302 ]);
303 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
304
305 $testingTypeManager
306 ->expects($this->once())
307 ->method('onPreInstall');
308 $testingTypeManager
309 ->expects($this->once())
310 ->method('onPostInstall');
311 $manager->install(['test.foo.bar']);
312 $this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
313
314 $testingTypeManager
315 ->expects($this->once())
316 ->method('onPreDisable');
317 $testingTypeManager
318 ->expects($this->once())
319 ->method('onPostDisable');
320 $manager->disable(['test.foo.bar']);
321 $this->assertEquals('disabled', $manager->getStatus('test.foo.bar'));
322
323 $testingTypeManager
324 ->expects($this->once())
325 ->method('onPreEnable');
326 $testingTypeManager
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'));
332 }
333
334 /**
335 * Install an extension with a valid type name.
336 */
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,
342 ]);
343 $this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
344
345 $testingTypeManager
346 ->expects($this->once())
347 ->method('onPreInstall');
348 $testingTypeManager
349 ->expects($this->once())
350 ->method('onPostInstall');
351 $testingTypeManager
352 ->expects($this->never())
353 ->method('onPreEnable');
354 $testingTypeManager
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'));
360 }
361
362 /**
363 * Get the status of an unknown extension.
364 */
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,
372 ]);
373 $this->assertEquals('unknown', $manager->getStatus('test.foo.bar.whiz.bang'));
374 }
375
376 /**
377 * Replace code for an extension that doesn't exist in the container
378 */
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,
384 ]);
385 $this->assertEquals('unknown', $manager->getStatus('test.newextension'));
386
387 $this->download = $this->_createDownload('test.newextension', 'newextension');
388
389 $testingTypeManager
390 // no data to replace
391 ->expects($this->never())
392 ->method('onPreReplace');
393 $testingTypeManager
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);
403 }
404
405 /**
406 * Replace code for an extension that doesn't exist in the container
407 */
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,
413 ]);
414 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
415 $this->assertEquals('oddball', $this->mapper->keyToInfo('test.whiz.bang')->file);
416
417 $this->download = $this->_createDownload('test.whiz.bang', 'newextension');
418
419 $testingTypeManager
420 // no data to replace
421 ->expects($this->never())
422 ->method('onPreReplace');
423 $testingTypeManager
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);
434 }
435
436 /**
437 * Install a module and then replace it with new code.
438 *
439 * Note that some metadata changes between versions -- the original has
440 * file="oddball", and the upgrade has file="newextension".
441 */
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,
447 ]);
448 $this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
449 $this->assertEquals('oddball', $this->mapper->keyToInfo('test.whiz.bang')->file);
450
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"');
455
456 $this->download = $this->_createDownload('test.whiz.bang', 'newextension');
457
458 $testingTypeManager
459 ->expects($this->once())
460 ->method('onPreReplace');
461 $testingTypeManager
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"');
471 }
472
473 /**
474 * Install a module and then delete (leaving stale DB info); restore
475 * the module by downloading new code.
476 *
477 * Note that some metadata changes between versions -- the original has
478 * file="oddball", and the upgrade has file="newextension".
479 */
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,
485 ]);
486
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'));
491
492 // dirty remove
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"));
496 $manager->refresh();
497 $this->assertEquals('installed-missing', $manager->getStatus('test.whiz.bang'));
498
499 // download and reinstall
500 $this->download = $this->_createDownload('test.whiz.bang', 'newextension');
501
502 $testingTypeManager
503 ->expects($this->once())
504 ->method('onPreReplace');
505 $testingTypeManager
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"');
514 }
515
516 /**
517 * @param $typeManagers
518 *
519 * @return CRM_Extension_Manager
520 */
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);
525 }
526
527 /**
528 * @param CRM_Utils_Cache_Interface $cache
529 * @param null $cacheKey
530 *
531 * @return array
532 */
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];
547 }
548
549 /**
550 * @param $key
551 * @param $file
552 *
553 * @return string
554 */
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");
559 return $basedir;
560 }
561
562 }