baa5ecde928f40d3a39d413c5b180e3a6f67e12b
[moodle.git] / lib / tests / coursecatlib_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  * Tests for class coursecat from lib/coursecatlib.php
19  *
20  * @package    core
21  * @category   phpunit
22  * @copyright  2013 Marina Glancy
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/coursecatlib.php');
31 /**
32  * Functional test for accesslib.php
33  *
34  * Note: execution may take many minutes especially on slower servers.
35  */
36 class coursecatlib_testcase extends advanced_testcase {
38     var $roles;
40     public function setUp() {
41         parent::setUp();
42         $this->resetAfterTest(true);
43         $user = $this->getDataGenerator()->create_user();
44         $this->setUser($user);
45     }
47     protected function get_roleid($context = null) {
48         global $USER;
49         if ($context === null) {
50             $context = context_system::instance();
51         }
52         if (is_object($context)) {
53             $context = $context->id;
54         }
55         if (empty($this->roles)) {
56             $this->roles = array();
57         }
58         if (empty($this->roles[$USER->id])) {
59             $this->roles[$USER->id] = array();
60         }
61         if (empty($this->roles[$USER->id][$context])) {
62             $this->roles[$USER->id][$context] = create_role('Role for '.$USER->id.' in '.$context, 'role'.$USER->id.'-'.$context, '-');
63             role_assign($this->roles[$USER->id][$context], $USER->id, $context);
64         }
65         return $this->roles[$USER->id][$context];
66     }
68     protected function assign_capability($capability, $permission = CAP_ALLOW, $contextid = null) {
69         if ($contextid === null) {
70             $contextid = context_system::instance();
71         }
72         if (is_object($contextid)) {
73             $contextid = $contextid->id;
74         }
75         assign_capability($capability, $permission, $this->get_roleid($contextid), $contextid, true);
76         accesslib_clear_all_caches_for_unit_testing();
77     }
79     public function test_create_coursecat() {
80         // Create the category
81         $data = new stdClass();
82         $data->name = 'aaa';
83         $data->description = 'aaa';
84         $data->idnumber = '';
86         $category1 = coursecat::create($data);
88         // Initially confirm that base data was inserted correctly
89         $this->assertEquals($data->name, $category1->name);
90         $this->assertEquals($data->description, $category1->description);
91         $this->assertEquals($data->idnumber, $category1->idnumber);
93         $this->assertGreaterThanOrEqual(1, $category1->sortorder);
95         // Create two more categories and test the sortorder worked correctly
96         $data->name = 'ccc';
97         $category2 = coursecat::create($data);
99         $data->name = 'bbb';
100         $category3 = coursecat::create($data);
102         $this->assertGreaterThan($category1->sortorder, $category2->sortorder);
103         $this->assertGreaterThan($category2->sortorder, $category3->sortorder);
104     }
106     public function test_name_idnumber_exceptions() {
107         try {
108             coursecat::create(array('name' => ''));
109             $this->fail('Missing category name exception expected in coursecat::create');
110         } catch (moodle_exception $e) {
111         }
112         $cat1 = coursecat::create(array('name' => 'Cat1', 'idnumber' => '1'));
113         try {
114             $cat1->update(array('name' => ''));
115             $this->fail('Missing category name exception expected in coursecat::update');
116         } catch (moodle_exception $e) {
117         }
118         try {
119             coursecat::create(array('name' => 'Cat2', 'idnumber' => '1'));
120             $this->fail('Duplicate idnumber exception expected in coursecat::create');
121         } catch (moodle_exception $e) {
122         }
123         $cat2 = coursecat::create(array('name' => 'Cat2', 'idnumber' => '2'));
124         try {
125             $cat2->update(array('idnumber' => '1'));
126             $this->fail('Duplicate idnumber exception expected in coursecat::update');
127         } catch (moodle_exception $e) {
128         }
129     }
131     public function test_visibility() {
132         $this->assign_capability('moodle/category:viewhiddencategories');
133         $this->assign_capability('moodle/category:manage');
135         // create category 1 initially hidden
136         $category1 = coursecat::create(array('name' => 'Cat1', 'visible' => 0));
137         $this->assertEquals(0, $category1->visible);
138         $this->assertEquals(0, $category1->visibleold);
140         // create category 2 initially hidden as a child of hidden category 1
141         $category2 = coursecat::create(array('name' => 'Cat2', 'visible' => 0, 'parent' => $category1->id));
142         $this->assertEquals(0, $category2->visible);
143         $this->assertEquals(0, $category2->visibleold);
145         // create category 3 initially visible as a child of hidden category 1
146         $category3 = coursecat::create(array('name' => 'Cat3', 'visible' => 1, 'parent' => $category1->id));
147         $this->assertEquals(0, $category3->visible);
148         $this->assertEquals(1, $category3->visibleold);
150         // show category 1 and make sure that category 2 is hidden and category 3 is visible
151         $category1->show();
152         $this->assertEquals(1, coursecat::get($category1->id)->visible);
153         $this->assertEquals(0, coursecat::get($category2->id)->visible);
154         $this->assertEquals(1, coursecat::get($category3->id)->visible);
156         // create visible category 4
157         $category4 = coursecat::create(array('name' => 'Cat4'));
158         $this->assertEquals(1, $category4->visible);
159         $this->assertEquals(1, $category4->visibleold);
161         // create visible category 5 as a child of visible category 4
162         $category5 = coursecat::create(array('name' => 'Cat5', 'parent' => $category4->id));
163         $this->assertEquals(1, $category5->visible);
164         $this->assertEquals(1, $category5->visibleold);
166         // hide category 4 and make sure category 5 is hidden too
167         $category4->hide();
168         $this->assertEquals(0, $category4->visible);
169         $this->assertEquals(0, $category4->visibleold);
170         $category5 = coursecat::get($category5->id); // we have to re-read from DB
171         $this->assertEquals(0, $category5->visible);
172         $this->assertEquals(1, $category5->visibleold);
174         // show category 4 and make sure category 5 is visible too
175         $category4->show();
176         $this->assertEquals(1, $category4->visible);
177         $this->assertEquals(1, $category4->visibleold);
178         $category5 = coursecat::get($category5->id); // we have to re-read from DB
179         $this->assertEquals(1, $category5->visible);
180         $this->assertEquals(1, $category5->visibleold);
182         // move category 5 under hidden category 2 and make sure it became hidden
183         $category5->change_parent($category2->id);
184         $this->assertEquals(0, $category5->visible);
185         $this->assertEquals(1, $category5->visibleold);
187         // re-read object for category 5 from DB and check again
188         $category5 = coursecat::get($category5->id);
189         $this->assertEquals(0, $category5->visible);
190         $this->assertEquals(1, $category5->visibleold);
192         // tricky one! Move hidden category 5 under visible category ("Top") and make sure it is still hidden
193         // WHY? Well, different people may expect different behaviour here. So better keep it hidden
194         $category5->change_parent(0);
195         $this->assertEquals(0, $category5->visible);
196         $this->assertEquals(1, $category5->visibleold);
197     }
199     public function test_hierarchy() {
200         $this->assign_capability('moodle/category:viewhiddencategories');
201         $this->assign_capability('moodle/category:manage');
203         $category1 = coursecat::create(array('name' => 'Cat1'));
204         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
205         $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category1->id));
206         $category4 = coursecat::create(array('name' => 'Cat4', 'parent' => $category2->id));
208         // check function get_children()
209         $this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
210         // check function get_parents()
211         $this->assertEquals(array($category1->id, $category2->id), $category4->get_parents());
213         // can not move category to itself or to it's children
214         $this->assertFalse($category1->can_change_parent($category2->id));
215         $this->assertFalse($category2->can_change_parent($category2->id));
216         // can move category to grandparent
217         $this->assertTrue($category4->can_change_parent($category1->id));
219         try {
220             $category2->change_parent($category4->id);
221             $this->fail('Exception expected - can not move category');
222         } catch (moodle_exception $e) {
223         }
225         $category4->change_parent(0);
226         $this->assertEquals(array(), $category4->get_parents());
227         $this->assertEquals(array($category2->id, $category3->id), array_keys($category1->get_children()));
228         $this->assertEquals(array(), array_keys($category2->get_children()));
229     }
231     public function test_update() {
232         $category1 = coursecat::create(array('name' => 'Cat1'));
233         $timecreated = $category1->timemodified;
234         $this->assertEquals('Cat1', $category1->name);
235         $this->assertTrue(empty($category1->description));
236         sleep(2);
237         $testdescription = 'This is cat 1 а также русский текст';
238         $category1->update(array('description' => $testdescription));
239         $this->assertEquals($testdescription, $category1->description);
240         $category1 = coursecat::get($category1->id);
241         $this->assertEquals($testdescription, $category1->description);
242         cache_helper::purge_by_event('changesincoursecat');
243         $category1 = coursecat::get($category1->id);
244         $this->assertEquals($testdescription, $category1->description);
246         $this->assertGreaterThan($timecreated, $category1->timemodified);
247     }
249     public function test_delete() {
250         global $DB;
252         $this->assign_capability('moodle/category:manage');
253         $this->assign_capability('moodle/course:create');
255         $initialcatid = $DB->get_field_sql('SELECT max(id) from {course_categories}');
257         $category1 = coursecat::create(array('name' => 'Cat1'));
258         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
259         $category3 = coursecat::create(array('name' => 'Cat3'));
260         $category4 = coursecat::create(array('name' => 'Cat4', 'parent' => $category2->id));
262         $course1 = $this->getDataGenerator()->create_course(array('category' => $category2->id));
263         $course2 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
264         $course3 = $this->getDataGenerator()->create_course(array('category' => $category4->id));
265         $course4 = $this->getDataGenerator()->create_course(array('category' => $category1->id));
267         // Now we have
268         // $category1
269         //   $category2
270         //      $category4
271         //        $course2
272         //        $course3
273         //      $course1
274         //   $course4
275         // $category3
277         // Login as another user to test course:delete capability (user who created course can delete it within 24h even without cap)
278         $this->setUser($this->getDataGenerator()->create_user());
280         // Delete category 2 and move content to category 3
281         $this->assertFalse($category2->can_move_content_to($category3->id)); // no luck!
282         // add necessary capabilities
283         $this->assign_capability('moodle/course:create', CAP_ALLOW, context_coursecat::instance($category3->id));
284         $this->assign_capability('moodle/category:manage');
285         $this->assertTrue($category2->can_move_content_to($category3->id)); // hurray!
286         $category2->delete_move($category3->id);
288         // Make sure we have:
289         // $category1
290         //   $course4
291         // $category3
292         //    $category4
293         //      $course2
294         //      $course3
295         //    $course1
297         $this->assertNull(coursecat::get($category2->id, IGNORE_MISSING, true));
298         $this->assertEquals(array(), $category1->get_children());
299         $this->assertEquals(array($category4->id), array_keys($category3->get_children()));
300         $this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course2->id)));
301         $this->assertEquals($category4->id, $DB->get_field('course', 'category', array('id' => $course3->id)));
302         $this->assertEquals($category3->id, $DB->get_field('course', 'category', array('id' => $course1->id)));
304         // Delete category 3 completely
305         $this->assertFalse($category3->can_delete_full()); // no luck!
306         // add necessary capabilities
307         $this->assign_capability('moodle/course:delete', CAP_ALLOW, context_coursecat::instance($category3->id));
308         $this->assertTrue($category3->can_delete_full()); // hurray!
309         $category3->delete_full();
311         // Make sure we have:
312         // $category1
313         //   $course4
315         // Note that we also have default 'Miscellaneous' category and default 'site' course
316         $this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course_categories} WHERE id > ?', array($initialcatid)));
317         $this->assertEquals($category1->id, $DB->get_field_sql('SELECT max(id) FROM {course_categories}'));
318         $this->assertEquals(1, $DB->get_field_sql('SELECT count(*) FROM {course} WHERE id <> ?', array(SITEID)));
319         $this->assertEquals(array('id' => $course4->id, 'category' => $category1->id),
320                 (array)$DB->get_record_sql('SELECT id, category from {course} where id <> ?', array(SITEID)));
321     }
323     public function test_get_children() {
324         $category1 = coursecat::create(array('name' => 'Cat1'));
325         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
326         $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category1->id, 'visible' => 0));
327         $category4 = coursecat::create(array('name' => 'Cat4', 'idnumber' => '12', 'parent' => $category1->id));
328         $category5 = coursecat::create(array('name' => 'Cat5', 'idnumber' => '11', 'parent' => $category1->id, 'visible' => 0));
329         $category6 = coursecat::create(array('name' => 'Cat6', 'idnumber' => '10', 'parent' => $category1->id));
330         $category7 = coursecat::create(array('name' => 'Cat0', 'parent' => $category1->id));
332         $children = $category1->get_children();
333         // user does not have the capability to view hidden categories, so the list should be
334         // 2,4,6,7
335         $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
336         $this->assertEquals(4, $category1->get_children_count());
338         $children = $category1->get_children(array('offset' => 2));
339         $this->assertEquals(array($category6->id, $category7->id), array_keys($children));
340         $this->assertEquals(4, $category1->get_children_count());
342         $children = $category1->get_children(array('limit' => 2));
343         $this->assertEquals(array($category2->id, $category4->id), array_keys($children));
345         $children = $category1->get_children(array('offset' => 1, 'limit' => 2));
346         $this->assertEquals(array($category4->id, $category6->id), array_keys($children));
348         $children = $category1->get_children(array('sort' => array('name' => 1)));
349         // must be 7,2,4,6
350         $this->assertEquals(array($category7->id, $category2->id, $category4->id, $category6->id), array_keys($children));
352         $children = $category1->get_children(array('sort' => array('idnumber' => 1, 'name' => -1)));
353         // must be 2,7,6,4
354         $this->assertEquals(array($category2->id, $category7->id, $category6->id, $category4->id), array_keys($children));
356         // check that everything is all right after purging the caches
357         cache_helper::purge_by_event('changesincoursecat');
358         $children = $category1->get_children();
359         $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children));
360         $this->assertEquals(4, $category1->get_children_count());
361     }
363     public function test_get_search_courses() {
364         $cat1 = coursecat::create(array('name' => 'Cat1'));
365         $cat2 = coursecat::create(array('name' => 'Cat2', 'parent' => $cat1->id));
366         $c1 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => ' ', 'idnumber' => 'ID3'));
367         $c2 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => ' ', 'visible' => 0));
368         $c3 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test '));
369         $c4 = $this->getDataGenerator()->create_course(array('category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => ' ', 'idnumber' => 'ID4'));
371         $c5 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => ' '));
372         $c6 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => ' '));
373         $c7 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => ' ', 'visible' => 0));
374         $c8 = $this->getDataGenerator()->create_course(array('category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => ' '));
376         // get courses in category 1 (returned visible only because user is not enrolled)        global $DB;
377         $res = $cat1->get_courses(array('sortorder' => 1));
378         $this->assertEquals(array($c4->id, $c3->id, $c1->id), array_keys($res)); // courses are added in reverse order
379         $this->assertEquals(3, $cat1->get_courses_count());
381         // get courses in category 1 recursively (returned visible only because user is not enrolled)
382         $res = $cat1->get_courses(array('recursive' => 1));
383         $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c6->id, $c5->id), array_keys($res));
384         $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1)));
386         // get courses sorted by fullname
387         $res = $cat1->get_courses(array('sort' => array('fullname' => 1)));
388         $this->assertEquals(array($c1->id, $c4->id, $c3->id), array_keys($res));
389         $this->assertEquals(3, $cat1->get_courses_count(array('sort' => array('fullname' => 1))));
391         // get courses sorted by fullname recursively
392         $res = $cat1->get_courses(array('recursive' => 1, 'sort' => array('fullname' => 1)));
393         $this->assertEquals(array($c1->id, $c4->id, $c5->id, $c8->id, $c6->id, $c3->id), array_keys($res));
394         $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'sort' => array('fullname' => 1))));
396         // get courses sorted by fullname recursively, use offset and limit
397         $res = $cat1->get_courses(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => -1)));
398         $this->assertEquals(array($c6->id, $c8->id), array_keys($res));
399         // offset and limit do not affect get_courses_count()
400         $this->assertEquals(6, $cat1->get_courses_count(array('recursive' => 1, 'offset' => 1, 'limit' => 2, 'sort' => array('fullname' => 1))));
402         // calling get_courses_count without prior call to get_courses()
403         $this->assertEquals(3, $cat2->get_courses_count(array('recursive' => 1, 'sort' => array('idnumber' => 1))));
405         // search courses
407         // search by text
408         $res = coursecat::search_courses(array('search' => 'Test'));
409         $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c5->id), array_keys($res));
410         $this->assertEquals(5, coursecat::search_courses_count(array('search' => 'Test')));
412         // search by text with specified offset and limit
413         $options = array('sort' => array('fullname' => 1), 'offset' => 1, 'limit' => 2);
414         $res = coursecat::search_courses(array('search' => 'Test'), $options);
415         $this->assertEquals(array($c4->id, $c5->id), array_keys($res));
416         $this->assertEquals(5, coursecat::search_courses_count(array('search' => 'Test'), $options));
418         // IMPORTANT: the tests below may fail on some databases
419         // case-insensitive search
420         $res = coursecat::search_courses(array('search' => 'test'));
421         $this->assertEquals(array($c4->id, $c3->id, $c1->id, $c8->id, $c5->id), array_keys($res));
422         $this->assertEquals(5, coursecat::search_courses_count(array('search' => 'test')));
424         // non-latin language search
425         $res = coursecat::search_courses(array('search' => 'Математика'));
426         $this->assertEquals(array($c3->id, $c6->id), array_keys($res));
427         $this->assertEquals(2, coursecat::search_courses_count(array('search' => 'Математика'), array()));
428     }