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