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