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