See Release Notes
Long Term Support Release
Differences Between: [Versions 311 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
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/>. 16 17 /** 18 * Lesson module external functions tests 19 * 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 25 */ 26 27 namespace mod_lesson\external; 28 29 use externallib_advanced_testcase; 30 use mod_lesson_external; 31 use lesson; 32 33 defined('MOODLE_INTERNAL') || die(); 34 35 global $CFG; 36 37 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 38 require_once($CFG->dirroot . '/mod/lesson/locallib.php'); 39 40 /** 41 * Silly class to access mod_lesson_external internal methods. 42 * 43 * @package mod_lesson 44 * @copyright 2017 Juan Leyva <juan@moodle.com> 45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 46 * @since Moodle 3.3 47 */ 48 class testable_mod_lesson_external extends mod_lesson_external { 49 50 /** 51 * Validates a new attempt. 52 * 53 * @param lesson $lesson lesson instance 54 * @param array $params request parameters 55 * @param boolean $return whether to return the errors or throw exceptions 56 * @return [array the errors (if return set to true) 57 * @since Moodle 3.3 58 */ 59 public static function validate_attempt(lesson $lesson, $params, $return = false) { 60 return parent::validate_attempt($lesson, $params, $return); 61 } 62 } 63 64 /** 65 * Lesson module external functions tests 66 * 67 * @package mod_lesson 68 * @category external 69 * @copyright 2017 Juan Leyva <juan@moodle.com> 70 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 71 * @since Moodle 3.3 72 */ 73 class external_test extends externallib_advanced_testcase { 74 75 /** 76 * Set up for every test 77 */ 78 public function setUp(): void { 79 global $DB; 80 $this->resetAfterTest(); 81 $this->setAdminUser(); 82 83 // Setup test data. 84 $this->course = $this->getDataGenerator()->create_course(); 85 $this->lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $this->course->id)); 86 $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson'); 87 $this->page1 = $lessongenerator->create_content($this->lesson); 88 $this->page2 = $lessongenerator->create_question_truefalse($this->lesson); 89 $this->context = \context_module::instance($this->lesson->cmid); 90 $this->cm = get_coursemodule_from_instance('lesson', $this->lesson->id); 91 92 // Create users. 93 $this->student = self::getDataGenerator()->create_user(); 94 $this->teacher = self::getDataGenerator()->create_user(); 95 96 // Users enrolments. 97 $this->studentrole = $DB->get_record('role', array('shortname' => 'student')); 98 $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 99 $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual'); 100 $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual'); 101 } 102 103 104 /** 105 * Test test_mod_lesson_get_lessons_by_courses 106 */ 107 public function test_mod_lesson_get_lessons_by_courses() { 108 global $DB, $CFG; 109 require_once($CFG->libdir . '/externallib.php'); 110 111 // Create additional course. 112 $course2 = self::getDataGenerator()->create_course(); 113 114 // Second lesson. 115 $record = new \stdClass(); 116 $record->course = $course2->id; 117 $record->name = '<span lang="en" class="multilang">English</span><span lang="es" class="multilang">EspaƱol</span>'; 118 $lesson2 = self::getDataGenerator()->create_module('lesson', $record); 119 120 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later. 121 $enrol = enrol_get_plugin('manual'); 122 $enrolinstances = enrol_get_instances($course2->id, true); 123 foreach ($enrolinstances as $courseenrolinstance) { 124 if ($courseenrolinstance->enrol == "manual") { 125 $instance2 = $courseenrolinstance; 126 break; 127 } 128 } 129 $enrol->enrol_user($instance2, $this->student->id, $this->studentrole->id); 130 131 self::setUser($this->student); 132 133 // Enable multilang filter to on content and heading. 134 \filter_manager::reset_caches(); 135 filter_set_global_state('multilang', TEXTFILTER_ON); 136 filter_set_applies_to_strings('multilang', true); 137 // Set WS filtering. 138 $wssettings = \external_settings::get_instance(); 139 $wssettings->set_filter(true); 140 141 $returndescription = mod_lesson_external::get_lessons_by_courses_returns(); 142 143 // Create what we expect to be returned when querying the two courses. 144 // First for the student user. 145 $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang', 146 'practice', 'modattempts', 'usepassword', 'grade', 'custom', 'ongoing', 'usemaxgrade', 147 'maxanswers', 'maxattempts', 'review', 'nextpagedefault', 'feedback', 'minquestions', 148 'maxpages', 'timelimit', 'retake', 'mediafile', 'mediafiles', 'mediaheight', 'mediawidth', 149 'mediaclose', 'slideshow', 'width', 'height', 'bgcolor', 'displayleft', 'displayleftif', 150 'progressbar', 'allowofflineattempts'); 151 152 // Add expected coursemodule and data. 153 $lesson1 = $this->lesson; 154 $lesson1->coursemodule = $lesson1->cmid; 155 $lesson1->introformat = 1; 156 $lesson1->introfiles = []; 157 $lesson1->mediafiles = []; 158 $lesson1->lang = ''; 159 160 $lesson2->coursemodule = $lesson2->cmid; 161 $lesson2->introformat = 1; 162 $lesson2->introfiles = []; 163 $lesson2->mediafiles = []; 164 $lesson2->lang = ''; 165 166 $booltypes = array('practice', 'modattempts', 'usepassword', 'custom', 'ongoing', 'review', 'feedback', 'retake', 167 'slideshow', 'displayleft', 'progressbar', 'allowofflineattempts'); 168 169 foreach ($expectedfields as $field) { 170 if (in_array($field, $booltypes)) { 171 $lesson1->{$field} = (bool) $lesson1->{$field}; 172 $lesson2->{$field} = (bool) $lesson2->{$field}; 173 } 174 $expected1[$field] = $lesson1->{$field}; 175 $expected2[$field] = $lesson2->{$field}; 176 } 177 178 $expected2['name'] = 'English'; // Lang filtered expected. 179 $expectedlessons = array($expected2, $expected1); 180 181 // Call the external function passing course ids. 182 $result = mod_lesson_external::get_lessons_by_courses(array($course2->id, $this->course->id)); 183 $result = \external_api::clean_returnvalue($returndescription, $result); 184 185 $this->assertEquals($expectedlessons, $result['lessons']); 186 $this->assertCount(0, $result['warnings']); 187 188 // Call the external function without passing course id. 189 $result = mod_lesson_external::get_lessons_by_courses(); 190 $result = \external_api::clean_returnvalue($returndescription, $result); 191 $this->assertEquals($expectedlessons, $result['lessons']); 192 $this->assertCount(0, $result['warnings']); 193 194 // Unenrol user from second course and alter expected lessons. 195 $enrol->unenrol_user($instance2, $this->student->id); 196 array_shift($expectedlessons); 197 198 // Call the external function without passing course id. 199 $result = mod_lesson_external::get_lessons_by_courses(); 200 $result = \external_api::clean_returnvalue($returndescription, $result); 201 $this->assertEquals($expectedlessons, $result['lessons']); 202 203 // Call for the second course we unenrolled the user from, expected warning. 204 $result = mod_lesson_external::get_lessons_by_courses(array($course2->id)); 205 $this->assertCount(1, $result['warnings']); 206 $this->assertEquals('1', $result['warnings'][0]['warningcode']); 207 $this->assertEquals($course2->id, $result['warnings'][0]['itemid']); 208 209 // Now, try as a teacher for getting all the additional fields. 210 self::setUser($this->teacher); 211 212 $additionalfields = array('password', 'dependency', 'conditions', 'activitylink', 'available', 'deadline', 213 'timemodified', 'completionendreached', 'completiontimespent'); 214 215 foreach ($additionalfields as $field) { 216 $expectedlessons[0][$field] = $lesson1->{$field}; 217 } 218 219 $result = mod_lesson_external::get_lessons_by_courses(); 220 $result = \external_api::clean_returnvalue($returndescription, $result); 221 $this->assertEquals($expectedlessons, $result['lessons']); 222 223 // Admin also should get all the information. 224 self::setAdminUser(); 225 226 $result = mod_lesson_external::get_lessons_by_courses(array($this->course->id)); 227 $result = \external_api::clean_returnvalue($returndescription, $result); 228 $this->assertEquals($expectedlessons, $result['lessons']); 229 230 // Now, add a restriction. 231 $this->setUser($this->student); 232 $DB->set_field('lesson', 'usepassword', 1, array('id' => $lesson1->id)); 233 $DB->set_field('lesson', 'password', 'abc', array('id' => $lesson1->id)); 234 235 $lessons = mod_lesson_external::get_lessons_by_courses(array($this->course->id)); 236 $lessons = \external_api::clean_returnvalue(mod_lesson_external::get_lessons_by_courses_returns(), $lessons); 237 $this->assertFalse(isset($lessons['lessons'][0]['intro'])); 238 } 239 240 /** 241 * Test the validate_attempt function. 242 */ 243 public function test_validate_attempt() { 244 global $DB; 245 246 $this->setUser($this->student); 247 // Test deadline. 248 $oldtime = time() - DAYSECS; 249 $DB->set_field('lesson', 'deadline', $oldtime, array('id' => $this->lesson->id)); 250 251 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id))); 252 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true); 253 $this->assertEquals('lessonclosed', key($validation)); 254 $this->assertCount(1, $validation); 255 256 // Test not available yet. 257 $futuretime = time() + DAYSECS; 258 $DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id)); 259 $DB->set_field('lesson', 'available', $futuretime, array('id' => $this->lesson->id)); 260 261 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id))); 262 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true); 263 $this->assertEquals('lessonopen', key($validation)); 264 $this->assertCount(1, $validation); 265 266 // Test password. 267 $DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id)); 268 $DB->set_field('lesson', 'available', 0, array('id' => $this->lesson->id)); 269 $DB->set_field('lesson', 'usepassword', 1, array('id' => $this->lesson->id)); 270 $DB->set_field('lesson', 'password', 'abc', array('id' => $this->lesson->id)); 271 272 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id))); 273 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true); 274 $this->assertEquals('passwordprotectedlesson', key($validation)); 275 $this->assertCount(1, $validation); 276 277 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id))); 278 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => 'abc'], true); 279 $this->assertCount(0, $validation); 280 281 // Dependencies. 282 $record = new \stdClass(); 283 $record->course = $this->course->id; 284 $lesson2 = self::getDataGenerator()->create_module('lesson', $record); 285 $DB->set_field('lesson', 'usepassword', 0, array('id' => $this->lesson->id)); 286 $DB->set_field('lesson', 'password', '', array('id' => $this->lesson->id)); 287 $DB->set_field('lesson', 'dependency', $lesson->id, array('id' => $this->lesson->id)); 288 289 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id))); 290 $lesson->conditions = serialize((object) ['completed' => true, 'timespent' => 0, 'gradebetterthan' => 0]); 291 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true); 292 $this->assertEquals('completethefollowingconditions', key($validation)); 293 $this->assertCount(1, $validation); 294 295 // Lesson withou pages. 296 $lesson = new lesson($lesson2); 297 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true); 298 $this->assertEquals('lessonnotready2', key($validation)); 299 $this->assertCount(1, $validation); 300 301 // Test retakes. 302 $DB->set_field('lesson', 'dependency', 0, array('id' => $this->lesson->id)); 303 $DB->set_field('lesson', 'retake', 0, array('id' => $this->lesson->id)); 304 $record = [ 305 'lessonid' => $this->lesson->id, 306 'userid' => $this->student->id, 307 'grade' => 100, 308 'late' => 0, 309 'completed' => 1, 310 ]; 311 $DB->insert_record('lesson_grades', (object) $record); 312 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id))); 313 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true); 314 $this->assertEquals('noretake', key($validation)); 315 $this->assertCount(1, $validation); 316 317 // Test time limit restriction. 318 $timenow = time(); 319 // Create a timer for the current user. 320 $timer1 = new \stdClass; 321 $timer1->lessonid = $this->lesson->id; 322 $timer1->userid = $this->student->id; 323 $timer1->completed = 0; 324 $timer1->starttime = $timenow - DAYSECS; 325 $timer1->lessontime = $timenow; 326 $timer1->id = $DB->insert_record("lesson_timer", $timer1); 327 328 // Out of time. 329 $DB->set_field('lesson', 'timelimit', HOURSECS, array('id' => $this->lesson->id)); 330 $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id))); 331 $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => '', 'pageid' => 1], true); 332 $this->assertEquals('eolstudentoutoftime', key($validation)); 333 $this->assertCount(1, $validation); 334 } 335 336 /** 337 * Test the get_lesson_access_information function. 338 */ 339 public function test_get_lesson_access_information() { 340 global $DB; 341 342 $this->setUser($this->student); 343 // Add previous attempt. 344 $record = [ 345 'lessonid' => $this->lesson->id, 346 'userid' => $this->student->id, 347 'grade' => 100, 348 'late' => 0, 349 'completed' => 1, 350 ]; 351 $DB->insert_record('lesson_grades', (object) $record); 352 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->assertFalse($result['canmanage']); 356 $this->assertFalse($result['cangrade']); 357 $this->assertFalse($result['canviewreports']); 358 359 $this->assertFalse($result['leftduringtimedsession']); 360 $this->assertEquals(1, $result['reviewmode']); 361 $this->assertEquals(1, $result['attemptscount']); 362 $this->assertEquals(0, $result['lastpageseen']); 363 $this->assertEquals($this->page2->id, $result['firstpageid']); 364 $this->assertCount(1, $result['preventaccessreasons']); 365 $this->assertEquals('noretake', $result['preventaccessreasons'][0]['reason']); 366 $this->assertEquals(null, $result['preventaccessreasons'][0]['data']); 367 $this->assertEquals(get_string('noretake', 'lesson'), $result['preventaccessreasons'][0]['message']); 368 369 // Now check permissions as admin. 370 $this->setAdminUser(); 371 $result = mod_lesson_external::get_lesson_access_information($this->lesson->id); 372 $result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_access_information_returns(), $result); 373 $this->assertTrue($result['canmanage']); 374 $this->assertTrue($result['cangrade']); 375 $this->assertTrue($result['canviewreports']); 376 } 377 378 /** 379 * Test test_view_lesson invalid id. 380 */ 381 public function test_view_lesson_invalid_id() { 382 $this->expectException('moodle_exception'); 383 mod_lesson_external::view_lesson(0); 384 } 385 386 /** 387 * Test test_view_lesson user not enrolled. 388 */ 389 public function test_view_lesson_user_not_enrolled() { 390 // Test not-enrolled user. 391 $usernotenrolled = self::getDataGenerator()->create_user(); 392 $this->setUser($usernotenrolled); 393 $this->expectException('moodle_exception'); 394 mod_lesson_external::view_lesson($this->lesson->id); 395 } 396 397 /** 398 * Test test_view_lesson user student. 399 */ 400 public function test_view_lesson_user_student() { 401 // Test user with full capabilities. 402 $this->setUser($this->student); 403 404 // Trigger and capture the event. 405 $sink = $this->redirectEvents(); 406 407 $result = mod_lesson_external::view_lesson($this->lesson->id); 408 $result = \external_api::clean_returnvalue(mod_lesson_external::view_lesson_returns(), $result); 409 $this->assertTrue($result['status']); 410 411 $events = $sink->get_events(); 412 $this->assertCount(1, $events); 413 $event = array_shift($events); 414 415 // Checking that the event contains the expected values. 416 $this->assertInstanceOf('\mod_lesson\event\course_module_viewed', $event); 417 $this->assertEquals($this->context, $event->get_context()); 418 $moodlelesson = new \moodle_url('/mod/lesson/view.php', array('id' => $this->cm->id)); 419 $this->assertEquals($moodlelesson, $event->get_url()); 420 $this->assertEventContextNotUsed($event); 421 $this->assertNotEmpty($event->get_name()); 422 } 423 424 /** 425 * Test test_view_lesson user missing capabilities. 426 */ 427 public function test_view_lesson_user_missing_capabilities() { 428 // Test user with no capabilities. 429 // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles. 430 assign_capability('mod/lesson:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id); 431 // Empty all the caches that may be affected by this change. 432 accesslib_clear_all_caches_for_unit_testing(); 433 \course_modinfo::clear_instance_cache(); 434 435 $this->setUser($this->student); 436 $this->expectException('moodle_exception'); 437 mod_lesson_external::view_lesson($this->lesson->id); 438 } 439 440 /** 441 * Test for get_questions_attempts 442 */ 443 public function test_get_questions_attempts() { 444 global $DB; 445 446 $this->setUser($this->student); 447 $attemptnumber = 1; 448 449 // Test lesson without page attempts. 450 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber); 451 $result = \external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result); 452 $this->assertCount(0, $result['warnings']); 453 $this->assertCount(0, $result['attempts']); 454 455 // Create a fake attempt for the first possible answer. 456 $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id'); 457 $answerid = reset($p2answers)->id; 458 459 $newpageattempt = [ 460 'lessonid' => $this->lesson->id, 461 'pageid' => $this->page2->id, 462 'userid' => $this->student->id, 463 'answerid' => $answerid, 464 'retry' => $attemptnumber, 465 'correct' => 1, 466 'useranswer' => '1', 467 'timeseen' => time(), 468 ]; 469 $DB->insert_record('lesson_attempts', (object) $newpageattempt); 470 471 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber); 472 $result = \external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result); 473 $this->assertCount(0, $result['warnings']); 474 $this->assertCount(1, $result['attempts']); 475 476 $newpageattempt['id'] = $result['attempts'][0]['id']; 477 $this->assertEquals($newpageattempt, $result['attempts'][0]); 478 479 // Test filtering. Only correct. 480 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, true); 481 $result = \external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result); 482 $this->assertCount(0, $result['warnings']); 483 $this->assertCount(1, $result['attempts']); 484 485 // Test filtering. Only correct only for page 2. 486 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, true, $this->page2->id); 487 $result = \external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result); 488 $this->assertCount(0, $result['warnings']); 489 $this->assertCount(1, $result['attempts']); 490 491 // Teacher retrieve student page attempts. 492 $this->setUser($this->teacher); 493 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, false, null, $this->student->id); 494 $result = \external_api::clean_returnvalue(mod_lesson_external::get_questions_attempts_returns(), $result); 495 $this->assertCount(0, $result['warnings']); 496 $this->assertCount(1, $result['attempts']); 497 498 // Test exception. 499 $this->setUser($this->student); 500 $this->expectException('moodle_exception'); 501 $result = mod_lesson_external::get_questions_attempts($this->lesson->id, $attemptnumber, false, null, $this->teacher->id); 502 } 503 504 /** 505 * Test get user grade. 506 */ 507 public function test_get_user_grade() { 508 global $DB; 509 510 // Add grades for the user. 511 $newgrade = [ 512 'lessonid' => $this->lesson->id, 513 'userid' => $this->student->id, 514 'grade' => 50, 515 'late' => 0, 516 'completed' => time(), 517 ]; 518 $DB->insert_record('lesson_grades', (object) $newgrade); 519 520 $newgrade = [ 521 'lessonid' => $this->lesson->id, 522 'userid' => $this->student->id, 523 'grade' => 100, 524 'late' => 0, 525 'completed' => time(), 526 ]; 527 $DB->insert_record('lesson_grades', (object) $newgrade); 528 529 $this->setUser($this->student); 530 531 // Test lesson without multiple attemps. The first result must be returned. 532 $result = mod_lesson_external::get_user_grade($this->lesson->id); 533 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result); 534 $this->assertCount(0, $result['warnings']); 535 $this->assertEquals(50, $result['grade']); 536 $this->assertEquals('50.00', $result['formattedgrade']); 537 538 // With retakes. By default average. 539 $DB->set_field('lesson', 'retake', 1, array('id' => $this->lesson->id)); 540 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->student->id); 541 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result); 542 $this->assertCount(0, $result['warnings']); 543 $this->assertEquals(75, $result['grade']); 544 $this->assertEquals('75.00', $result['formattedgrade']); 545 546 // With retakes. With max grade setting. 547 $DB->set_field('lesson', 'usemaxgrade', 1, array('id' => $this->lesson->id)); 548 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->student->id); 549 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result); 550 $this->assertCount(0, $result['warnings']); 551 $this->assertEquals(100, $result['grade']); 552 $this->assertEquals('100.00', $result['formattedgrade']); 553 554 // Test as teacher we get the same result. 555 $this->setUser($this->teacher); 556 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->student->id); 557 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_grade_returns(), $result); 558 $this->assertCount(0, $result['warnings']); 559 $this->assertEquals(100, $result['grade']); 560 $this->assertEquals('100.00', $result['formattedgrade']); 561 562 // Test exception. As student try to retrieve grades from teacher. 563 $this->setUser($this->student); 564 $this->expectException('moodle_exception'); 565 $result = mod_lesson_external::get_user_grade($this->lesson->id, $this->teacher->id); 566 } 567 568 /** 569 * Test get_user_attempt_grade 570 */ 571 public function test_get_user_attempt_grade() { 572 global $DB; 573 574 // Create a fake attempt for the first possible answer. 575 $attemptnumber = 1; 576 $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id'); 577 $answerid = reset($p2answers)->id; 578 579 $newpageattempt = [ 580 'lessonid' => $this->lesson->id, 581 'pageid' => $this->page2->id, 582 'userid' => $this->student->id, 583 'answerid' => $answerid, 584 'retry' => $attemptnumber, 585 'correct' => 1, 586 'useranswer' => '1', 587 'timeseen' => time(), 588 ]; 589 $DB->insert_record('lesson_attempts', (object) $newpageattempt); 590 591 // Test first without custom scoring. All questions receive the same value if correctly responsed. 592 $DB->set_field('lesson', 'custom', 0, array('id' => $this->lesson->id)); 593 $this->setUser($this->student); 594 $result = mod_lesson_external::get_user_attempt_grade($this->lesson->id, $attemptnumber, $this->student->id); 595 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_attempt_grade_returns(), $result); 596 $this->assertCount(0, $result['warnings']); 597 $this->assertEquals(1, $result['grade']['nquestions']); 598 $this->assertEquals(1, $result['grade']['attempts']); 599 $this->assertEquals(1, $result['grade']['total']); 600 $this->assertEquals(1, $result['grade']['earned']); 601 $this->assertEquals(100, $result['grade']['grade']); 602 $this->assertEquals(0, $result['grade']['nmanual']); 603 $this->assertEquals(0, $result['grade']['manualpoints']); 604 605 // With custom scoring, in this case, we don't retrieve any values since we are using questions without particular score. 606 $DB->set_field('lesson', 'custom', 1, array('id' => $this->lesson->id)); 607 $result = mod_lesson_external::get_user_attempt_grade($this->lesson->id, $attemptnumber, $this->student->id); 608 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_attempt_grade_returns(), $result); 609 $this->assertCount(0, $result['warnings']); 610 $this->assertEquals(1, $result['grade']['nquestions']); 611 $this->assertEquals(1, $result['grade']['attempts']); 612 $this->assertEquals(0, $result['grade']['total']); 613 $this->assertEquals(0, $result['grade']['earned']); 614 $this->assertEquals(0, $result['grade']['grade']); 615 $this->assertEquals(0, $result['grade']['nmanual']); 616 $this->assertEquals(0, $result['grade']['manualpoints']); 617 } 618 619 /** 620 * Test get_content_pages_viewed 621 */ 622 public function test_get_content_pages_viewed() { 623 global $DB; 624 625 // Create another content pages. 626 $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson'); 627 $page3 = $lessongenerator->create_content($this->lesson); 628 629 $branch1 = new \stdClass; 630 $branch1->lessonid = $this->lesson->id; 631 $branch1->userid = $this->student->id; 632 $branch1->pageid = $this->page1->id; 633 $branch1->retry = 1; 634 $branch1->flag = 0; 635 $branch1->timeseen = time(); 636 $branch1->nextpageid = $page3->id; 637 $branch1->id = $DB->insert_record("lesson_branch", $branch1); 638 639 $branch2 = new \stdClass; 640 $branch2->lessonid = $this->lesson->id; 641 $branch2->userid = $this->student->id; 642 $branch2->pageid = $page3->id; 643 $branch2->retry = 1; 644 $branch2->flag = 0; 645 $branch2->timeseen = time() + 1; 646 $branch2->nextpageid = 0; 647 $branch2->id = $DB->insert_record("lesson_branch", $branch2); 648 649 // Test first attempt. 650 $result = mod_lesson_external::get_content_pages_viewed($this->lesson->id, 1, $this->student->id); 651 $result = \external_api::clean_returnvalue(mod_lesson_external::get_content_pages_viewed_returns(), $result); 652 $this->assertCount(0, $result['warnings']); 653 $this->assertCount(2, $result['pages']); 654 foreach ($result['pages'] as $page) { 655 if ($page['id'] == $branch1->id) { 656 $this->assertEquals($branch1, (object) $page); 657 } else { 658 $this->assertEquals($branch2, (object) $page); 659 } 660 } 661 662 // Attempt without pages viewed. 663 $result = mod_lesson_external::get_content_pages_viewed($this->lesson->id, 3, $this->student->id); 664 $result = \external_api::clean_returnvalue(mod_lesson_external::get_content_pages_viewed_returns(), $result); 665 $this->assertCount(0, $result['warnings']); 666 $this->assertCount(0, $result['pages']); 667 } 668 669 /** 670 * Test get_user_timers 671 */ 672 public function test_get_user_timers() { 673 global $DB; 674 675 // Create a couple of timers for the current user. 676 $timer1 = new \stdClass; 677 $timer1->lessonid = $this->lesson->id; 678 $timer1->userid = $this->student->id; 679 $timer1->completed = 1; 680 $timer1->starttime = time() - WEEKSECS; 681 $timer1->lessontime = time(); 682 $timer1->timemodifiedoffline = time(); 683 $timer1->id = $DB->insert_record("lesson_timer", $timer1); 684 685 $timer2 = new \stdClass; 686 $timer2->lessonid = $this->lesson->id; 687 $timer2->userid = $this->student->id; 688 $timer2->completed = 0; 689 $timer2->starttime = time() - DAYSECS; 690 $timer2->lessontime = time() + 1; 691 $timer2->timemodifiedoffline = time() + 1; 692 $timer2->id = $DB->insert_record("lesson_timer", $timer2); 693 694 // Test retrieve timers. 695 $result = mod_lesson_external::get_user_timers($this->lesson->id, $this->student->id); 696 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_timers_returns(), $result); 697 $this->assertCount(0, $result['warnings']); 698 $this->assertCount(2, $result['timers']); 699 foreach ($result['timers'] as $timer) { 700 if ($timer['id'] == $timer1->id) { 701 $this->assertEquals($timer1, (object) $timer); 702 } else { 703 $this->assertEquals($timer2, (object) $timer); 704 } 705 } 706 } 707 708 /** 709 * Test for get_pages 710 */ 711 public function test_get_pages() { 712 global $DB; 713 714 $this->setAdminUser(); 715 // Create another content page. 716 $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson'); 717 $page3 = $lessongenerator->create_content($this->lesson); 718 719 $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id'); 720 721 // Add files everywhere. 722 $fs = get_file_storage(); 723 724 $filerecord = array( 725 'contextid' => $this->context->id, 726 'component' => 'mod_lesson', 727 'filearea' => 'page_contents', 728 'itemid' => $this->page1->id, 729 'filepath' => '/', 730 'filename' => 'file.txt', 731 'sortorder' => 1 732 ); 733 $fs->create_file_from_string($filerecord, 'Test resource file'); 734 735 $filerecord['itemid'] = $page3->id; 736 $fs->create_file_from_string($filerecord, 'Test resource file'); 737 738 foreach ($p2answers as $answer) { 739 $filerecord['filearea'] = 'page_answers'; 740 $filerecord['itemid'] = $answer->id; 741 $fs->create_file_from_string($filerecord, 'Test resource file'); 742 743 $filerecord['filearea'] = 'page_responses'; 744 $fs->create_file_from_string($filerecord, 'Test resource file'); 745 } 746 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']); 751 752 // Check pages and values. 753 foreach ($result['pages'] as $page) { 754 if ($page['page']['id'] == $this->page2->id) { 755 $this->assertEquals(2 * count($page['answerids']), $page['filescount']); 756 $this->assertEquals('Lesson TF question 2', $page['page']['title']); 757 } else { 758 // Content page, no answers. 759 $this->assertCount(0, $page['answerids']); 760 $this->assertEquals(1, $page['filescount']); 761 } 762 } 763 764 // Now, as student without pages menu. 765 $this->setUser($this->student); 766 $DB->set_field('lesson', 'displayleft', 0, array('id' => $this->lesson->id)); 767 $result = mod_lesson_external::get_pages($this->lesson->id); 768 $result = \external_api::clean_returnvalue(mod_lesson_external::get_pages_returns(), $result); 769 $this->assertCount(0, $result['warnings']); 770 $this->assertCount(3, $result['pages']); 771 772 foreach ($result['pages'] as $page) { 773 $this->assertArrayNotHasKey('title', $page['page']); 774 } 775 } 776 777 /** 778 * Test launch_attempt. Time restrictions already tested in test_validate_attempt. 779 */ 780 public function test_launch_attempt() { 781 global $DB, $SESSION; 782 783 // Test time limit restriction. 784 $timenow = time(); 785 // Create a timer for the current user. 786 $timer1 = new \stdClass; 787 $timer1->lessonid = $this->lesson->id; 788 $timer1->userid = $this->student->id; 789 $timer1->completed = 0; 790 $timer1->starttime = $timenow; 791 $timer1->lessontime = $timenow; 792 $timer1->id = $DB->insert_record("lesson_timer", $timer1); 793 794 $DB->set_field('lesson', 'timelimit', 30, array('id' => $this->lesson->id)); 795 796 unset($SESSION->lesson_messages); 797 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1); 798 $result = \external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result); 799 800 $this->assertCount(0, $result['warnings']); 801 $this->assertCount(2, $result['messages']); 802 $messages = []; 803 foreach ($result['messages'] as $message) { 804 $messages[] = $message['type']; 805 } 806 sort($messages); 807 $this->assertEquals(['center', 'notifyproblem'], $messages); 808 } 809 810 /** 811 * Test launch_attempt not finished forcing review mode. 812 */ 813 public function test_launch_attempt_not_finished_in_review_mode() { 814 global $DB, $SESSION; 815 816 // Create a timer for the current user. 817 $timenow = time(); 818 $timer1 = new \stdClass; 819 $timer1->lessonid = $this->lesson->id; 820 $timer1->userid = $this->student->id; 821 $timer1->completed = 0; 822 $timer1->starttime = $timenow; 823 $timer1->lessontime = $timenow; 824 $timer1->id = $DB->insert_record("lesson_timer", $timer1); 825 826 unset($SESSION->lesson_messages); 827 $this->setUser($this->teacher); 828 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true); 829 $result = \external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result); 830 // Everything ok as teacher. 831 $this->assertCount(0, $result['warnings']); 832 $this->assertCount(0, $result['messages']); 833 // Should fails as student. 834 $this->setUser($this->student); 835 // Now, try to review this attempt. We should not be able because is a non-finished attempt. 836 $this->expectException('moodle_exception'); 837 mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true); 838 } 839 840 /** 841 * Test launch_attempt just finished forcing review mode. 842 */ 843 public function test_launch_attempt_just_finished_in_review_mode() { 844 global $DB, $SESSION, $USER; 845 846 // Create a timer for the current user. 847 $timenow = time(); 848 $timer1 = new \stdClass; 849 $timer1->lessonid = $this->lesson->id; 850 $timer1->userid = $this->student->id; 851 $timer1->completed = 1; 852 $timer1->starttime = $timenow; 853 $timer1->lessontime = $timenow; 854 $timer1->id = $DB->insert_record("lesson_timer", $timer1); 855 856 // Create attempt. 857 $newpageattempt = [ 858 'lessonid' => $this->lesson->id, 859 'pageid' => $this->page2->id, 860 'userid' => $this->student->id, 861 'answerid' => 0, 862 'retry' => 0, // First attempt is always 0. 863 'correct' => 1, 864 'useranswer' => '1', 865 'timeseen' => time(), 866 ]; 867 $DB->insert_record('lesson_attempts', (object) $newpageattempt); 868 // Create grade. 869 $record = [ 870 'lessonid' => $this->lesson->id, 871 'userid' => $this->student->id, 872 'grade' => 100, 873 'late' => 0, 874 'completed' => 1, 875 ]; 876 $DB->insert_record('lesson_grades', (object) $record); 877 878 unset($SESSION->lesson_messages); 879 880 $this->setUser($this->student); 881 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', $this->page2->id, true); 882 $result = \external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result); 883 // Everything ok as student. 884 $this->assertCount(0, $result['warnings']); 885 $this->assertCount(0, $result['messages']); 886 } 887 888 /** 889 * Test launch_attempt not just finished forcing review mode. 890 */ 891 public function test_launch_attempt_not_just_finished_in_review_mode() { 892 global $DB, $CFG, $SESSION; 893 894 // Create a timer for the current user. 895 $timenow = time(); 896 $timer1 = new \stdClass; 897 $timer1->lessonid = $this->lesson->id; 898 $timer1->userid = $this->student->id; 899 $timer1->completed = 1; 900 $timer1->starttime = $timenow - DAYSECS; 901 $timer1->lessontime = $timenow - $CFG->sessiontimeout - HOURSECS; 902 $timer1->id = $DB->insert_record("lesson_timer", $timer1); 903 904 unset($SESSION->lesson_messages); 905 906 // Everything ok as teacher. 907 $this->setUser($this->teacher); 908 $result = mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true); 909 $result = \external_api::clean_returnvalue(mod_lesson_external::launch_attempt_returns(), $result); 910 $this->assertCount(0, $result['warnings']); 911 $this->assertCount(0, $result['messages']); 912 913 // Fail as student. 914 $this->setUser($this->student); 915 $this->expectException('moodle_exception'); 916 mod_lesson_external::launch_attempt($this->lesson->id, '', 1, true); 917 } 918 919 /* 920 * Test get_page_data 921 */ 922 public function test_get_page_data() { 923 global $DB; 924 925 // Test a content page first (page1). 926 $result = mod_lesson_external::get_page_data($this->lesson->id, $this->page1->id, '', false, true); 927 $result = \external_api::clean_returnvalue(mod_lesson_external::get_page_data_returns(), $result); 928 929 $this->assertCount(0, $result['warnings']); 930 $this->assertCount(0, $result['answers']); // No answers, auto-generated content page. 931 $this->assertEmpty($result['ongoingscore']); 932 $this->assertEmpty($result['progress']); 933 $this->assertEquals($this->page1->id, $result['newpageid']); // No answers, so is pointing to the itself. 934 $this->assertEquals($this->page1->id, $result['page']['id']); 935 $this->assertEquals(0, $result['page']['nextpageid']); // Is the last page. 936 $this->assertEquals('Content', $result['page']['typestring']); 937 $this->assertEquals($this->page2->id, $result['page']['prevpageid']); // Previous page. 938 // Check contents. 939 $this->assertTrue(strpos($result['pagecontent'], $this->page1->title) !== false); 940 $this->assertTrue(strpos($result['pagecontent'], $this->page1->contents) !== false); 941 // Check menu availability. 942 $this->assertFalse($result['displaymenu']); 943 944 // Check now a page with answers (true / false) and with menu available. 945 $DB->set_field('lesson', 'displayleft', 1, array('id' => $this->lesson->id)); 946 $result = mod_lesson_external::get_page_data($this->lesson->id, $this->page2->id, '', false, true); 947 $result = \external_api::clean_returnvalue(mod_lesson_external::get_page_data_returns(), $result); 948 $this->assertCount(0, $result['warnings']); 949 $this->assertCount(2, $result['answers']); // One for true, one for false. 950 // Check menu availability. 951 $this->assertTrue($result['displaymenu']); 952 953 // Check contents. 954 $this->assertTrue(strpos($result['pagecontent'], $this->page2->contents) !== false); 955 956 $this->assertEquals(0, $result['page']['prevpageid']); // Previous page. 957 $this->assertEquals($this->page1->id, $result['page']['nextpageid']); // Next page. 958 } 959 960 /** 961 * Test get_page_data as student 962 */ 963 public function test_get_page_data_student() { 964 // Now check using a normal student account. 965 $this->setUser($this->student); 966 // First we need to launch the lesson so the timer is on. 967 mod_lesson_external::launch_attempt($this->lesson->id); 968 $result = mod_lesson_external::get_page_data($this->lesson->id, $this->page2->id, '', false, true); 969 $result = \external_api::clean_returnvalue(mod_lesson_external::get_page_data_returns(), $result); 970 $this->assertCount(0, $result['warnings']); 971 $this->assertCount(2, $result['answers']); // One for true, one for false. 972 // Check contents. 973 $this->assertTrue(strpos($result['pagecontent'], $this->page2->contents) !== false); 974 // Check we don't see answer information. 975 $this->assertArrayNotHasKey('jumpto', $result['answers'][0]); 976 $this->assertArrayNotHasKey('score', $result['answers'][0]); 977 $this->assertArrayNotHasKey('jumpto', $result['answers'][1]); 978 $this->assertArrayNotHasKey('score', $result['answers'][1]); 979 } 980 981 /** 982 * Test get_page_data without launching attempt. 983 */ 984 public function test_get_page_data_without_launch() { 985 // Now check using a normal student account. 986 $this->setUser($this->student); 987 988 $this->expectException('moodle_exception'); 989 $result = mod_lesson_external::get_page_data($this->lesson->id, $this->page2->id, '', false, true); 990 } 991 992 /** 993 * Creates an attempt for the given userwith a correct or incorrect answer and optionally finishes it. 994 * 995 * @param \stdClass $user Create an attempt for this user 996 * @param boolean $correct If the answer should be correct 997 * @param boolean $finished If we should finish the attempt 998 * @return array the result of the attempt creation or finalisation 999 */ 1000 protected function create_attempt($user, $correct = true, $finished = false) { 1001 global $DB; 1002 1003 $this->setUser($user); 1004 1005 // First we need to launch the lesson so the timer is on. 1006 mod_lesson_external::launch_attempt($this->lesson->id); 1007 1008 $DB->set_field('lesson', 'feedback', 1, array('id' => $this->lesson->id)); 1009 $DB->set_field('lesson', 'progressbar', 1, array('id' => $this->lesson->id)); 1010 $DB->set_field('lesson', 'custom', 0, array('id' => $this->lesson->id)); 1011 $DB->set_field('lesson', 'maxattempts', 3, array('id' => $this->lesson->id)); 1012 1013 $answercorrect = 0; 1014 $answerincorrect = 0; 1015 $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id'); 1016 foreach ($p2answers as $answer) { 1017 if ($answer->jumpto == 0) { 1018 $answerincorrect = $answer->id; 1019 } else { 1020 $answercorrect = $answer->id; 1021 } 1022 } 1023 1024 $data = array( 1025 array( 1026 'name' => 'answerid', 1027 'value' => $correct ? $answercorrect : $answerincorrect, 1028 ), 1029 array( 1030 'name' => '_qf__lesson_display_answer_form_truefalse', 1031 'value' => 1, 1032 ) 1033 ); 1034 $result = mod_lesson_external::process_page($this->lesson->id, $this->page2->id, $data); 1035 $result = \external_api::clean_returnvalue(mod_lesson_external::process_page_returns(), $result); 1036 1037 if ($finished) { 1038 $result = mod_lesson_external::finish_attempt($this->lesson->id); 1039 $result = \external_api::clean_returnvalue(mod_lesson_external::finish_attempt_returns(), $result); 1040 } 1041 return $result; 1042 } 1043 1044 /** 1045 * Test process_page 1046 */ 1047 public function test_process_page() { 1048 global $DB; 1049 1050 // Attempt first with incorrect response. 1051 $result = $this->create_attempt($this->student, false, false); 1052 1053 $this->assertEquals($this->page2->id, $result['newpageid']); // Same page, since the answer was incorrect. 1054 $this->assertFalse($result['correctanswer']); // Incorrect answer. 1055 $this->assertEquals(50, $result['progress']); 1056 1057 // Attempt with correct response. 1058 $result = $this->create_attempt($this->student, true, false); 1059 1060 $this->assertEquals($this->page1->id, $result['newpageid']); // Next page, the answer was correct. 1061 $this->assertTrue($result['correctanswer']); // Correct response. 1062 $this->assertFalse($result['maxattemptsreached']); // Still one attempt. 1063 $this->assertEquals(50, $result['progress']); 1064 } 1065 1066 /** 1067 * Test finish attempt not doing anything. 1068 */ 1069 public function test_finish_attempt_not_doing_anything() { 1070 1071 $this->setUser($this->student); 1072 // First we need to launch the lesson so the timer is on. 1073 mod_lesson_external::launch_attempt($this->lesson->id); 1074 1075 $result = mod_lesson_external::finish_attempt($this->lesson->id); 1076 $result = \external_api::clean_returnvalue(mod_lesson_external::finish_attempt_returns(), $result); 1077 1078 $this->assertCount(0, $result['warnings']); 1079 $returneddata = []; 1080 foreach ($result['data'] as $data) { 1081 $returneddata[$data['name']] = $data['value']; 1082 } 1083 $this->assertEquals(1, $returneddata['gradelesson']); // Graded lesson. 1084 $this->assertEquals(1, $returneddata['welldone']); // Finished correctly (even without grades). 1085 $gradeinfo = json_decode($returneddata['gradeinfo']); 1086 $expectedgradeinfo = (object) [ 1087 'nquestions' => 0, 1088 'attempts' => 0, 1089 'total' => 0, 1090 'earned' => 0, 1091 'grade' => 0, 1092 'nmanual' => 0, 1093 'manualpoints' => 0, 1094 ]; 1095 } 1096 1097 /** 1098 * Test finish attempt with correct answer. 1099 */ 1100 public function test_finish_attempt_with_correct_answer() { 1101 // Create a finished attempt. 1102 $result = $this->create_attempt($this->student, true, true); 1103 1104 $this->assertCount(0, $result['warnings']); 1105 $returneddata = []; 1106 foreach ($result['data'] as $data) { 1107 $returneddata[$data['name']] = $data['value']; 1108 } 1109 $this->assertEquals(1, $returneddata['gradelesson']); // Graded lesson. 1110 $this->assertEquals(1, $returneddata['numberofpagesviewed']); 1111 $this->assertEquals(1, $returneddata['numberofcorrectanswers']); 1112 $gradeinfo = json_decode($returneddata['gradeinfo']); 1113 $expectedgradeinfo = (object) [ 1114 'nquestions' => 1, 1115 'attempts' => 1, 1116 'total' => 1, 1117 'earned' => 1, 1118 'grade' => 100, 1119 'nmanual' => 0, 1120 'manualpoints' => 0, 1121 ]; 1122 } 1123 1124 /** 1125 * Test get_attempts_overview 1126 */ 1127 public function test_get_attempts_overview() { 1128 global $DB; 1129 1130 // Create a finished attempt with incorrect answer. 1131 $this->setCurrentTimeStart(); 1132 $this->create_attempt($this->student, false, true); 1133 1134 $this->setAdminUser(); 1135 $result = mod_lesson_external::get_attempts_overview($this->lesson->id); 1136 $result = \external_api::clean_returnvalue(mod_lesson_external::get_attempts_overview_returns(), $result); 1137 1138 // One attempt, 0 for grade (incorrect response) in overal statistics. 1139 $this->assertEquals(1, $result['data']['numofattempts']); 1140 $this->assertEquals(0, $result['data']['avescore']); 1141 $this->assertEquals(0, $result['data']['highscore']); 1142 $this->assertEquals(0, $result['data']['lowscore']); 1143 // Check one student, finished attempt, 0 for grade. 1144 $this->assertCount(1, $result['data']['students']); 1145 $this->assertEquals($this->student->id, $result['data']['students'][0]['id']); 1146 $this->assertEquals(0, $result['data']['students'][0]['bestgrade']); 1147 $this->assertCount(1, $result['data']['students'][0]['attempts']); 1148 $this->assertEquals(1, $result['data']['students'][0]['attempts'][0]['end']); 1149 $this->assertEquals(0, $result['data']['students'][0]['attempts'][0]['grade']); 1150 $this->assertTimeCurrent($result['data']['students'][0]['attempts'][0]['timestart']); 1151 $this->assertTimeCurrent($result['data']['students'][0]['attempts'][0]['timeend']); 1152 1153 // Add a new attempt (same user). 1154 sleep(1); 1155 // Allow first retake. 1156 $DB->set_field('lesson', 'retake', 1, array('id' => $this->lesson->id)); 1157 // Create a finished attempt with correct answer. 1158 $this->setCurrentTimeStart(); 1159 $this->create_attempt($this->student, true, true); 1160 1161 $this->setAdminUser(); 1162 $result = mod_lesson_external::get_attempts_overview($this->lesson->id); 1163 $result = \external_api::clean_returnvalue(mod_lesson_external::get_attempts_overview_returns(), $result); 1164 1165 // Two attempts with maximum grade. 1166 $this->assertEquals(2, $result['data']['numofattempts']); 1167 $this->assertEquals(50.00, format_float($result['data']['avescore'], 2)); 1168 $this->assertEquals(100, $result['data']['highscore']); 1169 $this->assertEquals(0, $result['data']['lowscore']); 1170 // Check one student, finished two attempts, 100 for final grade. 1171 $this->assertCount(1, $result['data']['students']); 1172 $this->assertEquals($this->student->id, $result['data']['students'][0]['id']); 1173 $this->assertEquals(100, $result['data']['students'][0]['bestgrade']); 1174 $this->assertCount(2, $result['data']['students'][0]['attempts']); 1175 foreach ($result['data']['students'][0]['attempts'] as $attempt) { 1176 if ($attempt['try'] == 0) { 1177 // First attempt, 0 for grade. 1178 $this->assertEquals(0, $attempt['grade']); 1179 } else { 1180 $this->assertEquals(100, $attempt['grade']); 1181 } 1182 } 1183 1184 // Now, add other user failed attempt. 1185 $student2 = self::getDataGenerator()->create_user(); 1186 $this->getDataGenerator()->enrol_user($student2->id, $this->course->id, $this->studentrole->id, 'manual'); 1187 $this->create_attempt($student2, false, true); 1188 1189 // Now check we have two students and the statistics changed. 1190 $this->setAdminUser(); 1191 $result = mod_lesson_external::get_attempts_overview($this->lesson->id); 1192 $result = \external_api::clean_returnvalue(mod_lesson_external::get_attempts_overview_returns(), $result); 1193 1194 // Total of 3 attempts with maximum grade. 1195 $this->assertEquals(3, $result['data']['numofattempts']); 1196 $this->assertEquals(33.33, format_float($result['data']['avescore'], 2)); 1197 $this->assertEquals(100, $result['data']['highscore']); 1198 $this->assertEquals(0, $result['data']['lowscore']); 1199 // Check students. 1200 $this->assertCount(2, $result['data']['students']); 1201 } 1202 1203 /** 1204 * Test get_attempts_overview when there aren't attempts. 1205 */ 1206 public function test_get_attempts_overview_no_attempts() { 1207 $this->setAdminUser(); 1208 $result = mod_lesson_external::get_attempts_overview($this->lesson->id); 1209 $result = \external_api::clean_returnvalue(mod_lesson_external::get_attempts_overview_returns(), $result); 1210 $this->assertCount(0, $result['warnings']); 1211 $this->assertArrayNotHasKey('data', $result); 1212 } 1213 1214 /** 1215 * Test get_user_attempt 1216 */ 1217 public function test_get_user_attempt() { 1218 global $DB; 1219 1220 // Create a finished and unfinished attempt with incorrect answer. 1221 $this->setCurrentTimeStart(); 1222 $this->create_attempt($this->student, true, true); 1223 1224 $DB->set_field('lesson', 'retake', 1, array('id' => $this->lesson->id)); 1225 sleep(1); 1226 $this->create_attempt($this->student, false, false); 1227 1228 $this->setAdminUser(); 1229 // Test first attempt finished. 1230 $result = mod_lesson_external::get_user_attempt($this->lesson->id, $this->student->id, 0); 1231 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_attempt_returns(), $result); 1232 1233 $this->assertCount(2, $result['answerpages']); // 2 pages in the lesson. 1234 $this->assertCount(2, $result['answerpages'][0]['answerdata']['answers']); // 2 possible answers in true/false. 1235 $this->assertEquals(100, $result['userstats']['grade']); // Correct answer. 1236 $this->assertEquals(1, $result['userstats']['gradeinfo']['total']); // Total correct answers. 1237 $this->assertEquals(100, $result['userstats']['gradeinfo']['grade']); // Correct answer. 1238 1239 // Check page object contains the lesson pages answered. 1240 $pagesanswered = array(); 1241 foreach ($result['answerpages'] as $answerp) { 1242 $pagesanswered[] = $answerp['page']['id']; 1243 } 1244 sort($pagesanswered); 1245 $this->assertEquals(array($this->page1->id, $this->page2->id), $pagesanswered); 1246 1247 // Test second attempt unfinished. 1248 $result = mod_lesson_external::get_user_attempt($this->lesson->id, $this->student->id, 1); 1249 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_attempt_returns(), $result); 1250 1251 $this->assertCount(2, $result['answerpages']); // 2 pages in the lesson. 1252 $this->assertCount(2, $result['answerpages'][0]['answerdata']['answers']); // 2 possible answers in true/false. 1253 $this->assertArrayNotHasKey('gradeinfo', $result['userstats']); // No grade info since it not finished. 1254 1255 // Check as student I can get this information for only me. 1256 $this->setUser($this->student); 1257 // Test first attempt finished. 1258 $result = mod_lesson_external::get_user_attempt($this->lesson->id, $this->student->id, 0); 1259 $result = \external_api::clean_returnvalue(mod_lesson_external::get_user_attempt_returns(), $result); 1260 1261 $this->assertCount(2, $result['answerpages']); // 2 pages in the lesson. 1262 $this->assertCount(2, $result['answerpages'][0]['answerdata']['answers']); // 2 possible answers in true/false. 1263 $this->assertEquals(100, $result['userstats']['grade']); // Correct answer. 1264 $this->assertEquals(1, $result['userstats']['gradeinfo']['total']); // Total correct answers. 1265 $this->assertEquals(100, $result['userstats']['gradeinfo']['grade']); // Correct answer. 1266 1267 $this->expectException('moodle_exception'); 1268 $result = mod_lesson_external::get_user_attempt($this->lesson->id, $this->teacher->id, 0); 1269 } 1270 1271 /** 1272 * Test get_pages_possible_jumps 1273 */ 1274 public function test_get_pages_possible_jumps() { 1275 $this->setAdminUser(); 1276 $result = mod_lesson_external::get_pages_possible_jumps($this->lesson->id); 1277 $result = \external_api::clean_returnvalue(mod_lesson_external::get_pages_possible_jumps_returns(), $result); 1278 1279 $this->assertCount(0, $result['warnings']); 1280 $this->assertCount(3, $result['jumps']); // 3 jumps, 2 from the question page and 1 from the content. 1281 foreach ($result['jumps'] as $jump) { 1282 if ($jump['answerid'] != 0) { 1283 // Check only pages with answers. 1284 if ($jump['jumpto'] == 0) { 1285 $this->assertEquals($jump['pageid'], $jump['calculatedjump']); // 0 means to jump to current page. 1286 } else { 1287 // Question is configured to jump to next page if correct. 1288 $this->assertEquals($this->page1->id, $jump['calculatedjump']); 1289 } 1290 } 1291 } 1292 } 1293 1294 /** 1295 * Test get_pages_possible_jumps when offline attemps are disabled for a normal user 1296 */ 1297 public function test_get_pages_possible_jumps_with_offlineattemps_disabled() { 1298 $this->setUser($this->student->id); 1299 $result = mod_lesson_external::get_pages_possible_jumps($this->lesson->id); 1300 $result = \external_api::clean_returnvalue(mod_lesson_external::get_pages_possible_jumps_returns(), $result); 1301 $this->assertCount(0, $result['jumps']); 1302 } 1303 1304 /** 1305 * Test get_pages_possible_jumps when offline attemps are enabled for a normal user 1306 */ 1307 public function test_get_pages_possible_jumps_with_offlineattemps_enabled() { 1308 global $DB; 1309 1310 $DB->set_field('lesson', 'allowofflineattempts', 1, array('id' => $this->lesson->id)); 1311 $this->setUser($this->student->id); 1312 $result = mod_lesson_external::get_pages_possible_jumps($this->lesson->id); 1313 $result = \external_api::clean_returnvalue(mod_lesson_external::get_pages_possible_jumps_returns(), $result); 1314 $this->assertCount(3, $result['jumps']); 1315 } 1316 1317 /* 1318 * Test get_lesson user student. 1319 */ 1320 public function test_get_lesson_user_student() { 1321 // Test user with full capabilities. 1322 $this->setUser($this->student); 1323 1324 // Lesson not using password. 1325 $result = mod_lesson_external::get_lesson($this->lesson->id); 1326 $result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result); 1327 $this->assertCount(37, $result['lesson']); // Expect most of the fields. 1328 $this->assertFalse(isset($result['password'])); 1329 } 1330 1331 /** 1332 * Test get_lesson user student with missing password. 1333 */ 1334 public function test_get_lesson_user_student_with_missing_password() { 1335 global $DB; 1336 1337 // Test user with full capabilities. 1338 $this->setUser($this->student); 1339 $DB->set_field('lesson', 'usepassword', 1, array('id' => $this->lesson->id)); 1340 $DB->set_field('lesson', 'password', 'abc', array('id' => $this->lesson->id)); 1341 1342 // Lesson not using password. 1343 $result = mod_lesson_external::get_lesson($this->lesson->id); 1344 $result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result); 1345 $this->assertCount(7, $result['lesson']); // Expect just this few fields. 1346 $this->assertFalse(isset($result['intro'])); 1347 } 1348 1349 /** 1350 * Test get_lesson user student with correct password. 1351 */ 1352 public function test_get_lesson_user_student_with_correct_password() { 1353 global $DB; 1354 // Test user with full capabilities. 1355 $this->setUser($this->student); 1356 $password = 'abc'; 1357 $DB->set_field('lesson', 'usepassword', 1, array('id' => $this->lesson->id)); 1358 $DB->set_field('lesson', 'password', $password, array('id' => $this->lesson->id)); 1359 1360 // Lesson not using password. 1361 $result = mod_lesson_external::get_lesson($this->lesson->id, $password); 1362 $result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result); 1363 $this->assertCount(37 , $result['lesson']); 1364 $this->assertFalse(isset($result['intro'])); 1365 } 1366 1367 /** 1368 * Test get_lesson teacher. 1369 */ 1370 public function test_get_lesson_teacher() { 1371 global $DB; 1372 // Test user with full capabilities. 1373 $this->setUser($this->teacher); 1374 $password = 'abc'; 1375 $DB->set_field('lesson', 'usepassword', 1, array('id' => $this->lesson->id)); 1376 $DB->set_field('lesson', 'password', $password, array('id' => $this->lesson->id)); 1377 1378 // Lesson not passing a valid password (but we are teachers, we should see all the info). 1379 $result = mod_lesson_external::get_lesson($this->lesson->id); 1380 $result = \external_api::clean_returnvalue(mod_lesson_external::get_lesson_returns(), $result); 1381 $this->assertCount(46, $result['lesson']); // Expect all the fields. 1382 $this->assertEquals($result['lesson']['password'], $password); 1383 } 1384 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body