MDL-64656 core_tag: New WebService core_tag_get_tag_cloud
[moodle.git] / tag / tests / external_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 WS in tags
19  *
20  * @package core_tag
21  * @category test
22  * @copyright 2015 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;
30 require_once($CFG->libdir . '/externallib.php');
31 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
33 class core_tag_external_testcase extends externallib_advanced_testcase {
34     /**
35      * Test update_categories
36      */
37     public function test_update_tags() {
38         global $DB;
39         $this->resetAfterTest();
40         $context = context_system::instance();
42         $originaltag = array(
43             'isstandard' => 0,
44             'flag' => 1,
45             'rawname' => 'test',
46             'description' => 'desc'
47         );
48         $tag = $this->getDataGenerator()->create_tag($originaltag);
50         $updatetag = array(
51             'id' => $tag->id,
52             'description' => 'Trying to change tag description',
53             'rawname' => 'Trying to change tag name',
54             'flag' => 0,
55             'isstandard' => 1,
56         );
57         $gettag = array(
58             'id' => $tag->id,
59         );
61         // User without any caps can not change anything about a tag but can request [partial] tag data.
62         $this->setUser($this->getDataGenerator()->create_user());
63         $result = core_tag_external::update_tags(array($updatetag));
64         $result = external_api::clean_returnvalue(core_tag_external::update_tags_returns(), $result);
65         $this->assertEquals($tag->id, $result['warnings'][0]['item']);
66         $this->assertEquals('nothingtoupdate', $result['warnings'][0]['warningcode']);
67         $this->assertEquals($originaltag['rawname'], $DB->get_field('tag', 'rawname',
68             array('id' => $tag->id)));
69         $this->assertEquals($originaltag['description'], $DB->get_field('tag', 'description',
70             array('id' => $tag->id)));
72         $result = core_tag_external::get_tags(array($gettag));
73         $result = external_api::clean_returnvalue(core_tag_external::get_tags_returns(), $result);
74         $this->assertEquals($originaltag['rawname'], $result['tags'][0]['rawname']);
75         $this->assertEquals($originaltag['description'], $result['tags'][0]['description']);
76         $this->assertNotEmpty($result['tags'][0]['viewurl']);
77         $this->assertArrayNotHasKey('changetypeurl', $result['tags'][0]);
78         $this->assertArrayNotHasKey('changeflagurl', $result['tags'][0]);
79         $this->assertArrayNotHasKey('flag', $result['tags'][0]);
80         $this->assertArrayNotHasKey('official', $result['tags'][0]);
81         $this->assertArrayNotHasKey('isstandard', $result['tags'][0]);
83         // User with editing only capability can change description but not the tag name.
84         $roleid = $this->assignUserCapability('moodle/tag:edit', $context->id);
85         $result = core_tag_external::update_tags(array($updatetag));
86         $result = external_api::clean_returnvalue(core_tag_external::update_tags_returns(), $result);
87         $this->assertEmpty($result['warnings']);
89         $result = core_tag_external::get_tags(array($gettag));
90         $result = external_api::clean_returnvalue(core_tag_external::get_tags_returns(), $result);
91         $this->assertEquals($updatetag['id'], $result['tags'][0]['id']);
92         $this->assertEquals($updatetag['description'], $result['tags'][0]['description']);
93         $this->assertEquals($originaltag['rawname'], $result['tags'][0]['rawname']);
94         $this->assertArrayNotHasKey('flag', $result['tags'][0]); // 'Flag' is not available unless 'moodle/tag:manage' cap exists.
95         $this->assertEquals(0, $result['tags'][0]['official']);
96         $this->assertEquals(0, $result['tags'][0]['isstandard']);
97         $this->assertEquals($originaltag['rawname'], $DB->get_field('tag', 'rawname',
98                 array('id' => $tag->id)));
99         $this->assertEquals($updatetag['description'], $DB->get_field('tag', 'description',
100                 array('id' => $tag->id)));
102         // User with editing and manage cap can also change the tag name,
103         // make it standard and reset flag.
104         assign_capability('moodle/tag:manage', CAP_ALLOW, $roleid, $context->id);
105         $this->assertTrue(has_capability('moodle/tag:manage', $context));
106         $result = core_tag_external::update_tags(array($updatetag));
107         $result = external_api::clean_returnvalue(core_tag_external::update_tags_returns(), $result);
108         $this->assertEmpty($result['warnings']);
110         $result = core_tag_external::get_tags(array($gettag));
111         $result = external_api::clean_returnvalue(core_tag_external::get_tags_returns(), $result);
112         $this->assertEquals($updatetag['id'], $result['tags'][0]['id']);
113         $this->assertEquals($updatetag['rawname'], $result['tags'][0]['rawname']);
114         $this->assertEquals(core_text::strtolower($updatetag['rawname']), $result['tags'][0]['name']);
115         $this->assertEquals($updatetag['flag'], $result['tags'][0]['flag']);
116         $this->assertEquals($updatetag['isstandard'], $result['tags'][0]['official']);
117         $this->assertEquals($updatetag['isstandard'], $result['tags'][0]['isstandard']);
118         $this->assertEquals($updatetag['rawname'], $DB->get_field('tag', 'rawname',
119                 array('id' => $tag->id)));
120         $this->assertEquals(1, $DB->get_field('tag', 'isstandard', array('id' => $tag->id)));
122         // Updating and getting non-existing tag.
123         $nonexistingtag = array(
124             'id' => 123,
125             'description' => 'test'
126         );
127         $getnonexistingtag = array(
128             'id' => 123,
129         );
130         $result = core_tag_external::update_tags(array($nonexistingtag));
131         $result = external_api::clean_returnvalue(core_tag_external::update_tags_returns(), $result);
132         $this->assertEquals(123, $result['warnings'][0]['item']);
133         $this->assertEquals('tagnotfound', $result['warnings'][0]['warningcode']);
135         $result = core_tag_external::get_tags(array($getnonexistingtag));
136         $result = external_api::clean_returnvalue(core_tag_external::get_tags_returns(), $result);
137         $this->assertEmpty($result['tags']);
138         $this->assertEquals(123, $result['warnings'][0]['item']);
139         $this->assertEquals('tagnotfound', $result['warnings'][0]['warningcode']);
141         // Attempt to update a tag to the name that is reserved.
142         $anothertag = $this->getDataGenerator()->create_tag(array('rawname' => 'Mytag'));
143         $updatetag2 = array('id' => $tag->id, 'rawname' => 'MYTAG');
144         $result = core_tag_external::update_tags(array($updatetag2));
145         $result = external_api::clean_returnvalue(core_tag_external::update_tags_returns(), $result);
146         $this->assertEquals($tag->id, $result['warnings'][0]['item']);
147         $this->assertEquals('namesalreadybeeingused', $result['warnings'][0]['warningcode']);
148     }
150     /**
151      * Test update_inplace_editable()
152      */
153     public function test_update_inplace_editable() {
154         global $CFG, $DB, $PAGE;
155         require_once($CFG->dirroot . '/lib/external/externallib.php');
157         $this->resetAfterTest(true);
158         $tag = $this->getDataGenerator()->create_tag();
159         $this->setUser($this->getDataGenerator()->create_user());
161         // Call service for core_tag component without necessary permissions.
162         try {
163             core_external::update_inplace_editable('core_tag', 'tagname', $tag->id, 'new tag name');
164             $this->fail('Exception expected');
165         } catch (moodle_exception $e) {
166             $this->assertEquals('Sorry, but you do not currently have permissions to do that (Manage all tags).',
167                     $e->getMessage());
168         }
170         // Change to admin user and make sure that tag name can be updated using web service update_inplace_editable().
171         $this->setAdminUser();
172         $res = core_external::update_inplace_editable('core_tag', 'tagname', $tag->id, 'New tag name');
173         $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
174         $this->assertEquals('New tag name', $res['value']);
175         $this->assertEquals('New tag name', $DB->get_field('tag', 'rawname', array('id' => $tag->id)));
177         // Call callback core_tag_inplace_editable() directly.
178         $tmpl = component_callback('core_tag', 'inplace_editable', array('tagname', $tag->id, 'Rename me again'));
179         $this->assertInstanceOf('core\output\inplace_editable', $tmpl);
180         $res = $tmpl->export_for_template($PAGE->get_renderer('core'));
181         $this->assertEquals('Rename me again', $res['value']);
182         $this->assertEquals('Rename me again', $DB->get_field('tag', 'rawname', array('id' => $tag->id)));
183     }
185     /**
186      * Test get_tagindex_per_area.
187      */
188     public function test_get_tagindex_per_area() {
189         global $USER;
190         $this->resetAfterTest(true);
192         // Create tags for two user profiles and one course.
193         $this->setAdminUser();
194         $context = context_user::instance($USER->id);
195         core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('test'));
197         $this->setUser($this->getDataGenerator()->create_user());
198         $context = context_user::instance($USER->id);
199         core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('test'));
201         $course = $this->getDataGenerator()->create_course();
202         $context = context_course::instance($course->id);
203         core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('test'));
205         $tag = core_tag_tag::get_by_name(0, 'test');
207         // First, search by id.
208         $result = core_tag_external::get_tagindex_per_area(array('id' => $tag->id));
209         $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
210         $this->assertCount(2, $result); // Two different areas: course and user.
211         $this->assertEquals($tag->id, $result[0]['tagid']);
212         $this->assertEquals('course', $result[0]['itemtype']);
213         $this->assertEquals($tag->id, $result[1]['tagid']);
214         $this->assertEquals('user', $result[1]['itemtype']);
216         // Now, search by name.
217         $result = core_tag_external::get_tagindex_per_area(array('tag' => 'test'));
218         $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
219         $this->assertCount(2, $result); // Two different areas: course and user.
220         $this->assertEquals($tag->id, $result[0]['tagid']);
221         $this->assertEquals('course', $result[0]['itemtype']);
222         $this->assertEquals($tag->id, $result[1]['tagid']);
223         $this->assertEquals('user', $result[1]['itemtype']);
225         // Filter by tag area.
226         $result = core_tag_external::get_tagindex_per_area(array('tag' => 'test', 'ta' => $result[0]['ta']));
227         $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
228         $this->assertCount(1, $result); // Just the given area.
229         $this->assertEquals($tag->id, $result[0]['tagid']);
230         $this->assertEquals('course', $result[0]['itemtype']);
232         // Now, search by tag collection (use default).
233         $result = core_tag_external::get_tagindex_per_area(array('id' => $tag->id, 'tc' => 1));
234         $result = external_api::clean_returnvalue(core_tag_external::get_tagindex_per_area_returns(), $result);
235         $this->assertCount(2, $result); // Two different areas: course and user.
236     }
238     /**
239      * Test get_tag_areas.
240      */
241     public function test_get_tag_areas() {
242         global $DB;
243         $this->resetAfterTest(true);
245         $this->setAdminUser();
246         $result = core_tag_external::get_tag_areas();
247         $result = external_api::clean_returnvalue(core_tag_external::get_tag_areas_returns(), $result);
248         $areas = $DB->get_records('tag_area');
249         $this->assertCount(count($areas), $result['areas']);
250         foreach ($result['areas'] as $area) {
251             $this->assertEquals($areas[$area['id']]->component, $area['component']);
252             $this->assertEquals($areas[$area['id']]->itemtype, $area['itemtype']);
253         }
254     }
256     /**
257      * Test get_tag_collections.
258      */
259     public function test_get_tag_collections() {
260         global $DB;
261         $this->resetAfterTest(true);
263         // Create new tag collection.
264         $data = (object) array('name' => 'new tag coll');
265         core_tag_collection::create($data);
267         $this->setAdminUser();
268         $result = core_tag_external::get_tag_collections();
269         $result = external_api::clean_returnvalue(core_tag_external::get_tag_collections_returns(), $result);
271         $collections = $DB->get_records('tag_coll');
272         $this->assertCount(count($collections), $result['collections']);
273         foreach ($result['collections'] as $collection) {
274             $this->assertEquals($collections[$collection['id']]->component, $collection['component']);
275             $this->assertEquals($collections[$collection['id']]->name, $collection['name']);
276         }
277     }
279     /**
280      * Test get_tag_cloud.
281      */
282     public function test_get_tag_cloud() {
283         global $USER, $DB;
284         $this->resetAfterTest(true);
286         // Create tags for two user profiles, a post and one course.
287         $this->setAdminUser();
288         $context = context_user::instance($USER->id);
289         core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('Cats', 'Dogs'));
291         $this->setUser($this->getDataGenerator()->create_user());
292         $context = context_user::instance($USER->id);
293         core_tag_tag::set_item_tags('core', 'user', $USER->id, $context, array('Mice'));
295         $course = $this->getDataGenerator()->create_course();
296         $coursecontext = context_course::instance($course->id);
297         core_tag_tag::set_item_tags('core', 'course', $course->id, $coursecontext, array('Cats'));
299         $post = new stdClass();
300         $post->userid = $USER->id;
301         $post->content = 'test post content text';
302         $post->id = $DB->insert_record('post', $post);
303         $context = context_system::instance();
304         core_tag_tag::set_item_tags('core', 'post', $post->id, $context, array('Horses', 'Cats'));
306         // First, retrieve complete cloud.
307         $result = core_tag_external::get_tag_cloud();
308         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
309         $this->assertCount(4, $result['tags']); // Four different tags: Cats, Dogs, Mice, Horses.
310         $this->assertEquals(4, $result['tagscount']);
311         $this->assertEquals(4, $result['totalcount']);
313         foreach ($result['tags'] as $tag) {
314             if ($tag['name'] == 'Cats') {
315                 $this->assertEquals(3, $tag['count']);
316             } else {
317                 $this->assertEquals(1, $tag['count']);
318             }
319         }
321         // Test filter by collection, pagination and sorting.
322         $defaultcoll = core_tag_collection::get_default();
323         $result = core_tag_external::get_tag_cloud($defaultcoll, false, 2, 'count');
324         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
325         $this->assertCount(2, $result['tags']); // Only two tags.
326         $this->assertEquals(2, $result['tagscount']);
327         $this->assertEquals(4, $result['totalcount']);
328         $this->assertEquals('Dogs', $result['tags'][0]['name']); // Lower count first.
330         // Test search.
331         $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', 'Mice');
332         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
333         $this->assertCount(1, $result['tags']); // Only the searched tags.
334         $this->assertEquals(1, $result['tagscount']);
335         $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
336         $this->assertEquals('Mice', $result['tags'][0]['name']);
338         $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', 'Conejo');
339         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
340         $this->assertCount(0, $result['tags']); // Nothing found.
341         $this->assertEquals(0, $result['tagscount']);
342         $this->assertEquals(0, $result['totalcount']); // When searching, the total is always for the search.
344         // Test standard filtering.
345         $micetag = core_tag_tag::get_by_name($defaultcoll, 'Mice', '*');
346         $micetag->update(array('isstandard' => 1));
348         $result = core_tag_external::get_tag_cloud(0, true);
349         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
350         $this->assertCount(1, $result['tags']);
351         $this->assertEquals(1, $result['tagscount']);
352         $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
353         $this->assertEquals('Mice', $result['tags'][0]['name']);
355         // Test course context filtering.
356         $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, $coursecontext->id);
357         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
358         $this->assertCount(1, $result['tags']);
359         $this->assertEquals(1, $result['tagscount']);
360         $this->assertEquals(1, $result['totalcount']); // When searching, the total is always for the search.
361         $this->assertEquals('Cats', $result['tags'][0]['name']);
363         // Complete system context.
364         $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, context_system::instance()->id);
365         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
366         $this->assertCount(4, $result['tags']);
367         $this->assertEquals(4, $result['tagscount']);
369         // Just system context - avoid children.
370         $result = core_tag_external::get_tag_cloud(0, false, 150, 'name', '', 0, context_system::instance()->id, 0);
371         $result = external_api::clean_returnvalue(core_tag_external::get_tag_cloud_returns(), $result);
372         $this->assertCount(2, $result['tags']);
373         $this->assertEquals(2, $result['tagscount']); // Horses and Cats.
374         $this->assertEquals('Cats', $result['tags'][0]['name']);
375         $this->assertEquals('Horses', $result['tags'][1]['name']);
376     }