Merge branch 'MDL-52333_master' of git://github.com/dmonllao/moodle
[moodle.git] / backup / util / settings / tests / settings_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  * @package   core_backup
19  * @category  phpunit
20  * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 defined('MOODLE_INTERNAL') || die();
26 // Include all the needed stuff
27 global $CFG;
28 require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
29 require_once($CFG->dirroot . '/backup/backup.class.php');
30 require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
31 require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
32 require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
33 require_once($CFG->dirroot . '/backup/util/settings/root/root_backup_setting.class.php');
34 require_once($CFG->dirroot . '/backup/util/settings/activity/activity_backup_setting.class.php');
35 require_once($CFG->dirroot . '/backup/util/settings/section/section_backup_setting.class.php');
36 require_once($CFG->dirroot . '/backup/util/settings/course/course_backup_setting.class.php');
37 require_once($CFG->dirroot . '/backup/util/ui/backup_ui_setting.class.php');
40 /**
41  * setting tests (all)
42  */
43 class backp_settings_testcase extends basic_testcase {
45     /**
46      * test base_setting class
47      */
48     function test_base_setting() {
49         // Instantiate base_setting and check everything
50         $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
51         $this->assertTrue($bs instanceof base_setting);
52         $this->assertEquals($bs->get_name(), 'test');
53         $this->assertEquals($bs->get_vtype(), base_setting::IS_BOOLEAN);
54         $this->assertTrue(is_null($bs->get_value()));
55         $this->assertEquals($bs->get_visibility(), base_setting::VISIBLE);
56         $this->assertEquals($bs->get_status(), base_setting::NOT_LOCKED);
58         // Instantiate base_setting with explicit nulls
59         $bs = new mock_base_setting('test', base_setting::IS_FILENAME, 'filename.txt', null, null);
60         $this->assertEquals($bs->get_value() , 'filename.txt');
61         $this->assertEquals($bs->get_visibility(), base_setting::VISIBLE);
62         $this->assertEquals($bs->get_status(), base_setting::NOT_LOCKED);
64         // Instantiate base_setting and set value, visibility and status
65         $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
66         $bs->set_value(true);
67         $this->assertNotEmpty($bs->get_value());
68         $bs->set_visibility(base_setting::HIDDEN);
69         $this->assertEquals($bs->get_visibility(), base_setting::HIDDEN);
70         $bs->set_status(base_setting::LOCKED_BY_HIERARCHY);
71         $this->assertEquals($bs->get_status(), base_setting::LOCKED_BY_HIERARCHY);
73         // Instantiate with wrong vtype
74         try {
75             $bs = new mock_base_setting('test', 'one_wrong_type');
76             $this->assertTrue(false, 'base_setting_exception expected');
77         } catch (exception $e) {
78             $this->assertTrue($e instanceof base_setting_exception);
79             $this->assertEquals($e->errorcode, 'setting_invalid_type');
80         }
82         // Instantiate with wrong integer value
83         try {
84             $bs = new mock_base_setting('test', base_setting::IS_INTEGER, 99.99);
85             $this->assertTrue(false, 'base_setting_exception expected');
86         } catch (exception $e) {
87             $this->assertTrue($e instanceof base_setting_exception);
88             $this->assertEquals($e->errorcode, 'setting_invalid_integer');
89         }
91         // Instantiate with wrong filename value
92         try {
93             $bs = new mock_base_setting('test', base_setting::IS_FILENAME, '../../filename.txt');
94             $this->assertTrue(false, 'base_setting_exception expected');
95         } catch (exception $e) {
96             $this->assertTrue($e instanceof base_setting_exception);
97             $this->assertEquals($e->errorcode, 'setting_invalid_filename');
98         }
100         // Instantiate with wrong visibility
101         try {
102             $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, 'one_wrong_visibility');
103             $this->assertTrue(false, 'base_setting_exception expected');
104         } catch (exception $e) {
105             $this->assertTrue($e instanceof base_setting_exception);
106             $this->assertEquals($e->errorcode, 'setting_invalid_visibility');
107         }
109         // Instantiate with wrong status
110         try {
111             $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, 'one_wrong_status');
112             $this->assertTrue(false, 'base_setting_exception expected');
113         } catch (exception $e) {
114             $this->assertTrue($e instanceof base_setting_exception);
115             $this->assertEquals($e->errorcode, 'setting_invalid_status');
116         }
118         // Instantiate base_setting and try to set wrong ui_type
119         // We need a custom error handler to catch the type hinting error
120         // that should return incorrect_object_passed
121         $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
122         set_error_handler('backup_setting_error_handler', E_RECOVERABLE_ERROR);
123         try {
124             $bs->set_ui('one_wrong_ui_type', 'label', array(), array());
125             $this->assertTrue(false, 'base_setting_exception expected');
126         } catch (exception $e) {
127             $this->assertTrue($e instanceof base_setting_exception);
128             $this->assertEquals($e->errorcode, 'incorrect_object_passed');
129         }
130         restore_error_handler();
132         // Instantiate base_setting and try to set wrong ui_label
133         // We need a custom error handler to catch the type hinting error
134         // that should return incorrect_object_passed
135         $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
136         set_error_handler('backup_setting_error_handler', E_RECOVERABLE_ERROR);
137         try {
138             $bs->set_ui(base_setting::UI_HTML_CHECKBOX, 'one/wrong/label', array(), array());
139             $this->assertTrue(false, 'base_setting_exception expected');
140         } catch (exception $e) {
141             $this->assertTrue($e instanceof base_setting_exception);
142             $this->assertEquals($e->errorcode, 'incorrect_object_passed');
143         }
144         restore_error_handler();
146         // Try to change value of locked setting by permission
147         $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, base_setting::LOCKED_BY_PERMISSION);
148         try {
149             $bs->set_value(true);
150             $this->assertTrue(false, 'base_setting_exception expected');
151         } catch (exception $e) {
152             $this->assertTrue($e instanceof base_setting_exception);
153             $this->assertEquals($e->errorcode, 'setting_locked_by_permission');
154         }
156         // Try to change value of locked setting by config
157         $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, base_setting::LOCKED_BY_CONFIG);
158         try {
159             $bs->set_value(true);
160             $this->assertTrue(false, 'base_setting_exception expected');
161         } catch (exception $e) {
162             $this->assertTrue($e instanceof base_setting_exception);
163             $this->assertEquals($e->errorcode, 'setting_locked_by_config');
164         }
166         // Try to add same setting twice
167         $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
168         $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
169         $bs1->add_dependency($bs2, null, array('value'=>0));
170         try {
171             $bs1->add_dependency($bs2);
172             $this->assertTrue(false, 'base_setting_exception expected');
173         } catch (exception $e) {
174             $this->assertTrue($e instanceof base_setting_exception);
175             $this->assertEquals($e->errorcode, 'setting_already_added');
176         }
178         // Try to create one circular reference
179         $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
180         try {
181             $bs1->add_dependency($bs1); // self
182             $this->assertTrue(false, 'base_setting_exception expected');
183         } catch (exception $e) {
184             $this->assertTrue($e instanceof base_setting_exception);
185             $this->assertEquals($e->errorcode, 'setting_circular_reference');
186             $this->assertTrue($e->a instanceof stdclass);
187             $this->assertEquals($e->a->main, 'test1');
188             $this->assertEquals($e->a->alreadydependent, 'test1');
189         }
191         $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
192         $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
193         $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
194         $bs4 = new mock_base_setting('test4', base_setting::IS_INTEGER, null);
195         $bs1->add_dependency($bs2, null, array('value'=>0));
196         $bs2->add_dependency($bs3, null, array('value'=>0));
197         $bs3->add_dependency($bs4, null, array('value'=>0));
198         try {
199             $bs4->add_dependency($bs1, null, array('value'=>0));
200             $this->assertTrue(false, 'base_setting_exception expected');
201         } catch (exception $e) {
202             $this->assertTrue($e instanceof base_setting_exception);
203             $this->assertEquals($e->errorcode, 'setting_circular_reference');
204             $this->assertTrue($e->a instanceof stdclass);
205             $this->assertEquals($e->a->main, 'test1');
206             $this->assertEquals($e->a->alreadydependent, 'test4');
207         }
209         $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
210         $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
211         $bs1->register_dependency(new setting_dependency_disabledif_empty($bs1, $bs2));
212         try {
213             // $bs1 is already dependent on $bs2 so this should fail.
214             $bs2->register_dependency(new setting_dependency_disabledif_empty($bs2, $bs1));
215             $this->assertTrue(false, 'base_setting_exception expected');
216         } catch (exception $e) {
217             $this->assertTrue($e instanceof base_setting_exception);
218             $this->assertEquals($e->errorcode, 'setting_circular_reference');
219             $this->assertTrue($e->a instanceof stdclass);
220             $this->assertEquals($e->a->main, 'test1');
221             $this->assertEquals($e->a->alreadydependent, 'test2');
222         }
224         // Create 3 settings and observe between them, last one must
225         // automatically inherit all the settings defined in the main one
226         $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
227         $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
228         $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
229         $bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
230         $bs2->add_dependency($bs3, setting_dependency::DISABLED_NOT_EMPTY);
231         // Check values are spreaded ok
232         $bs1->set_value(123);
233         $this->assertEquals($bs1->get_value(), 123);
234         $this->assertEquals($bs2->get_value(), $bs1->get_value());
235         $this->assertEquals($bs3->get_value(), $bs1->get_value());
237         // Add one more setting and set value again
238         $bs4 = new mock_base_setting('test4', base_setting::IS_INTEGER, null);
239         $bs2->add_dependency($bs4, setting_dependency::DISABLED_NOT_EMPTY);
240         $bs2->set_value(321);
241         // The above change should change
242         $this->assertEquals($bs1->get_value(), 123);
243         $this->assertEquals($bs2->get_value(), 321);
244         $this->assertEquals($bs3->get_value(), 321);
245         $this->assertEquals($bs4->get_value(), 321);
247         // Check visibility is spreaded ok
248         $bs1->set_visibility(base_setting::HIDDEN);
249         $this->assertEquals($bs2->get_visibility(), $bs1->get_visibility());
250         $this->assertEquals($bs3->get_visibility(), $bs1->get_visibility());
251         // Check status is spreaded ok
252         $bs1->set_status(base_setting::LOCKED_BY_HIERARCHY);
253         $this->assertEquals($bs2->get_status(), $bs1->get_status());
254         $this->assertEquals($bs3->get_status(), $bs1->get_status());
256         // Create 3 settings and observe between them, put them in one array,
257         // force serialize/deserialize to check the observable pattern continues
258         // working after that
259         $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
260         $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
261         $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
262         $bs1->add_dependency($bs2, null, array('value'=>0));
263         $bs2->add_dependency($bs3, null, array('value'=>0));
264         // Serialize
265         $arr = array($bs1, $bs2, $bs3);
266         $ser = base64_encode(serialize($arr));
267         // Unserialize and copy to new objects
268         $newarr = unserialize(base64_decode($ser));
269         $ubs1 = $newarr[0];
270         $ubs2 = $newarr[1];
271         $ubs3 = $newarr[2];
272         // Must continue being base settings
273         $this->assertTrue($ubs1 instanceof base_setting);
274         $this->assertTrue($ubs2 instanceof base_setting);
275         $this->assertTrue($ubs3 instanceof base_setting);
276         // Set parent setting
277         $ubs1->set_value(1234);
278         $ubs1->set_visibility(base_setting::HIDDEN);
279         $ubs1->set_status(base_setting::LOCKED_BY_HIERARCHY);
280         // Check changes have been spreaded
281         $this->assertEquals($ubs2->get_visibility(), $ubs1->get_visibility());
282         $this->assertEquals($ubs3->get_visibility(), $ubs1->get_visibility());
283         $this->assertEquals($ubs2->get_status(), $ubs1->get_status());
284         $this->assertEquals($ubs3->get_status(), $ubs1->get_status());
285     }
287     /**
288      * test backup_setting class
289      */
290     function test_backup_setting() {
291         // Instantiate backup_setting class and set level
292         $bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
293         $bs->set_level(1);
294         $this->assertEquals($bs->get_level(), 1);
296         // Instantiate backup setting class and try to add one non backup_setting dependency
297         set_error_handler('backup_setting_error_handler', E_RECOVERABLE_ERROR);
298         $bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
299         try {
300             $bs->add_dependency(new stdclass());
301             $this->assertTrue(false, 'backup_setting_exception expected');
302         } catch (exception $e) {
303             $this->assertTrue($e instanceof backup_setting_exception);
304             $this->assertEquals($e->errorcode, 'incorrect_object_passed');
305         }
306         restore_error_handler();
308         // Try to assing upper level dependency
309         $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, null);
310         $bs1->set_level(1);
311         $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, null);
312         $bs2->set_level(2);
313         try {
314             $bs2->add_dependency($bs1);
315             $this->assertTrue(false, 'backup_setting_exception expected');
316         } catch (exception $e) {
317             $this->assertTrue($e instanceof backup_setting_exception);
318             $this->assertEquals($e->errorcode, 'cannot_add_upper_level_dependency');
319         }
321         // Check dependencies are working ok
322         $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, null);
323         $bs1->set_level(1);
324         $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, null);
325         $bs2->set_level(1); // Same level *must* work
326         $bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
327         $bs1->set_value(123456);
328         $this->assertEquals($bs2->get_value(), $bs1->get_value());
329     }
331     /**
332      * test activity_backup_setting class
333      */
334     function test_activity_backup_setting() {
335         $bs = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
336         $this->assertEquals($bs->get_level(), backup_setting::ACTIVITY_LEVEL);
338         // Check checksum implementation is working
339         $bs1 = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
340         $bs1->set_value(123);
341         $checksum = $bs1->calculate_checksum();
342         $this->assertNotEmpty($checksum);
343         $this->assertTrue($bs1->is_checksum_correct($checksum));
344     }
346     /**
347      * test section_backup_setting class
348      */
349     function test_section_backup_setting() {
350         $bs = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
351         $this->assertEquals($bs->get_level(), backup_setting::SECTION_LEVEL);
353         // Check checksum implementation is working
354         $bs1 = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
355         $bs1->set_value(123);
356         $checksum = $bs1->calculate_checksum();
357         $this->assertNotEmpty($checksum);
358         $this->assertTrue($bs1->is_checksum_correct($checksum));
359     }
361     /**
362      * test course_backup_setting class
363      */
364     function test_course_backup_setting() {
365         $bs = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
366         $this->assertEquals($bs->get_level(), backup_setting::COURSE_LEVEL);
368         // Check checksum implementation is working
369         $bs1 = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
370         $bs1->set_value(123);
371         $checksum = $bs1->calculate_checksum();
372         $this->assertNotEmpty($checksum);
373         $this->assertTrue($bs1->is_checksum_correct($checksum));
374     }
377 /**
378  * helper extended base_setting class that makes some methods public for testing
379  */
380 class mock_base_setting extends base_setting {
381     public function get_vtype() {
382         return $this->vtype;
383     }
385     public function process_change($setting, $ctype, $oldv) {
386         // Simply, inherit from the main object
387         $this->set_value($setting->get_value());
388         $this->set_visibility($setting->get_visibility());
389         $this->set_status($setting->get_status());
390     }
392     public function get_ui_info() {
393         // Return an array with all the ui info to be tested
394         return array($this->ui_type, $this->ui_label, $this->ui_values, $this->ui_options);
395     }
398 /**
399  * helper extended backup_setting class that makes some methods public for testing
400  */
401 class mock_backup_setting extends backup_setting {
402     public function set_level($level) {
403         $this->level = $level;
404     }
406     public function process_change($setting, $ctype, $oldv) {
407         // Simply, inherit from the main object
408         $this->set_value($setting->get_value());
409         $this->set_visibility($setting->get_visibility());
410         $this->set_status($setting->get_status());
411     }
414 /**
415  * helper extended activity_backup_setting class that makes some methods public for testing
416  */
417 class mock_activity_backup_setting extends activity_backup_setting {
418     public function process_change($setting, $ctype, $oldv) {
419         // Do nothing
420     }
423 /**
424  * helper extended section_backup_setting class that makes some methods public for testing
425  */
426 class mock_section_backup_setting extends section_backup_setting {
427     public function process_change($setting, $ctype, $oldv) {
428         // Do nothing
429     }
432 /**
433  * helper extended course_backup_setting class that makes some methods public for testing
434  */
435 class mock_course_backup_setting extends course_backup_setting {
436     public function process_change($setting, $ctype, $oldv) {
437         // Do nothing
438     }
441 /**
442  * This error handler is used to convert errors to excpetions so that simepltest can
443  * catch them.
444  *
445  * This is required in order to catch type hint mismatches that result in a error
446  * being thrown. It should only ever be used to catch E_RECOVERABLE_ERROR's.
447  *
448  * It throws a backup_setting_exception with 'incorrect_object_passed'
449  *
450  * @param int $errno E_RECOVERABLE_ERROR
451  * @param string $errstr
452  * @param string $errfile
453  * @param int $errline
454  * @param array $errcontext
455  * @return null
456  */
457 function backup_setting_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
458     if ($errno !== E_RECOVERABLE_ERROR) {
459         // Currently we only want to deal with type hinting errors
460         return false;
461     }
462     throw new backup_setting_exception('incorrect_object_passed');