MDL-57688 mod_lesson: New WS mod_lesson_launch_attempt
[moodle.git] / mod / lesson / tests / external_test.php
blobf89bc0193b1638f22794ccd8a6c8b03c68714d2d
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.
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 * Lesson module external functions tests
20 * @package mod_lesson
21 * @category external
22 * @copyright 2017 Juan Leyva <juan@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 * @since Moodle 3.3
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
31 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32 require_once($CFG->dirroot . '/mod/lesson/locallib.php');
34 /**
35 * Silly class to access mod_lesson_external internal methods.
37 * @package mod_lesson
38 * @copyright 2017 Juan Leyva <juan@moodle.com>
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 * @since Moodle 3.3
42 class testable_mod_lesson_external extends mod_lesson_external {
44 /**
45 * Validates a new attempt.
47 * @param lesson $lesson lesson instance
48 * @param array $params request parameters
49 * @param boolean $return whether to return the errors or throw exceptions
50 * @return [array the errors (if return set to true)
51 * @since Moodle 3.3
53 public static function validate_attempt(lesson $lesson, $params, $return = false) {
54 return parent::validate_attempt($lesson, $params, $return);
58 /**
59 * Lesson module external functions tests
61 * @package mod_lesson
62 * @category external
63 * @copyright 2017 Juan Leyva <juan@moodle.com>
64 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
65 * @since Moodle 3.3
67 class mod_lesson_external_testcase extends externallib_advanced_testcase {
69 /**
70 * Set up for every test
72 public function setUp() {
73 global $DB;
74 $this->resetAfterTest();
75 $this->setAdminUser();
77 // Setup test data.
78 $this->course = $this->getDataGenerator()->create_course();
79 $this->lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $this->course->id));
80 $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
81 $this->page1 = $lessongenerator->create_content($this->lesson);
82 $this->page2 = $lessongenerator->create_question_truefalse($this->lesson);
83 $this->context = context_module::instance($this->lesson->cmid);
84 $this->cm = get_coursemodule_from_instance('lesson', $this->lesson->id);
86 // Create users.
87 $this->student = self::getDataGenerator()->create_user();
88 $this->teacher = self::getDataGenerator()->create_user();
90 // Users enrolments.
91 $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
92 $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
93 $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
94 $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
98 /**
99 * Test test_mod_lesson_get_lessons_by_courses
101 public function test_mod_lesson_get_lessons_by_courses() {
102 global $DB;
104 // Create additional course.
105 $course2 = self::getDataGenerator()->create_course();
107 // Second lesson.
108 $record = new stdClass();
109 $record->course = $course2->id;
110 $lesson2 = self::getDataGenerator()->create_module('lesson', $record);
112 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
113 $enrol = enrol_get_plugin('manual');
114 $enrolinstances = enrol_get_instances($course2->id, true);
115 foreach ($enrolinstances as $courseenrolinstance) {
116 if ($courseenrolinstance->enrol == "manual") {
117 $instance2 = $courseenrolinstance;
118 break;
121 $enrol->enrol_user($instance2, $this->student->id, $this->studentrole->id);
123 self::setUser($this->student);
125 $returndescription = mod_lesson_external::get_lessons_by_courses_returns();
127 // Create what we expect to be returned when querying the two courses.
128 // First for the student user.
129 $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'practice',
130 'modattempts', 'usepassword', 'grade', 'custom', 'ongoing', 'usemaxgrade',
131 'maxanswers', 'maxattempts', 'review', 'nextpagedefault', 'feedback', 'minquestions',
132 'maxpages', 'timelimit', 'retake', 'mediafile', 'mediafiles', 'mediaheight', 'mediawidth',
133 'mediaclose', 'slideshow', 'width', 'height', 'bgcolor', 'displayleft', 'displayleftif',
134 'progressbar');
136 // Add expected coursemodule and data.
137 $lesson1 = $this->lesson;
138 $lesson1->coursemodule = $lesson1->cmid;
139 $lesson1->introformat = 1;
140 $lesson1->section = 0;
141 $lesson1->visible = true;
142 $lesson1->groupmode = 0;
143 $lesson1->groupingid = 0;
144 $lesson1->introfiles = [];
145 $lesson1->mediafiles = [];
147 $lesson2->coursemodule = $lesson2->cmid;
148 $lesson2->introformat = 1;
149 $lesson2->section = 0;
150 $lesson2->visible = true;
151 $lesson2->groupmode = 0;
152 $lesson2->groupingid = 0;
153 $lesson2->introfiles = [];
154 $lesson2->mediafiles = [];
156 foreach ($expectedfields as $field) {
157 $expected1[$field] = $lesson1->{$field};
158 $expected2[$field] = $lesson2->{$field};
161 $expectedlessons = array($expected2, $expected1);
163 // Call the external function passing course ids.
164 $result = mod_lesson_external::get_lessons_by_courses(array($course2->id, $this->course->id));
165 $result = external_api::clean_returnvalue($returndescription, $result);
167 $this->assertEquals($expectedlessons, $result['lessons']);
168 $this->assertCount(0, $result['warnings']);
170 // Call the external function without passing course id.
171 $result = mod_lesson_external::get_lessons_by_courses();
172 $result = external_api::clean_returnvalue($returndescription, $result);
173 $this->assertEquals($expectedlessons, $result['lessons']);
174 $this->assertCount(0, $result['warnings']);
176 // Unenrol user from second course and alter expected lessons.
177 $enrol->unenrol_user($instance2, $this->student->id);
178 array_shift($expectedlessons);
180 // Call the external function without passing course id.
181 $result = mod_lesson_external::get_lessons_by_courses();
182 $result = external_api::clean_returnvalue($returndescription, $result);
183 $this->assertEquals($expectedlessons, $result['lessons']);
185 // Call for the second course we unenrolled the user from, expected warning.
186 $result = mod_lesson_external::get_lessons_by_courses(array($course2->id));
187 $this->assertCount(1, $result['warnings']);
188 $this->assertEquals('1', $result['warnings'][0]['warningcode']);
189 $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
191 // Now, try as a teacher for getting all the additional fields.
192 self::setUser($this->teacher);
194 $additionalfields = array('password', 'dependency', 'conditions', 'activitylink', 'available', 'deadline',
195 'timemodified', 'completionendreached', 'completiontimespent');
197 foreach ($additionalfields as $field) {
198 $expectedlessons[0][$field] = $lesson1->{$field};
201 $result = mod_lesson_external::get_lessons_by_courses();
202 $result = external_api::clean_returnvalue($returndescription, $result);
203 $this->assertEquals($expectedlessons, $result['lessons']);
205 // Admin also should get all the information.
206 self::setAdminUser();
208 $result = mod_lesson_external::get_lessons_by_courses(array($this->course->id));
209 $result = external_api::clean_returnvalue($returndescription, $result);
210 $this->assertEquals($expectedlessons, $result['lessons']);
212 // Now, add a restriction.
213 $this->setUser($this->student);
214 $DB->set_field('lesson', 'usepassword', 1, array('id' => $lesson1->id));
215 $DB->set_field('lesson', 'password', 'abc', array('id' => $lesson1->id));
217 $lessons = mod_lesson_external::get_lessons_by_courses(array($this->course->id));
218 $lessons = external_api::clean_returnvalue(mod_lesson_external::get_lessons_by_courses_returns(), $lessons);
219 $this->assertFalse(isset($lessons['lessons'][0]['intro']));
223 * Test the validate_attempt function.
225 public function test_validate_attempt() {
226 global $DB;
228 $this->setUser($this->student);
229 // Test deadline.
230 $oldtime = time() - DAYSECS;
231 $DB->set_field('lesson', 'deadline', $oldtime, array('id' => $this->lesson->id));
233 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
234 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
235 $this->assertEquals('lessonclosed', key($validation));
236 $this->assertCount(1, $validation);
238 // Test not available yet.
239 $futuretime = time() + DAYSECS;
240 $DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id));
241 $DB->set_field('lesson', 'available', $futuretime, array('id' => $this->lesson->id));
243 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
244 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
245 $this->assertEquals('lessonopen', key($validation));
246 $this->assertCount(1, $validation);
248 // Test password.
249 $DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id));
250 $DB->set_field('lesson', 'available', 0, array('id' => $this->lesson->id));
251 $DB->set_field('lesson', 'usepassword', 1, array('id' => $this->lesson->id));
252 $DB->set_field('lesson', 'password', 'abc', array('id' => $this->lesson->id));
254 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
255 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
256 $this->assertEquals('passwordprotectedlesson', key($validation));
257 $this->assertCount(1, $validation);
259 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
260 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => 'abc'], true);
261 $this->assertCount(0, $validation);
263 // Dependencies.
264 $record = new stdClass();
265 $record->course = $this->course->id;
266 $lesson2 = self::getDataGenerator()->create_module('lesson', $record);
267 $DB->set_field('lesson', 'usepassword', 0, array('id' => $this->lesson->id));
268 $DB->set_field('lesson', 'password', '', array('id' => $this->lesson->id));
269 $DB->set_field('lesson', 'dependency', $lesson->id, array('id' => $this->lesson->id));
271 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
272 $lesson->conditions = serialize((object) ['completed' => true, 'timespent' => 0, 'gradebetterthan' => 0]);
273 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
274 $this->assertEquals('completethefollowingconditions', key($validation));
275 $this->assertCount(1, $validation);
277 // Lesson withou pages.
278 $lesson = new lesson($lesson2);
279 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
280 $this->assertEquals('lessonnotready2', key($validation));
281 $this->assertCount(1, $validation);
283 // Test retakes.
284 $DB->set_field('lesson', 'dependency', 0, array('id' => $this->lesson->id));
285 $DB->set_field('lesson', 'retake', 0, array('id' => $this->lesson->id));
286 $record = [
287 'lessonid' => $this->lesson->id,
288 'userid' => $this->student->id,
289 'grade' => 100,
290 'late' => 0,
291 'completed' => 1,
293 $DB->insert_record('lesson_grades', (object) $record);
294 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
295 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
296 $this->assertEquals('noretake', key($validation));
297 $this->assertCount(1, $validation);
299 // Test time limit restriction.
300 $timenow = time();
301 // Create a timer for the current user.
302 $timer1 = new stdClass;
303 $timer1->lessonid = $this->lesson->id;
304 $timer1->userid = $this->student->id;
305 $timer1->completed = 0;
306 $timer1->starttime = $timenow - DAYSECS;
307 $timer1->lessontime = $timenow;
308 $timer1->id = $DB->insert_record("lesson_timer", $timer1);
310 // Out of time.
311 $DB->set_field('lesson', 'timelimit', HOURSECS, array('id' => $this->lesson->id));
312 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
313 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => '', 'pageid' => 1], true);
314 $this->assertEquals('eolstudentoutoftime', key($validation));
315 $this->assertCount(1, $validation);
319 * Test the get_lesson_access_information function.
321 public function test_get_lesson_access_information() {
322 global $DB;
324 $this->setUser($this->student);
325 // Add previous attempt.
326 $record = [
327 'lessonid' => $this->lesson->id,
328 'userid' => $this->student->id,
329 'grade' => 100,
330 'late' => 0,
331 'completed' => 1,
333 $DB->insert_record('lesson_grades', (object) $record);
335 $result = mod_lesson_external::get_lesson_access_information($this->lesson->id);
336 $result = external_api::clean_returnvalue(mod_lesson_external::get_lesson_access_information_returns(), $result);
337 $this->assertFalse($result['canmanage']);
338 $this->assertFalse($result['cangrade']);
339 $this->assertFalse($result['canviewreports']);
341 $this->assertFalse($result['leftduringtimedsession']);
342 $this->assertEquals(1, $result['reviewmode']);
343 $this->assertEquals(1, $result['attemptscount']);
344 $this->assertEquals(0, $result['lastpageseen']);
345 $this->assertEquals($this->page2->id, $result['firstpageid']);
346 $this->assertCount(1, $result['preventaccessreasons']);
347 $this->assertEquals('noretake', $result['preventaccessreasons'][0]['reason']);
348 $this->assertEquals(null, $result['preventaccessreasons'][0]['data']);
349 $this->assertEquals(get_string('noretake', 'lesson'), $result['preventaccessreasons'][0]['message']);
351 // Now check permissions as admin.
352 $this->setAdminUser();
353 $result = mod_lesson_external::get_lesson_access_information($this->lesson->id);
354 $result = external_api::clean_returnvalue(mod_lesson_external::get_lesson_access_information_returns(), $result);
355 $this->assertTrue($result['canmanage']);
356 $this->assertTrue($result['cangrade']);
357 $this->assertTrue($result['canviewreports']);
361 * Test test_view_lesson invalid id.
363 public function test_view_lesson_invalid_id() {
364 $this->setExpectedException('moodle_exception');
365 mod_lesson_external::view_lesson(0);
369 * Test test_view_lesson user not enrolled.
371 public function test_view_lesson_user_not_enrolled() {
372 // Test not-enrolled user.
373 $usernotenrolled = self::getDataGenerator()->create_user();
374 $this->setUser($usernotenrolled);
375 $this->setExpectedException('moodle_exception');
376 mod_lesson_external::view_lesson($this->lesson->id);
380 * Test test_view_lesson user student.
382 public function test_view_lesson_user_student() {
383 // Test user with full capabilities.
384 $this->setUser($this->student);
386 // Trigger and capture the event.
387 $sink = $this->redirectEvents();
389 $result = mod_lesson_external::view_lesson($this->lesson->id);
390 $result = external_api::clean_returnvalue(mod_lesson_external::view_lesson_returns(), $result);
391 $this->assertTrue($result['status']);
393 $events = $sink->get_events();
394 $this->assertCount(1, $events);
395 $event = array_shift($events);
397 // Checking that the event contains the expected values.
398 $this->assertInstanceOf('\mod_lesson\event\course_module_viewed', $event);
399 $this->assertEquals($this->context, $event->get_context());
400 $moodlelesson = new \moodle_url('/mod/lesson/view.php', array('id' => $this->cm->id));
401 $this->assertEquals($moodlelesson, $event->get_url());
402 $this->assertEventContextNotUsed($event);
403 $this->assertNotEmpty($event->get_name());
407 * Test test_view_lesson user missing capabilities.
409 public function test_view_lesson_user_missing_capabilities() {
410 // Test user with no capabilities.
411 // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
412 assign_capability('mod/lesson:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
413 // Empty all the caches that may be affected by this change.
414 accesslib_clear_all_caches_for_unit_testing();
415 course_modinfo::clear_instance_cache();
417 $this->setUser($this->student);
418 $this->setExpectedException('moodle_exception');
419 mod_lesson_external::view_lesson($this->lesson->id);
423 * Test for get_questions_attempts
425 public function test_get_questions_attempts() {
426 global $DB;
428 $this->setUser($this->student);
429 $attemptnumber = 1;
431 // Test lesson without page attempts.
432 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber);
433 $result = external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result);
434 $this->assertCount(0, $result['warnings']);
435 $this->assertCount(0, $result['attempts']);
437 // Create a fake attempt for the first possible answer.
438 $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id');
439 $answerid = reset($p2answers)->id;
441 $newpageattempt = [
442 'lessonid' => $this->lesson->id,
443 'pageid' => $this->page2->id,
444 'userid' => $this->student->id,
445 'answerid' => $answerid,
446 'retry' => $attemptnumber,
447 'correct' => 1,
448 'useranswer' => '1',
449 'timeseen' => time(),
451 $DB->insert_record('lesson_attempts', (object) $newpageattempt);
453 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber);
454 $result = external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result);
455 $this->assertCount(0, $result['warnings']);
456 $this->assertCount(1, $result['attempts']);
458 $newpageattempt['id'] = $result['attempts'][0]['id'];
459 $this->assertEquals($newpageattempt, $result['attempts'][0]);
461 // Test filtering. Only correct.
462 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, true);
463 $result = external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result);
464 $this->assertCount(0, $result['warnings']);
465 $this->assertCount(1, $result['attempts']);
467 // Test filtering. Only correct only for page 2.
468 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, true, $this->page2->id);
469 $result = external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result);
470 $this->assertCount(0, $result['warnings']);
471 $this->assertCount(1, $result['attempts']);
473 // Teacher retrieve student page attempts.
474 $this->setUser($this->teacher);
475 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, false, null, $this->student->id);
476 $result = external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result);
477 $this->assertCount(0, $result['warnings']);
478 $this->assertCount(1, $result['attempts']);
480 // Test exception.
481 $this->setUser($this->student);
482 $this->expectException('moodle_exception');
483 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, false, null, $this->teacher->id);
487 * Test get user grade.
489 public function test_get_user_grade() {
490 global $DB;
492 // Add grades for the user.
493 $newgrade = [
494 'lessonid' => $this->lesson->id,
495 'userid' => $this->student->id,
496 'grade' => 50,
497 'late' => 0,
498 'completed' => time(),
500 $DB->insert_record('lesson_grades', (object) $newgrade);
502 $newgrade = [
503 'lessonid' => $this->lesson->id,
504 'userid' => $this->student->id,
505 'grade' => 100,
506 'late' => 0,
507 'completed' => time(),
509 $DB->insert_record('lesson_grades', (object) $newgrade);
511 $this->setUser($this->student);
513 // Test lesson without multiple attemps. The first result must be returned.
514 $result = mod_lesson_external::get_user_grade($this->lesson->id);
515 $result = external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result);
516 $this->assertCount(0, $result['warnings']);
517 $this->assertEquals(50, $result['grade']);
518 $this->assertEquals('50.00', $result['formattedgrade']);
520 // With retakes. By default average.
521 $DB->set_field('lesson', 'retake', 1, array('id' => $this->lesson->id));
522 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->student->id);
523 $result = external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result);
524 $this->assertCount(0, $result['warnings']);
525 $this->assertEquals(75, $result['grade']);
526 $this->assertEquals('75.00', $result['formattedgrade']);
528 // With retakes. With max grade setting.
529 $DB->set_field('lesson', 'usemaxgrade', 1, array('id' => $this->lesson->id));
530 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->student->id);
531 $result = external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result);
532 $this->assertCount(0, $result['warnings']);
533 $this->assertEquals(100, $result['grade']);
534 $this->assertEquals('100.00', $result['formattedgrade']);
536 // Test as teacher we get the same result.
537 $this->setUser($this->teacher);
538 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->student->id);
539 $result = external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result);
540 $this->assertCount(0, $result['warnings']);
541 $this->assertEquals(100, $result['grade']);
542 $this->assertEquals('100.00', $result['formattedgrade']);
544 // Test exception. As student try to retrieve grades from teacher.
545 $this->setUser($this->student);
546 $this->expectException('moodle_exception');
547 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->teacher->id);
551 * Test get_user_attempt_grade
553 public function test_get_user_attempt_grade() {
554 global $DB;
556 // Create a fake attempt for the first possible answer.
557 $attemptnumber = 1;
558 $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id');
559 $answerid = reset($p2answers)->id;
561 $newpageattempt = [
562 'lessonid' => $this->lesson->id,
563 'pageid' => $this->page2->id,
564 'userid' => $this->student->id,
565 'answerid' => $answerid,
566 'retry' => $attemptnumber,
567 'correct' => 1,
568 'useranswer' => '1',
569 'timeseen' => time(),
571 $DB->insert_record('lesson_attempts', (object) $newpageattempt);
573 // Test first without custom scoring. All questions receive the same value if correctly responsed.
574 $DB->set_field('lesson', 'custom', 0, array('id' => $this->lesson->id));
575 $this->setUser($this->student);
576 $result = mod_lesson_external::get_user_attempt_grade($this->lesson->id, $attemptnumber, $this->student->id);
577 $result = external_api::clean_returnvalue(mod_lesson_external::get_user_attempt_grade_returns(), $result);
578 $this->assertCount(0, $result['warnings']);
579 $this->assertEquals(1, $result['nquestions']);
580 $this->assertEquals(1, $result['attempts']);
581 $this->assertEquals(1, $result['total']);
582 $this->assertEquals(1, $result['earned']);
583 $this->assertEquals(100, $result['grade']);
584 $this->assertEquals(0, $result['nmanual']);
585 $this->assertEquals(0, $result['manualpoints']);
587 // With custom scoring, in this case, we don't retrieve any values since we are using questions without particular score.
588 $DB->set_field('lesson', 'custom', 1, array('id' => $this->lesson->id));
589 $result = mod_lesson_external::get_user_attempt_grade($this->lesson->id, $attemptnumber, $this->student->id);
590 $result = external_api::clean_returnvalue(mod_lesson_external::get_user_attempt_grade_returns(), $result);
591 $this->assertCount(0, $result['warnings']);
592 $this->assertEquals(1, $result['nquestions']);
593 $this->assertEquals(1, $result['attempts']);
594 $this->assertEquals(0, $result['total']);
595 $this->assertEquals(0, $result['earned']);
596 $this->assertEquals(0, $result['grade']);
597 $this->assertEquals(0, $result['nmanual']);
598 $this->assertEquals(0, $result['manualpoints']);
602 * Test get_content_pages_viewed
604 public function test_get_content_pages_viewed() {
605 global $DB;
607 // Create another content pages.
608 $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
609 $page3 = $lessongenerator->create_content($this->lesson);
611 $branch1 = new stdClass;
612 $branch1->lessonid = $this->lesson->id;
613 $branch1->userid = $this->student->id;
614 $branch1->pageid = $this->page1->id;
615 $branch1->retry = 1;
616 $branch1->flag = 0;
617 $branch1->timeseen = time();
618 $branch1->nextpageid = $page3->id;
619 $branch1->id = $DB->insert_record("lesson_branch", $branch1);
621 $branch2 = new stdClass;
622 $branch2->lessonid = $this->lesson->id;
623 $branch2->userid = $this->student->id;
624 $branch2->pageid = $page3->id;
625 $branch2->retry = 1;
626 $branch2->flag = 0;
627 $branch2->timeseen = time() + 1;
628 $branch2->nextpageid = 0;
629 $branch2->id = $DB->insert_record("lesson_branch", $branch2);
631 // Test first attempt.
632 $result = mod_lesson_external::get_content_pages_viewed($this->lesson->id, 1, $this->student->id);
633 $result = external_api::clean_returnvalue(mod_lesson_external::get_content_pages_viewed_returns(), $result);
634 $this->assertCount(0, $result['warnings']);
635 $this->assertCount(2, $result['pages']);
636 foreach ($result['pages'] as $page) {
637 if ($page['id'] == $branch1->id) {
638 $this->assertEquals($branch1, (object) $page);
639 } else {
640 $this->assertEquals($branch2, (object) $page);
644 // Attempt without pages viewed.
645 $result = mod_lesson_external::get_content_pages_viewed($this->lesson->id, 3, $this->student->id);
646 $result = external_api::clean_returnvalue(mod_lesson_external::get_content_pages_viewed_returns(), $result);
647 $this->assertCount(0, $result['warnings']);
648 $this->assertCount(0, $result['pages']);
652 * Test get_user_timers
654 public function test_get_user_timers() {
655 global $DB;
657 // Create a couple of timers for the current user.
658 $timer1 = new stdClass;
659 $timer1->lessonid = $this->lesson->id;
660 $timer1->userid = $this->student->id;
661 $timer1->completed = 1;
662 $timer1->starttime = time() - WEEKSECS;
663 $timer1->lessontime = time();
664 $timer1->id = $DB->insert_record("lesson_timer", $timer1);
666 $timer2 = new stdClass;
667 $timer2->lessonid = $this->lesson->id;
668 $timer2->userid = $this->student->id;
669 $timer2->completed = 0;
670 $timer2->starttime = time() - DAYSECS;
671 $timer2->lessontime = time() + 1;
672 $timer2->id = $DB->insert_record("lesson_timer", $timer2);
674 // Test retrieve timers.
675 $result = mod_lesson_external::get_user_timers($this->lesson->id, $this->student->id);
676 $result = external_api::clean_returnvalue(mod_lesson_external::get_user_timers_returns(), $result);
677 $this->assertCount(0, $result['warnings']);
678 $this->assertCount(2, $result['timers']);
679 foreach ($result['timers'] as $timer) {
680 if ($timer['id'] == $timer1->id) {
681 $this->assertEquals($timer1, (object) $timer);
682 } else {
683 $this->assertEquals($timer2, (object) $timer);
689 * Test for get_pages
691 public function test_get_pages() {
692 global $DB;
694 $this->setAdminUser();
695 // Create another content page.
696 $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
697 $page3 = $lessongenerator->create_content($this->lesson);
699 $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id');
701 // Add files everywhere.
702 $fs = get_file_storage();
704 $filerecord = array(
705 'contextid' => $this->context->id,
706 'component' => 'mod_lesson',
707 'filearea' => 'page_contents',
708 'itemid' => $this->page1->id,
709 'filepath' => '/',
710 'filename' => 'file.txt',
711 'sortorder' => 1
713 $fs->create_file_from_string($filerecord, 'Test resource file');
715 $filerecord['itemid'] = $page3->id;
716 $fs->create_file_from_string($filerecord, 'Test resource file');
718 foreach ($p2answers as $answer) {
719 $filerecord['filearea'] = 'page_answers';
720 $filerecord['itemid'] = $answer->id;
721 $fs->create_file_from_string($filerecord, 'Test resource file');
723 $filerecord['filearea'] = 'page_responses';
724 $fs->create_file_from_string($filerecord, 'Test resource file');
727 $result = mod_lesson_external::get_pages($this->lesson->id);
728 $result = external_api::clean_returnvalue(mod_lesson_external::get_pages_returns(), $result);
729 $this->assertCount(0, $result['warnings']);
730 $this->assertCount(3, $result['pages']);
732 // Check pages and values.
733 foreach ($result['pages'] as $page) {
734 if ($page['id'] == $this->page2->id) {
735 $this->assertEquals(2 * count($page['answerids']), $page['filescount']);
736 $this->assertEquals('Lesson TF question 2', $page['title']);
737 } else {
738 // Content page, no answers.
739 $this->assertCount(0, $page['answerids']);
740 $this->assertEquals(1, $page['filescount']);
744 // Now, as student without pages menu.
745 $this->setUser($this->student);
746 $DB->set_field('lesson', 'displayleft', 0, array('id' => $this->lesson->id));
747 $result = mod_lesson_external::get_pages($this->lesson->id);
748 $result = external_api::clean_returnvalue(mod_lesson_external::get_pages_returns(), $result);
749 $this->assertCount(0, $result['warnings']);
750 $this->assertCount(3, $result['pages']);
752 foreach ($result['pages'] as $page) {
753 $this->assertArrayNotHasKey('title', $page);
758 * Test launch_attempt. Time restrictions already tested in test_validate_attempt.
760 public function test_launch_attempt() {
761 global $DB, $SESSION;
763 // Test time limit restriction.
764 $timenow = time();
765 // Create a timer for the current user.
766 $timer1 = new stdClass;
767 $timer1->lessonid = $this->lesson->id;
768 $timer1->userid = $this->student->id;
769 $timer1->completed = 0;
770 $timer1->starttime = $timenow;
771 $timer1->lessontime = $timenow;
772 $timer1->id = $DB->insert_record("lesson_timer", $timer1);
774 $DB->set_field('lesson', 'timelimit', 30, array('id' => $this->lesson->id));
776 unset($SESSION->lesson_messages);
777 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1);
778 $result = external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result);
780 $this->assertCount(0, $result['warnings']);
781 $this->assertCount(2, $result['messages']);
782 $messages = [];
783 foreach ($result['messages'] as $message) {
784 $messages[] = $message['type'];
786 sort($messages);
787 $this->assertEquals(['center', 'notifyproblem'], $messages);
791 * Test launch_attempt not finished forcing review mode.
793 public function test_launch_attempt_not_finished_in_review_mode() {
794 global $DB, $SESSION;
796 // Create a timer for the current user.
797 $timenow = time();
798 $timer1 = new stdClass;
799 $timer1->lessonid = $this->lesson->id;
800 $timer1->userid = $this->student->id;
801 $timer1->completed = 0;
802 $timer1->starttime = $timenow;
803 $timer1->lessontime = $timenow;
804 $timer1->id = $DB->insert_record("lesson_timer", $timer1);
806 unset($SESSION->lesson_messages);
807 $this->setUser($this->teacher);
808 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true);
809 $result = external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result);
810 // Everything ok as teacher.
811 $this->assertCount(0, $result['warnings']);
812 $this->assertCount(0, $result['messages']);
813 // Should fails as student.
814 $this->setUser($this->student);
815 // Now, try to review this attempt. We should not be able because is a non-finished attempt.
816 $this->setExpectedException('moodle_exception');
817 mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true);
821 * Test launch_attempt just finished forcing review mode.
823 public function test_launch_attempt_just_finished_in_review_mode() {
824 global $DB, $SESSION, $USER;
826 // Create a timer for the current user.
827 $timenow = time();
828 $timer1 = new stdClass;
829 $timer1->lessonid = $this->lesson->id;
830 $timer1->userid = $this->student->id;
831 $timer1->completed = 1;
832 $timer1->starttime = $timenow;
833 $timer1->lessontime = $timenow;
834 $timer1->id = $DB->insert_record("lesson_timer", $timer1);
836 // Create attempt.
837 $newpageattempt = [
838 'lessonid' => $this->lesson->id,
839 'pageid' => $this->page2->id,
840 'userid' => $this->student->id,
841 'answerid' => 0,
842 'retry' => 1,
843 'correct' => 1,
844 'useranswer' => '1',
845 'timeseen' => time(),
847 $DB->insert_record('lesson_attempts', (object) $newpageattempt);
848 // Create grade.
849 $record = [
850 'lessonid' => $this->lesson->id,
851 'userid' => $this->student->id,
852 'grade' => 100,
853 'late' => 0,
854 'completed' => 1,
856 $DB->insert_record('lesson_grades', (object) $record);
858 unset($SESSION->lesson_messages);
859 $this->setUser($this->teacher);
860 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true);
861 $result = external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result);
862 // Everything ok as teacher.
863 $this->assertCount(0, $result['warnings']);
864 $this->assertCount(0, $result['messages']);
866 $this->setUser($this->student);
867 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true);
868 $result = external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result);
869 // Everything ok as student.
870 $this->assertCount(0, $result['warnings']);
871 $this->assertCount(0, $result['messages']);
875 * Test launch_attempt not just finished forcing review mode.
877 public function test_launch_attempt_not_just_finished_in_review_mode() {
878 global $DB, $CFG, $SESSION;
880 // Create a timer for the current user.
881 $timenow = time();
882 $timer1 = new stdClass;
883 $timer1->lessonid = $this->lesson->id;
884 $timer1->userid = $this->student->id;
885 $timer1->completed = 1;
886 $timer1->starttime = $timenow - DAYSECS;
887 $timer1->lessontime = $timenow - $CFG->sessiontimeout - HOURSECS;
888 $timer1->id = $DB->insert_record("lesson_timer", $timer1);
890 unset($SESSION->lesson_messages);
892 // Everything ok as teacher.
893 $this->setUser($this->teacher);
894 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true);
895 $result = external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result);
896 $this->assertCount(0, $result['warnings']);
897 $this->assertCount(0, $result['messages']);
899 // Fail as student.
900 $this->setUser($this->student);
901 $this->setExpectedException('moodle_exception');
902 mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true);