MDL-46458 forum: New WS get_forum_discussions_paginated
[moodle.git] / mod / forum / tests / externallib_test.php
CommitLineData
2b9fe87d
MN
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * The module forums external functions unit tests
20 *
21 * @package mod_forum
22 * @category external
23 * @copyright 2012 Mark Nelson <markn@moodle.com>
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
27defined('MOODLE_INTERNAL') || die();
28
29global $CFG;
30
31require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32
33class mod_forum_external_testcase extends externallib_advanced_testcase {
34
35 /**
36 * Tests set up
37 */
38 protected function setUp() {
39 global $CFG;
40
59075a43
AN
41 // We must clear the subscription caches. This has to be done both before each test, and after in case of other
42 // tests using these functions.
43 \mod_forum\subscriptions::reset_forum_cache();
44
2b9fe87d
MN
45 require_once($CFG->dirroot . '/mod/forum/externallib.php');
46 }
47
59075a43
AN
48 public function tearDown() {
49 // We must clear the subscription caches. This has to be done both before each test, and after in case of other
50 // tests using these functions.
51 \mod_forum\subscriptions::reset_forum_cache();
52 }
53
2b9fe87d
MN
54 /**
55 * Test get forums
56 */
57 public function test_mod_forum_get_forums_by_courses() {
58 global $USER, $CFG, $DB;
59
60 $this->resetAfterTest(true);
61
62 // Create a user.
63 $user = self::getDataGenerator()->create_user();
64
65 // Set to the user.
66 self::setUser($user);
67
68 // Create courses to add the modules.
69 $course1 = self::getDataGenerator()->create_course();
70 $course2 = self::getDataGenerator()->create_course();
71
72 // First forum.
73 $record = new stdClass();
74 $record->introformat = FORMAT_HTML;
75 $record->course = $course1->id;
76 $forum1 = self::getDataGenerator()->create_module('forum', $record);
77
78 // Second forum.
79 $record = new stdClass();
80 $record->introformat = FORMAT_HTML;
81 $record->course = $course2->id;
82 $forum2 = self::getDataGenerator()->create_module('forum', $record);
83
84 // Check the forum was correctly created.
85 $this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2',
86 array('forum1' => $forum1->id, 'forum2' => $forum2->id)));
87
88 // Enrol the user in two courses.
909f27ac
JM
89 // DataGenerator->enrol_user automatically sets a role for the user with the permission mod/form:viewdiscussion.
90 $this->getDataGenerator()->enrol_user($user->id, $course1->id, null, 'manual');
91 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
2b9fe87d 92 $enrol = enrol_get_plugin('manual');
2b9fe87d
MN
93 $enrolinstances = enrol_get_instances($course2->id, true);
94 foreach ($enrolinstances as $courseenrolinstance) {
95 if ($courseenrolinstance->enrol == "manual") {
96 $instance2 = $courseenrolinstance;
97 break;
98 }
99 }
100 $enrol->enrol_user($instance2, $user->id);
101
2b9fe87d 102 // Assign capabilities to view forums for forum 2.
74b63eae 103 $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
2b9fe87d
MN
104 $context2 = context_module::instance($cm2->id);
105 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
106 $roleid2 = $this->assignUserCapability('mod/forum:viewdiscussion', $context2->id, $newrole);
107
108 // Create what we expect to be returned when querying the two courses.
109 $expectedforums = array();
110 $expectedforums[$forum1->id] = (array) $forum1;
111 $expectedforums[$forum2->id] = (array) $forum2;
112
113 // Call the external function passing course ids.
114 $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id));
2b9fe87d 115 external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
a9a0cb69 116 $this->assertEquals($expectedforums, $forums);
2b9fe87d
MN
117
118 // Call the external function without passing course id.
119 $forums = mod_forum_external::get_forums_by_courses();
2b9fe87d 120 external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
a9a0cb69 121 $this->assertEquals($expectedforums, $forums);
2b9fe87d
MN
122
123 // Unenrol user from second course and alter expected forums.
124 $enrol->unenrol_user($instance2, $user->id);
125 unset($expectedforums[$forum2->id]);
126
127 // Call the external function without passing course id.
128 $forums = mod_forum_external::get_forums_by_courses();
2b9fe87d 129 external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
a9a0cb69 130 $this->assertEquals($expectedforums, $forums);
2b9fe87d 131
a9a0cb69
MN
132 // Call for the second course we unenrolled the user from, ensure exception thrown.
133 try {
134 mod_forum_external::get_forums_by_courses(array($course2->id));
135 $this->fail('Exception expected due to being unenrolled from the course.');
136 } catch (moodle_exception $e) {
137 $this->assertEquals('requireloginerror', $e->errorcode);
138 }
139
140 // Call without required capability, ensure exception thrown.
909f27ac 141 $this->unassignUserCapability('mod/forum:viewdiscussion', null, null, $course1->id);
a9a0cb69 142 try {
909f27ac 143 $forums = mod_forum_external::get_forums_by_courses(array($course1->id));
a9a0cb69
MN
144 $this->fail('Exception expected due to missing capability.');
145 } catch (moodle_exception $e) {
146 $this->assertEquals('nopermissions', $e->errorcode);
147 }
148 }
149
150 /**
151 * Test get forum discussions
152 */
153 public function test_mod_forum_get_forum_discussions() {
154 global $USER, $CFG, $DB;
155
156 $this->resetAfterTest(true);
157
158 // Set the CFG variable to allow track forums.
159 $CFG->forum_trackreadposts = true;
160
161 // Create a user who can track forums.
162 $record = new stdClass();
163 $record->trackforums = true;
164 $user1 = self::getDataGenerator()->create_user($record);
165 // Create a bunch of other users to post.
166 $user2 = self::getDataGenerator()->create_user();
167 $user3 = self::getDataGenerator()->create_user();
168 $user4 = self::getDataGenerator()->create_user();
169
170 // Set the first created user to the test user.
171 self::setUser($user1);
172
173 // Create courses to add the modules.
174 $course1 = self::getDataGenerator()->create_course();
175 $course2 = self::getDataGenerator()->create_course();
176
177 // First forum with tracking off.
178 $record = new stdClass();
179 $record->course = $course1->id;
180 $record->trackingtype = FORUM_TRACKING_OFF;
181 $forum1 = self::getDataGenerator()->create_module('forum', $record);
182
183 // Second forum of type 'qanda' with tracking enabled.
184 $record = new stdClass();
185 $record->course = $course2->id;
186 $record->type = 'qanda';
bd8f5d45 187 $record->trackingtype = FORUM_TRACKING_FORCED;
a9a0cb69
MN
188 $forum2 = self::getDataGenerator()->create_module('forum', $record);
189
190 // Third forum where we will only have one discussion with no replies.
191 $record = new stdClass();
192 $record->course = $course2->id;
193 $forum3 = self::getDataGenerator()->create_module('forum', $record);
194
195 // Add discussions to the forums.
196 $record = new stdClass();
197 $record->course = $course1->id;
198 $record->userid = $user1->id;
199 $record->forum = $forum1->id;
c3f31a3d 200 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
a9a0cb69
MN
201
202 $record = new stdClass();
203 $record->course = $course2->id;
204 $record->userid = $user2->id;
205 $record->forum = $forum2->id;
c3f31a3d 206 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
a9a0cb69
MN
207
208 $record = new stdClass();
209 $record->course = $course2->id;
210 $record->userid = $user2->id;
211 $record->forum = $forum3->id;
c3f31a3d 212 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
a9a0cb69
MN
213
214 // Add three replies to the discussion 1 from different users.
215 $record = new stdClass();
216 $record->discussion = $discussion1->id;
217 $record->parent = $discussion1->firstpost;
218 $record->userid = $user2->id;
c3f31a3d 219 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
2b9fe87d 220
a9a0cb69
MN
221 $record->parent = $discussion1reply1->id;
222 $record->userid = $user3->id;
c3f31a3d 223 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
a9a0cb69
MN
224
225 $record->userid = $user4->id;
c3f31a3d 226 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
a9a0cb69
MN
227
228 // Add two replies to discussion 2 from different users.
229 $record = new stdClass();
230 $record->discussion = $discussion2->id;
231 $record->parent = $discussion2->firstpost;
232 $record->userid = $user1->id;
c3f31a3d 233 $discussion2reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
a9a0cb69
MN
234
235 $record->parent = $discussion2reply1->id;
236 $record->userid = $user3->id;
c3f31a3d 237 $discussion2reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
a9a0cb69
MN
238
239 // Check the forums were correctly created.
240 $this->assertEquals(3, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2 OR id = :forum3',
241 array('forum1' => $forum1->id, 'forum2' => $forum2->id, 'forum3' => $forum3->id)));
242
243 // Check the discussions were correctly created.
244 $this->assertEquals(3, $DB->count_records_select('forum_discussions', 'forum = :forum1 OR forum = :forum2
d2c58b95 245 OR forum = :forum3', array('forum1' => $forum1->id, 'forum2' => $forum2->id, 'forum3' => $forum3->id)));
a9a0cb69
MN
246
247 // Check the posts were correctly created, don't forget each discussion created also creates a post.
248 $this->assertEquals(7, $DB->count_records_select('forum_posts', 'discussion = :discussion1 OR discussion = :discussion2',
249 array('discussion1' => $discussion1->id, 'discussion2' => $discussion2->id)));
250
251 // Enrol the user in the first course.
252 $enrol = enrol_get_plugin('manual');
909f27ac
JM
253 // Following line enrol and assign default role id to the user.
254 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
255 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
a9a0cb69
MN
256
257 // Now enrol into the second course.
909f27ac 258 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
a9a0cb69
MN
259 $enrolinstances = enrol_get_instances($course2->id, true);
260 foreach ($enrolinstances as $courseenrolinstance) {
261 if ($courseenrolinstance->enrol == "manual") {
262 $instance2 = $courseenrolinstance;
263 break;
264 }
265 }
266 $enrol->enrol_user($instance2, $user1->id);
267
a9a0cb69 268 // Assign capabilities to view discussions for forum 2.
74b63eae 269 $cm = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
a9a0cb69
MN
270 $context = context_module::instance($cm->id);
271 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
272 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
273
274 // Assign capabilities to view discussions for forum 3.
74b63eae 275 $cm = get_coursemodule_from_id('forum', $forum3->cmid, 0, false, MUST_EXIST);
a9a0cb69
MN
276 $context = context_module::instance($cm->id);
277 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
278
279 // Create what we expect to be returned when querying the forums.
280 $expecteddiscussions = array();
281 $expecteddiscussions[$discussion1->id] = array(
282 'id' => $discussion1->id,
283 'course' => $discussion1->course,
284 'forum' => $discussion1->forum,
285 'name' => $discussion1->name,
286 'firstpost' => $discussion1->firstpost,
287 'userid' => $discussion1->userid,
288 'groupid' => $discussion1->groupid,
289 'assessed' => $discussion1->assessed,
290 'timemodified' => $discussion1reply3->created,
291 'usermodified' => $discussion1reply3->userid,
292 'timestart' => $discussion1->timestart,
293 'timeend' => $discussion1->timeend,
294 'firstuserfullname' => fullname($user1),
295 'firstuserimagealt' => $user1->imagealt,
296 'firstuserpicture' => $user1->picture,
297 'firstuseremail' => $user1->email,
298 'subject' => $discussion1->name,
299 'numreplies' => 3,
300 'numunread' => '',
301 'lastpost' => $discussion1reply3->id,
302 'lastuserid' => $user4->id,
303 'lastuserfullname' => fullname($user4),
304 'lastuserimagealt' => $user4->imagealt,
305 'lastuserpicture' => $user4->picture,
306 'lastuseremail' => $user4->email
307 );
308 $expecteddiscussions[$discussion2->id] = array(
309 'id' => $discussion2->id,
310 'course' => $discussion2->course,
311 'forum' => $discussion2->forum,
312 'name' => $discussion2->name,
313 'firstpost' => $discussion2->firstpost,
314 'userid' => $discussion2->userid,
315 'groupid' => $discussion2->groupid,
316 'assessed' => $discussion2->assessed,
317 'timemodified' => $discussion2reply2->created,
318 'usermodified' => $discussion2reply2->userid,
319 'timestart' => $discussion2->timestart,
320 'timeend' => $discussion2->timeend,
321 'firstuserfullname' => fullname($user2),
322 'firstuserimagealt' => $user2->imagealt,
323 'firstuserpicture' => $user2->picture,
324 'firstuseremail' => $user2->email,
325 'subject' => $discussion2->name,
326 'numreplies' => 2,
327 'numunread' => 3,
328 'lastpost' => $discussion2reply2->id,
329 'lastuserid' => $user3->id,
330 'lastuserfullname' => fullname($user3),
331 'lastuserimagealt' => $user3->imagealt,
332 'lastuserpicture' => $user3->picture,
333 'lastuseremail' => $user3->email
334 );
335 $expecteddiscussions[$discussion3->id] = array(
336 'id' => $discussion3->id,
337 'course' => $discussion3->course,
338 'forum' => $discussion3->forum,
339 'name' => $discussion3->name,
340 'firstpost' => $discussion3->firstpost,
341 'userid' => $discussion3->userid,
342 'groupid' => $discussion3->groupid,
343 'assessed' => $discussion3->assessed,
344 'timemodified' => $discussion3->timemodified,
345 'usermodified' => $discussion3->usermodified,
346 'timestart' => $discussion3->timestart,
347 'timeend' => $discussion3->timeend,
348 'firstuserfullname' => fullname($user2),
349 'firstuserimagealt' => $user2->imagealt,
350 'firstuserpicture' => $user2->picture,
351 'firstuseremail' => $user2->email,
352 'subject' => $discussion3->name,
353 'numreplies' => 0,
354 'numunread' => 1,
355 'lastpost' => $discussion3->firstpost,
356 'lastuserid' => $user2->id,
357 'lastuserfullname' => fullname($user2),
358 'lastuserimagealt' => $user2->imagealt,
359 'lastuserpicture' => $user2->picture,
360 'lastuseremail' => $user2->email
361 );
362
363 // Call the external function passing forum ids.
364 $discussions = mod_forum_external::get_forum_discussions(array($forum1->id, $forum2->id, $forum3->id));
365 external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
366 $this->assertEquals($expecteddiscussions, $discussions);
367
368 // Remove the users post from the qanda forum and ensure they can not return the discussion.
369 $DB->delete_records('forum_posts', array('id' => $discussion2reply1->id));
370 try {
371 mod_forum_external::get_forum_discussions(array($forum2->id));
372 $this->fail('Exception expected due to attempting to access qanda forum without posting.');
373 } catch (moodle_exception $e) {
374 $this->assertEquals('nopermissions', $e->errorcode);
375 }
376
377 // Call without required view discussion capability.
909f27ac 378 $this->unassignUserCapability('mod/forum:viewdiscussion', null, null, $course1->id);
a9a0cb69
MN
379 try {
380 mod_forum_external::get_forum_discussions(array($forum1->id));
381 $this->fail('Exception expected due to missing capability.');
382 } catch (moodle_exception $e) {
383 $this->assertEquals('nopermissions', $e->errorcode);
384 }
385
386 // Unenrol user from second course.
387 $enrol->unenrol_user($instance2, $user1->id);
388
389 // Call for the second course we unenrolled the user from, make sure exception thrown.
390 try {
391 mod_forum_external::get_forum_discussions(array($forum2->id));
392 $this->fail('Exception expected due to being unenrolled from the course.');
393 } catch (moodle_exception $e) {
394 $this->assertEquals('requireloginerror', $e->errorcode);
395 }
2b9fe87d 396 }
e2ede426
JL
397
398 /**
399 * Test get forum posts
400 */
401 public function test_mod_forum_get_forum_discussion_posts() {
402 global $CFG;
403
404 $this->resetAfterTest(true);
405
406 // Set the CFG variable to allow track forums.
407 $CFG->forum_trackreadposts = true;
408
409 // Create a user who can track forums.
410 $record = new stdClass();
411 $record->trackforums = true;
412 $user1 = self::getDataGenerator()->create_user($record);
413 // Create a bunch of other users to post.
414 $user2 = self::getDataGenerator()->create_user();
415 $user3 = self::getDataGenerator()->create_user();
416
417 // Set the first created user to the test user.
418 self::setUser($user1);
419
420 // Create course to add the module.
421 $course1 = self::getDataGenerator()->create_course();
422
423 // Forum with tracking off.
424 $record = new stdClass();
425 $record->course = $course1->id;
426 $record->trackingtype = FORUM_TRACKING_OFF;
427 $forum1 = self::getDataGenerator()->create_module('forum', $record);
428 $forum1context = context_module::instance($forum1->cmid);
429
430 // Add discussions to the forums.
431 $record = new stdClass();
432 $record->course = $course1->id;
433 $record->userid = $user1->id;
434 $record->forum = $forum1->id;
435 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
436
437 $record = new stdClass();
438 $record->course = $course1->id;
439 $record->userid = $user2->id;
440 $record->forum = $forum1->id;
441 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
442
443 // Add 2 replies to the discussion 1 from different users.
444 $record = new stdClass();
445 $record->discussion = $discussion1->id;
446 $record->parent = $discussion1->firstpost;
447 $record->userid = $user2->id;
448 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
449
450 $record->parent = $discussion1reply1->id;
451 $record->userid = $user3->id;
452 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
453
454 // Enrol the user in the course.
455 $enrol = enrol_get_plugin('manual');
456 // Following line enrol and assign default role id to the user.
457 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
458 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
459 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
460 $this->getDataGenerator()->enrol_user($user3->id, $course1->id);
461
462 // Create what we expect to be returned when querying the discussion.
463 $expectedposts = array(
464 'posts' => array(),
465 'warnings' => array(),
466 );
467 $expectedposts['posts'][] = array(
468 'id' => $discussion1reply2->id,
469 'discussion' => $discussion1reply2->discussion,
470 'parent' => $discussion1reply2->parent,
471 'userid' => $discussion1reply2->userid,
472 'created' => $discussion1reply2->created,
473 'modified' => $discussion1reply2->modified,
474 'mailed' => $discussion1reply2->mailed,
475 'subject' => $discussion1reply2->subject,
476 'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
477 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
478 'messageformat' => $discussion1reply2->messageformat,
479 'messagetrust' => $discussion1reply2->messagetrust,
480 'attachment' => $discussion1reply2->attachment,
481 'totalscore' => $discussion1reply2->totalscore,
482 'mailnow' => $discussion1reply2->mailnow,
483 'children' => array(),
484 'canreply' => true,
485 'postread' => false,
486 'userfullname' => fullname($user3)
487 );
488 $expectedposts['posts'][] = array(
489 'id' => $discussion1reply1->id,
490 'discussion' => $discussion1reply1->discussion,
491 'parent' => $discussion1reply1->parent,
492 'userid' => $discussion1reply1->userid,
493 'created' => $discussion1reply1->created,
494 'modified' => $discussion1reply1->modified,
495 'mailed' => $discussion1reply1->mailed,
496 'subject' => $discussion1reply1->subject,
497 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
498 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
499 'messageformat' => $discussion1reply1->messageformat,
500 'messagetrust' => $discussion1reply1->messagetrust,
501 'attachment' => $discussion1reply1->attachment,
502 'totalscore' => $discussion1reply1->totalscore,
503 'mailnow' => $discussion1reply1->mailnow,
d2c58b95 504 'children' => array($discussion1reply2->id),
e2ede426
JL
505 'canreply' => true,
506 'postread' => false,
507 'userfullname' => fullname($user2)
508 );
509
510 // Test a discussion with two additional posts (total 3 posts).
511 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
512 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
513 $this->assertEquals(3, count($posts['posts']));
514
515 // Unset the initial discussion post.
516 array_pop($posts['posts']);
517 $this->assertEquals($expectedposts, $posts);
518
519 // Test discussion without additional posts. There should be only one post (the one created by the discussion).
520 $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
521 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
522 $this->assertEquals(1, count($posts['posts']));
523
524 }
2b9fe87d 525}