MDL-40901 use assertInternalType()
[moodle.git] / lib / tests / pluginlib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for the lib/pluginlib.php library
19  *
20  * Execute the core_plugin group to run all tests in this file:
21  *
22  *  $ phpunit --group core_plugin
23  *
24  * @package   core
25  * @category  phpunit
26  * @copyright 2012 David Mudrak <david@moodle.com>
27  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28  */
30 defined('MOODLE_INTERNAL') || die();
32 global $CFG;
33 require_once($CFG->libdir.'/pluginlib.php');
36 /**
37  * Tests of the basic API of the plugin manager.
38  *
39  * @group core_plugin
40  */
41 class core_plugin_manager_testcase extends advanced_testcase {
43     public function setUp() {
44         $this->resetAfterTest();
45     }
47     public function test_plugin_manager_instance() {
48         $pluginman = testable_plugin_manager::instance();
49         $this->assertInstanceOf('testable_plugin_manager', $pluginman);
50     }
52     public function test_get_plugins_of_type() {
53         $pluginman = testable_plugin_manager::instance();
54         $mods = $pluginman->get_plugins_of_type('mod');
55         $this->assertInternalType('array', $mods);
56         $this->assertCount(5, $mods);
57         $this->assertInstanceOf('testable_plugininfo_mod', $mods['foo']);
58         $this->assertInstanceOf('testable_plugininfo_mod', $mods['bar']);
59         $this->assertInstanceOf('testable_plugininfo_mod', $mods['baz']);
60         $this->assertInstanceOf('testable_plugininfo_mod', $mods['qux']);
61         $this->assertInstanceOf('testable_plugininfo_mod', $mods['new']);
62         $foolishes = $pluginman->get_plugins_of_type('foolish');
63         $this->assertCount(2, $foolishes);
64         $this->assertInstanceOf('testable_pluginfo_foolish', $foolishes['frog']);
65         $this->assertInstanceOf('testable_pluginfo_foolish', $foolishes['hippo']);
66         $bazmegs = $pluginman->get_plugins_of_type('bazmeg');
67         $this->assertCount(1, $bazmegs);
68         $this->assertInstanceOf('testable_pluginfo_bazmeg', $bazmegs['one']);
69         $quxcats = $pluginman->get_plugins_of_type('quxcat');
70         $this->assertCount(1, $quxcats);
71         $this->assertInstanceOf('testable_pluginfo_quxcat', $quxcats['one']);
72         $unknown = $pluginman->get_plugins_of_type('muhehe');
73         $this->assertSame(array(), $unknown);
74     }
76     public function test_get_plugins() {
77         $pluginman = testable_plugin_manager::instance();
78         $plugins = $pluginman->get_plugins();
79         $this->assertInternalType('array', $plugins);
80         $this->assertTrue(isset($plugins['mod']['foo']));
81         $this->assertTrue(isset($plugins['mod']['bar']));
82         $this->assertTrue(isset($plugins['mod']['baz']));
83         $this->assertTrue(isset($plugins['mod']['new']));
84         $this->assertTrue(isset($plugins['foolish']['frog']));
85         $this->assertTrue(isset($plugins['foolish']['hippo']));
86         $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['foo']);
87         $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['bar']);
88         $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['baz']);
89         $this->assertInstanceOf('testable_plugininfo_mod', $plugins['mod']['new']);
90         $this->assertInstanceOf('testable_pluginfo_foolish', $plugins['foolish']['frog']);
91         $this->assertInstanceOf('testable_pluginfo_foolish', $plugins['foolish']['hippo']);
92         $this->assertInstanceOf('testable_pluginfo_bazmeg', $plugins['bazmeg']['one']);
93         $this->assertInstanceOf('testable_pluginfo_quxcat', $plugins['quxcat']['one']);
94     }
96     public function test_get_subplugins_of_plugin() {
97         $pluginman = testable_plugin_manager::instance();
98         $this->assertSame(array(), $pluginman->get_subplugins_of_plugin('mod_missing'));
99         $this->assertSame(array(), $pluginman->get_subplugins_of_plugin('mod_bar'));
100         $foosubs = $pluginman->get_subplugins_of_plugin('mod_foo');
101         $this->assertInternalType('array', $foosubs);
102         $this->assertCount(2, $foosubs);
103         $this->assertInstanceOf('testable_pluginfo_foolish', $foosubs['foolish_frog']);
104         $this->assertInstanceOf('testable_pluginfo_foolish', $foosubs['foolish_hippo']);
105         $bazsubs = $pluginman->get_subplugins_of_plugin('mod_baz');
106         $this->assertInternalType('array', $bazsubs);
107         $this->assertCount(1, $bazsubs);
108         $this->assertInstanceOf('testable_pluginfo_bazmeg', $bazsubs['bazmeg_one']);
109         $quxsubs = $pluginman->get_subplugins_of_plugin('mod_qux');
110         $this->assertInternalType('array', $quxsubs);
111         $this->assertCount(1, $quxsubs);
112         $this->assertInstanceOf('testable_pluginfo_quxcat', $quxsubs['quxcat_one']);
113     }
115     public function test_get_subplugins() {
116         $pluginman = testable_plugin_manager::instance();
117         $subplugins = $pluginman->get_subplugins();
118         $this->assertTrue(isset($subplugins['mod_foo']['foolish']));
119         $this->assertTrue(isset($subplugins['mod_baz']['bazmeg']));
120         $this->assertTrue(isset($subplugins['mod_qux']['quxcat']));
121     }
123     public function test_get_parent_of_subplugin() {
124         $pluginman = testable_plugin_manager::instance();
125         $this->assertSame('mod_foo', $pluginman->get_parent_of_subplugin('foolish'));
126         $this->assertSame('mod_baz', $pluginman->get_parent_of_subplugin('bazmeg'));
127         $this->assertSame('mod_qux', $pluginman->get_parent_of_subplugin('quxcat'));
128         $this->assertFalse($pluginman->get_parent_of_subplugin('mod'));
129         $this->assertFalse($pluginman->get_parent_of_subplugin('unknown'));
130         $plugins = $pluginman->get_plugins();
131         $this->assertFalse($plugins['mod']['foo']->is_subplugin());
132         $this->assertFalse($plugins['mod']['foo']->get_parent_plugin());
133         $this->assertTrue($plugins['foolish']['frog']->is_subplugin());
134         $this->assertSame('mod_foo', $plugins['foolish']['frog']->get_parent_plugin());
135     }
137     public function test_plugin_name() {
138         $pluginman = testable_plugin_manager::instance();
139         $this->assertSame('Foo', $pluginman->plugin_name('mod_foo'));
140         $this->assertSame('Bar', $pluginman->plugin_name('mod_bar'));
141         $this->assertSame('Frog', $pluginman->plugin_name('foolish_frog'));
142         $this->assertSame('Hippo', $pluginman->plugin_name('foolish_hippo'));
143         $this->assertSame('One', $pluginman->plugin_name('bazmeg_one'));
144         $this->assertSame('One', $pluginman->plugin_name('quxcat_one'));
145     }
147     public function test_get_plugin_info() {
148         $pluginman = testable_plugin_manager::instance();
149         $this->assertInstanceOf('testable_plugininfo_mod', $pluginman->get_plugin_info('mod_foo'));
150         $this->assertInstanceOf('testable_pluginfo_foolish', $pluginman->get_plugin_info('foolish_frog'));
151     }
153     public function test_other_plugins_that_require() {
154         $pluginman = testable_plugin_manager::instance();
155         $this->assertEquals(array('foolish_frog'), $pluginman->other_plugins_that_require('mod_foo'));
156         $this->assertCount(2, $pluginman->other_plugins_that_require('foolish_frog'));
157         $this->assertContains('foolish_hippo', $pluginman->other_plugins_that_require('foolish_frog'));
158         $this->assertContains('mod_foo', $pluginman->other_plugins_that_require('foolish_frog'));
159         $this->assertEquals(array(), $pluginman->other_plugins_that_require('foolish_hippo'));
160         $this->assertEquals(array('mod_foo'), $pluginman->other_plugins_that_require('mod_bar'));
161         $this->assertEquals(array('mod_foo'), $pluginman->other_plugins_that_require('mod_missing'));
162         $this->assertEquals(array('quxcat_one'), $pluginman->other_plugins_that_require('bazmeg_one'));
163     }
165     public function test_are_dependencies_satisfied() {
166         $pluginman = testable_plugin_manager::instance();
167         $this->assertTrue($pluginman->are_dependencies_satisfied(array()));
168         $this->assertTrue($pluginman->are_dependencies_satisfied(array(
169             'mod_bar' => 2012030500,
170         )));
171         $this->assertTrue($pluginman->are_dependencies_satisfied(array(
172             'mod_bar' => ANY_VERSION,
173         )));
174         $this->assertFalse($pluginman->are_dependencies_satisfied(array(
175             'mod_bar' => 2099010000,
176         )));
177         $this->assertFalse($pluginman->are_dependencies_satisfied(array(
178             'mod_bar' => 2012030500,
179             'mod_missing' => ANY_VERSION,
180         )));
181     }
183     public function test_all_plugins_ok() {
184         $pluginman = testable_plugin_manager::instance();
185         $failedplugins = array();
186         $this->assertFalse($pluginman->all_plugins_ok(2013010100, $failedplugins));
187         $this->assertContains('mod_foo', $failedplugins); // Requires mod_missing.
188         $this->assertNotContains('mod_bar', $failedplugins);
189         $this->assertNotContains('foolish_frog', $failedplugins);
190         $this->assertNotContains('foolish_hippo', $failedplugins);
192         $failedplugins = array();
193         $this->assertFalse($pluginman->all_plugins_ok(2012010100, $failedplugins));
194         $this->assertContains('mod_foo', $failedplugins); // Requires mod_missing.
195         $this->assertNotContains('mod_bar', $failedplugins);
196         $this->assertContains('foolish_frog', $failedplugins); // Requires Moodle 2013010100.
197         $this->assertNotContains('foolish_hippo', $failedplugins);
199         $failedplugins = array();
200         $this->assertFalse($pluginman->all_plugins_ok(2011010100, $failedplugins));
201         $this->assertContains('mod_foo', $failedplugins); // Requires mod_missing and Moodle 2012010100.
202         $this->assertContains('mod_bar', $failedplugins); // Requires Moodle 2012010100.
203         $this->assertContains('foolish_frog', $failedplugins); // Requires Moodle 2013010100.
204         $this->assertContains('foolish_hippo', $failedplugins); // Requires Moodle 2012010100.
205     }
207     public function test_some_plugins_updatable() {
208         $pluginman = testable_plugin_manager::instance();
209         $this->assertTrue($pluginman->some_plugins_updatable()); // We have available update for mod_foo.
210     }
212     public function test_is_standard() {
213         $pluginman = testable_plugin_manager::instance();
214         $this->assertTrue($pluginman->get_plugin_info('mod_bar')->is_standard());
215         $this->assertFalse($pluginman->get_plugin_info('mod_foo')->is_standard());
216         $this->assertFalse($pluginman->get_plugin_info('foolish_frog')->is_standard());
217     }
219     public function test_get_status() {
220         $pluginman = testable_plugin_manager::instance();
221         $plugins = $pluginman->get_plugins();
222         $this->assertEquals(plugin_manager::PLUGIN_STATUS_UPGRADE, $plugins['mod']['foo']->get_status());
223         $this->assertEquals(plugin_manager::PLUGIN_STATUS_NEW, $plugins['mod']['new']->get_status());
224         $this->assertEquals(plugin_manager::PLUGIN_STATUS_NEW, $plugins['bazmeg']['one']->get_status());
225         $this->assertEquals(plugin_manager::PLUGIN_STATUS_UPTODATE, $plugins['quxcat']['one']->get_status());
226     }
228     public function test_available_update() {
229         $pluginman = testable_plugin_manager::instance();
230         $plugins = $pluginman->get_plugins();
231         $this->assertNull($plugins['mod']['bar']->available_updates());
232         $this->assertInternalType('array', $plugins['mod']['foo']->available_updates());
233         foreach ($plugins['mod']['foo']->available_updates() as $availableupdate) {
234             $this->assertInstanceOf('available_update_info', $availableupdate);
235         }
236     }
238     public function test_can_uninstall_plugin() {
239         $pluginman = testable_plugin_manager::instance();
240         $this->assertFalse($pluginman->can_uninstall_plugin('mod_missing'));
241         $this->assertTrue($pluginman->can_uninstall_plugin('mod_foo')); // Because mod_foo is required by foolish_frog only
242                                                                         // and foolish_frog is required by mod_foo and foolish_hippo only.
243         $this->assertFalse($pluginman->can_uninstall_plugin('mod_bar')); // Because mod_bar is required by mod_foo.
244         $this->assertFalse($pluginman->can_uninstall_plugin('mod_qux')); // Because even if no plugin (not even subplugins) declare
245                                                                          // dependency on it, but its subplugin can't be uninstalled.
246         $this->assertFalse($pluginman->can_uninstall_plugin('mod_baz')); // Because it's subplugin bazmeg_one is required by quxcat_one.
247         $this->assertFalse($pluginman->can_uninstall_plugin('mod_new')); // Because it is not installed.
248         $this->assertFalse($pluginman->can_uninstall_plugin('quxcat_one')); // Because of testable_pluginfo_quxcat::is_uninstall_allowed().
249         $this->assertFalse($pluginman->can_uninstall_plugin('foolish_frog')); // Because foolish_hippo requires it.
250     }
252     public function test_get_uninstall_url() {
253         $pluginman = testable_plugin_manager::instance();
254         foreach ($pluginman->get_plugins() as $plugintype => $plugininfos) {
255             foreach ($plugininfos as $plugininfo) {
256                 $this->assertInstanceOf('moodle_url', $plugininfo->get_uninstall_url());
257             }
258         }
259     }
263 /**
264  * Tests of the basic API of the available update checker.
265  *
266  * @group core_plugin
267  */
268 class core_available_update_checker_testcase extends advanced_testcase {
270     public function test_core_available_update() {
271         $provider = testable_available_update_checker::instance();
272         $this->assertInstanceOf('available_update_checker', $provider);
274         $provider->fake_current_environment(2012060102.00, '2.3.2 (Build: 20121012)', '2.3', array());
275         $updates = $provider->get_update_info('core');
276         $this->assertCount(2, $updates);
278         $provider->fake_current_environment(2012060103.00, '2.3.3 (Build: 20121212)', '2.3', array());
279         $updates = $provider->get_update_info('core');
280         $this->assertCount(1, $updates);
282         $provider->fake_current_environment(2012060103.00, '2.3.3 (Build: 20121212)', '2.3', array());
283         $updates = $provider->get_update_info('core', array('minmaturity' => MATURITY_STABLE));
284         $this->assertNull($updates);
285     }
287     /**
288      * If there are no fetched data yet, the first cron should fetch them.
289      */
290     public function test_cron_initial_fetch() {
291         $provider = testable_available_update_checker::instance();
292         $provider->fakerecentfetch = null;
293         $provider->fakecurrenttimestamp = -1;
294         $this->setExpectedException('testable_available_update_checker_cron_executed');
295         $provider->cron();
296     }
298     /**
299      * If there is a fresh fetch available, no cron execution is expected.
300      */
301     public function test_cron_has_fresh_fetch() {
302         $provider = testable_available_update_checker::instance();
303         $provider->fakerecentfetch = time() - 23 * HOURSECS; // Fetched 23 hours ago.
304         $provider->fakecurrenttimestamp = -1;
305         $provider->cron();
306         $this->assertTrue(true); // We should get here with no exception thrown.
307     }
309     /**
310      * If there is an outdated fetch, the cron execution is expected.
311      */
312     public function test_cron_has_outdated_fetch() {
313         $provider = testable_available_update_checker::instance();
314         $provider->fakerecentfetch = time() - 49 * HOURSECS; // Fetched 49 hours ago.
315         $provider->fakecurrenttimestamp = -1;
316         $this->setExpectedException('testable_available_update_checker_cron_executed');
317         $provider->cron();
318     }
320     /**
321      * The first cron after 01:42 AM today should fetch the data.
322      *
323      * @see testable_available_update_checker::cron_execution_offset()
324      */
325     public function test_cron_offset_execution_not_yet() {
326         $provider = testable_available_update_checker::instance();
327         $provider->fakecurrenttimestamp = mktime(1, 40, 02); // 01:40:02 AM today
328         $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS;
329         $provider->cron();
330         $this->assertTrue(true); // We should get here with no exception thrown.
331     }
333     /**
334      * The first cron after 01:42 AM today should fetch the data and then
335      * it is supposed to wait next 24 hours.
336      *
337      * @see testable_available_update_checker::cron_execution_offset()
338      */
339     public function test_cron_offset_execution() {
340         $provider = testable_available_update_checker::instance();
342         // The cron at 01:45 should fetch the data.
343         $provider->fakecurrenttimestamp = mktime(1, 45, 02); // 01:45:02 AM today
344         $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS - 1;
345         $executed = false;
346         try {
347             $provider->cron();
348         } catch (testable_available_update_checker_cron_executed $e) {
349             $executed = true;
350         }
351         $this->assertTrue($executed, 'Cron should be executed at 01:45:02 but it was not.');
353         // Another cron at 06:45 should still consider data as fresh enough.
354         $provider->fakerecentfetch = $provider->fakecurrenttimestamp;
355         $provider->fakecurrenttimestamp = mktime(6, 45, 03); // 06:45:03 AM
356         $executed = false;
357         try {
358             $provider->cron();
359         } catch (testable_available_update_checker_cron_executed $e) {
360             $executed = true;
361         }
362         $this->assertFalse($executed, 'Cron should not be executed at 06:45:03 but it was.');
364         // The next scheduled execution should happen the next day.
365         $provider->fakecurrenttimestamp = $provider->fakerecentfetch + 24 * HOURSECS + 1;
366         $executed = false;
367         try {
368             $provider->cron();
369         } catch (testable_available_update_checker_cron_executed $e) {
370             $executed = true;
371         }
372         $this->assertTrue($executed, 'Cron should be executed the next night but it was not.');
373     }
375     public function test_compare_responses_both_empty() {
376         $provider = testable_available_update_checker::instance();
377         $old = array();
378         $new = array();
379         $cmp = $provider->compare_responses($old, $new);
380         $this->assertInternalType('array', $cmp);
381         $this->assertEmpty($cmp);
382     }
384     public function test_compare_responses_old_empty() {
385         $provider = testable_available_update_checker::instance();
386         $old = array();
387         $new = array(
388             'updates' => array(
389                 'core' => array(
390                     array(
391                         'version' => 2012060103
392                     )
393                 )
394             )
395         );
396         $cmp = $provider->compare_responses($old, $new);
397         $this->assertInternalType('array', $cmp);
398         $this->assertNotEmpty($cmp);
399         $this->assertTrue(isset($cmp['core'][0]['version']));
400         $this->assertEquals(2012060103, $cmp['core'][0]['version']);
401     }
403     public function test_compare_responses_no_change() {
404         $provider = testable_available_update_checker::instance();
405         $old = $new = array(
406             'updates' => array(
407                 'core' => array(
408                     array(
409                         'version' => 2012060104
410                     ),
411                     array(
412                         'version' => 2012120100
413                     )
414                 ),
415                 'mod_foo' => array(
416                     array(
417                         'version' => 2011010101
418                     )
419                 )
420             )
421         );
422         $cmp = $provider->compare_responses($old, $new);
423         $this->assertInternalType('array', $cmp);
424         $this->assertEmpty($cmp);
425     }
427     public function test_compare_responses_new_and_missing_update() {
428         $provider = testable_available_update_checker::instance();
429         $old = array(
430             'updates' => array(
431                 'core' => array(
432                     array(
433                         'version' => 2012060104
434                     )
435                 ),
436                 'mod_foo' => array(
437                     array(
438                         'version' => 2011010101
439                     )
440                 )
441             )
442         );
443         $new = array(
444             'updates' => array(
445                 'core' => array(
446                     array(
447                         'version' => 2012060104
448                     ),
449                     array(
450                         'version' => 2012120100
451                     )
452                 )
453             )
454         );
455         $cmp = $provider->compare_responses($old, $new);
456         $this->assertInternalType('array', $cmp);
457         $this->assertNotEmpty($cmp);
458         $this->assertCount(1, $cmp);
459         $this->assertCount(1, $cmp['core']);
460         $this->assertEquals(2012120100, $cmp['core'][0]['version']);
461     }
463     public function test_compare_responses_modified_update() {
464         $provider = testable_available_update_checker::instance();
465         $old = array(
466             'updates' => array(
467                 'mod_foo' => array(
468                     array(
469                         'version' => 2011010101
470                     )
471                 )
472             )
473         );
474         $new = array(
475             'updates' => array(
476                 'mod_foo' => array(
477                     array(
478                         'version' => 2011010102
479                     )
480                 )
481             )
482         );
483         $cmp = $provider->compare_responses($old, $new);
484         $this->assertInternalType('array', $cmp);
485         $this->assertNotEmpty($cmp);
486         $this->assertCount(1, $cmp);
487         $this->assertCount(1, $cmp['mod_foo']);
488         $this->assertEquals(2011010102, $cmp['mod_foo'][0]['version']);
489     }
491     public function test_compare_responses_invalid_format() {
492         $provider = testable_available_update_checker::instance();
493         $broken = array(
494             'status' => 'ERROR' // No 'updates' key here.
495         );
496         $this->setExpectedException('available_update_checker_exception');
497         $cmp = $provider->compare_responses($broken, $broken);
498     }
500     public function test_is_same_release_explicit() {
501         $provider = testable_available_update_checker::instance();
502         $this->assertTrue($provider->is_same_release('2.3dev (Build: 20120323)', '2.3dev (Build: 20120323)'));
503         $this->assertTrue($provider->is_same_release('2.3dev (Build: 20120323)', '2.3dev (Build: 20120330)'));
504         $this->assertFalse($provider->is_same_release('2.3dev (Build: 20120529)', '2.3 (Build: 20120601)'));
505         $this->assertFalse($provider->is_same_release('2.3dev', '2.3 dev'));
506         $this->assertFalse($provider->is_same_release('2.3.1', '2.3'));
507         $this->assertFalse($provider->is_same_release('2.3.1', '2.3.2'));
508         $this->assertTrue($provider->is_same_release('2.3.2+', '2.3.2')); // Yes, really!
509         $this->assertTrue($provider->is_same_release('2.3.2 (Build: 123456)', '2.3.2+ (Build: 123457)'));
510         $this->assertFalse($provider->is_same_release('3.0 Community Edition', '3.0 Enterprise Edition'));
511         $this->assertTrue($provider->is_same_release('3.0 Community Edition', '3.0 Community Edition (Build: 20290101)'));
512     }
514     public function test_is_same_release_implicit() {
515         $provider = testable_available_update_checker::instance();
516         $provider->fake_current_environment(2012060102.00, '2.3.2 (Build: 20121012)', '2.3', array());
517         $this->assertTrue($provider->is_same_release('2.3.2'));
518         $this->assertTrue($provider->is_same_release('2.3.2+'));
519         $this->assertTrue($provider->is_same_release('2.3.2+ (Build: 20121013)'));
520         $this->assertFalse($provider->is_same_release('2.4dev (Build: 20121012)'));
521     }
525 /**
526  * Base class for testable plugininfo classes.
527  */
528 class testable_plugininfo_base extends plugininfo_base {
530     protected function get_plugin_manager() {
531         return testable_plugin_manager::instance();
532     }
536 /**
537  * Modified {@link plugininfo_mod} suitable for testing purposes.
538  */
539 class testable_plugininfo_mod extends plugininfo_mod {
541     public function init_display_name() {
542         $this->displayname = ucfirst($this->name);
543     }
545     public function is_standard() {
546         if ($this->component === 'mod_foo') {
547             return false;
548         } else {
549             return true;
550         }
551     }
553     public function load_db_version() {
554         if ($this->component !== 'mod_new') {
555             $this->versiondb = 2012022900;
556         }
557     }
559     public function is_uninstall_allowed() {
560         return true; // Allow uninstall for standard plugins too.
561     }
563     protected function get_plugin_manager() {
564         return testable_plugin_manager::instance();
565     }
569 /**
570  * Testable class representing subplugins of testable mod_foo.
571  */
572 class testable_pluginfo_foolish extends testable_plugininfo_base {
574     public function init_display_name() {
575         $this->displayname = ucfirst($this->name);
576     }
578     public function is_standard() {
579         return false;
580     }
582     public function load_db_version() {
583         $this->versiondb = 2012022900;
584     }
588 /**
589  * Testable class representing subplugins of testable mod_baz.
590  */
591 class testable_pluginfo_bazmeg extends testable_plugininfo_base {
593     public function init_display_name() {
594         $this->displayname = ucfirst($this->name);
595     }
597     public function is_standard() {
598         return false;
599     }
601     public function load_db_version() {
602         $this->versiondb = null;
603     }
607 /**
608  * Testable class representing subplugins of testable mod_qux.
609  */
610 class testable_pluginfo_quxcat extends testable_plugininfo_base {
612     public function init_display_name() {
613         $this->displayname = ucfirst($this->name);
614     }
616     public function is_standard() {
617         return false;
618     }
620     public function load_db_version() {
621         $this->versiondb = 2013041103;
622     }
624     public function is_uninstall_allowed() {
625         return false;
626     }
630 /**
631  * Modified {@link plugin_manager} suitable for testing purposes
632  */
633 class testable_plugin_manager extends plugin_manager {
635     /**
636      * Factory method for this class
637      *
638      * @return plugin_manager the singleton instance
639      */
640     public static function instance() {
641         global $CFG;
643         if (is_null(self::$singletoninstance)) {
644             self::$singletoninstance = new self();
645         }
646         return self::$singletoninstance;
647     }
649     /**
650      * A version of {@link plugin_manager::get_plugins()} that prepares some faked
651      * testable instances.
652      *
653      * @param bool $disablecache ignored in this class
654      * @return array
655      */
656     public function get_plugins($disablecache = false) {
658         $dirroot = dirname(__FILE__).'/fixtures/mockplugins';
660         $this->pluginsinfo = array(
661             'mod' => array(
662                 'foo' => plugininfo_default_factory::make('mod', $dirroot.'/mod', 'foo',
663                     $dirroot.'/mod/foo', 'testable_plugininfo_mod'),
664                 'bar' => plugininfo_default_factory::make('mod', $dirroot.'/bar', 'bar',
665                     $dirroot.'/mod/bar', 'testable_plugininfo_mod'),
666                 'baz' => plugininfo_default_factory::make('mod', $dirroot.'/baz', 'baz',
667                     $dirroot.'/mod/baz', 'testable_plugininfo_mod'),
668                 'qux' => plugininfo_default_factory::make('mod', $dirroot.'/qux', 'qux',
669                     $dirroot.'/mod/qux', 'testable_plugininfo_mod'),
670                 'new' => plugininfo_default_factory::make('mod', $dirroot.'/new', 'new',
671                     $dirroot.'/mod/new', 'testable_plugininfo_mod'),
672             ),
673             'foolish' => array(
674                 'frog' => plugininfo_default_factory::make('foolish', $dirroot.'/mod/foo/lish', 'frog',
675                     $dirroot.'/mod/foo/lish/frog', 'testable_pluginfo_foolish'),
676                 'hippo' => plugininfo_default_factory::make('foolish', $dirroot.'/mod/foo/lish', 'hippo',
677                     $dirroot.'/mod/foo/lish/hippo', 'testable_pluginfo_foolish'),
678             ),
679             'bazmeg' => array(
680                 'one' => plugininfo_default_factory::make('bazmeg', $dirroot.'/mod/baz/meg', 'one',
681                     $dirroot.'/mod/baz/meg/one', 'testable_pluginfo_bazmeg'),
682             ),
683             'quxcat' => array(
684                 'one' => plugininfo_default_factory::make('quxcat', $dirroot.'/mod/qux/cat', 'one',
685                     $dirroot.'/mod/qux/cat/one', 'testable_pluginfo_quxcat'),
686             ),
687         );
689         $checker = testable_available_update_checker::instance();
690         $this->pluginsinfo['mod']['foo']->check_available_updates($checker);
691         $this->pluginsinfo['mod']['bar']->check_available_updates($checker);
692         $this->pluginsinfo['mod']['baz']->check_available_updates($checker);
693         $this->pluginsinfo['mod']['new']->check_available_updates($checker);
694         $this->pluginsinfo['bazmeg']['one']->check_available_updates($checker);
695         $this->pluginsinfo['quxcat']['one']->check_available_updates($checker);
697         return $this->pluginsinfo;
698     }
700     /**
701      * Testable version of {@link plugin_manager::get_subplugins()} that works with
702      * the simulated environment.
703      *
704      * In this case, the mod_foo fake module provides subplugins of type 'foolish',
705      * mod_baz provides subplugins of type 'bazmeg' and mod_qux has 'quxcat'.
706      *
707      * @param bool $disablecache ignored in this class
708      * @return array
709      */
710     public function get_subplugins($disablecache = false) {
712         $this->subpluginsinfo = array(
713             'mod_foo' => array(
714                 'foolish' => (object)array(
715                     'type' => 'foolish',
716                     'typerootdir' => 'mod/foo/lish',
717                 ),
718             ),
719             'mod_baz' => array(
720                 'bazmeg' => (object)array(
721                     'type' => 'bazmeg',
722                     'typerootdir' => 'mod/baz/meg',
723                 ),
724             ),
725             'mod_qux' => array(
726                 'quxcat' => (object)array(
727                     'type' => 'quxcat',
728                     'typerootdir' => 'mod/qux/cat',
729                 ),
730             ),
731         );
733         return $this->subpluginsinfo;
734     }
736     /**
737      * Adds support for mock plugin types.
738      */
739     protected function normalize_component($component) {
741         // List of mock plugin types used in these unit tests.
742         $faketypes = array('foolish', 'bazmeg', 'quxcat');
744         foreach ($faketypes as $faketype) {
745             if (strpos($component, $faketype.'_') === 0) {
746                 return explode('_', $component, 2);
747             }
748         }
750         return parent::normalize_component($component);
751     }
753     public function plugintype_name($type) {
754         return ucfirst($type);
755     }
757     public function plugintype_name_plural($type) {
758         return ucfirst($type).'s'; // Simple, isn't it? ;-).
759     }
761     public function plugin_external_source($component) {
762         if ($component === 'foolish_frog') {
763             return true;
764         }
765         return false;
766     }
770 /**
771  * Modified version of {@link available_update_checker} suitable for testing.
772  */
773 class testable_available_update_checker extends available_update_checker {
775     /** @var replaces the default DB table storage for the fetched response */
776     protected $fakeresponsestorage;
777     /** @var int stores the fake recentfetch value */
778     public $fakerecentfetch = -1;
779     /** @var int stores the fake value of time() */
780     public $fakecurrenttimestamp = -1;
782     /**
783      * Factory method for this class.
784      *
785      * @return testable_available_update_checker the singleton instance
786      */
787     public static function instance() {
788         global $CFG;
790         if (is_null(self::$singletoninstance)) {
791             self::$singletoninstance = new self();
792         }
793         return self::$singletoninstance;
794     }
796     protected function validate_response($response) {
797     }
799     protected function store_response($response) {
800         $this->fakeresponsestorage = $response;
801     }
803     protected function restore_response($forcereload = false) {
804         $this->recentfetch = time();
805         $this->recentresponse = $this->decode_response($this->get_fake_response());
806     }
808     public function compare_responses(array $old, array $new) {
809         return parent::compare_responses($old, $new);
810     }
812     public function is_same_release($remote, $local=null) {
813         return parent::is_same_release($remote, $local);
814     }
816     protected function load_current_environment($forcereload=false) {
817     }
819     public function fake_current_environment($version, $release, $branch, array $plugins) {
820         $this->currentversion = $version;
821         $this->currentrelease = $release;
822         $this->currentbranch = $branch;
823         $this->currentplugins = $plugins;
824     }
826     public function get_last_timefetched() {
827         if ($this->fakerecentfetch == -1) {
828             return parent::get_last_timefetched();
829         } else {
830             return $this->fakerecentfetch;
831         }
832     }
834     private function get_fake_response() {
835         $fakeresponse = array(
836             'status' => 'OK',
837             'provider' => 'http://download.moodle.org/api/1.0/updates.php',
838             'apiver' => '1.0',
839             'timegenerated' => time(),
840             'forversion' => '2012010100.00',
841             'forbranch' => '2.3',
842             'ticket' => sha1('No, I am not going to mention the word "frog" here. Oh crap. I just did.'),
843             'updates' => array(
844                 'core' => array(
845                     array(
846                         'version' => 2012060103.00,
847                         'release' => '2.3.3 (Build: 20121201)',
848                         'maturity' => 200,
849                         'url' => 'http://download.moodle.org/',
850                         'download' => 'http://download.moodle.org/download.php/MOODLE_23_STABLE/moodle-2.3.3-latest.zip',
851                     ),
852                     array(
853                         'version' => 2012120100.00,
854                         'release' => '2.4dev (Build: 20121201)',
855                         'maturity' => 50,
856                         'url' => 'http://download.moodle.org/',
857                         'download' => 'http://download.moodle.org/download.php/MOODLE_24_STABLE/moodle-2.4.0-latest.zip',
858                     ),
859                 ),
860                 'mod_foo' => array(
861                     array(
862                         'version' => 2012030501,
863                         'requires' => 2012010100,
864                         'maturity' => 200,
865                         'release' => '1.1',
866                         'url' => 'http://moodle.org/plugins/blahblahblah/',
867                         'download' => 'http://moodle.org/plugins/download.php/blahblahblah',
868                     ),
869                     array(
870                         'version' => 2012030502,
871                         'requires' => 2012010100,
872                         'maturity' => 100,
873                         'release' => '1.2 beta',
874                         'url' => 'http://moodle.org/plugins/',
875                     ),
876                 ),
877             ),
878         );
880         return json_encode($fakeresponse);
881     }
883     protected function cron_current_timestamp() {
884         if ($this->fakecurrenttimestamp == -1) {
885             return parent::cron_current_timestamp();
886         } else {
887             return $this->fakecurrenttimestamp;
888         }
889     }
891     protected function cron_mtrace($msg, $eol = PHP_EOL) {
892     }
894     protected function cron_autocheck_enabled() {
895         return true;
896     }
898     protected function cron_execution_offset() {
899         // Autofetch should run by the first cron after 01:42 AM.
900         return 42 * MINSECS;
901     }
903     protected function cron_execute() {
904         throw new testable_available_update_checker_cron_executed('Cron executed!');
905     }
909 /**
910  * Exception used to detect {@link available_update_checker::cron_execute()} calls.
911  */
912 class testable_available_update_checker_cron_executed extends Exception {
916 /**
917  * Modified {@link available_update_deployer} suitable for testing purposes.
918  */
919 class testable_available_update_deployer extends available_update_deployer {
923 /**
924  * Test cases for {@link available_update_deployer} class.
925  *
926  * @group core_plugin
927  */
928 class core_available_update_deployer_testcase extends advanced_testcase {
930     public function test_magic_setters() {
931         $deployer = testable_available_update_deployer::instance();
932         $value = new moodle_url('/');
933         $deployer->set_returnurl($value);
934         $this->assertSame($deployer->get_returnurl(), $value);
935     }
937     public function test_prepare_authorization() {
938         global $CFG;
940         $deployer = testable_available_update_deployer::instance();
941         list($passfile, $password) = $deployer->prepare_authorization();
942         $filename = $CFG->phpunit_dataroot.'/mdeploy/auth/'.$passfile;
943         $this->assertFileExists($filename);
944         $stored = file($filename, FILE_IGNORE_NEW_LINES);
945         $this->assertCount(2, $stored);
946         $this->assertGreaterThan(23, strlen($stored[0]));
947         $this->assertSame($stored[0], $password);
948         $this->assertLessThan(60, time() - (int)$stored[1]);
949     }