Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 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 namespace mod_h5pactivity\xapi; 18 19 use \core_xapi\local\statement; 20 use \core_xapi\local\statement\item_agent; 21 use \core_xapi\local\statement\item_activity; 22 use \core_xapi\local\statement\item_definition; 23 use \core_xapi\local\statement\item_verb; 24 use \core_xapi\local\statement\item_result; 25 use context_module; 26 use core_xapi\test_helper; 27 use stdClass; 28 29 /** 30 * Attempt tests class for mod_h5pactivity. 31 * 32 * @package mod_h5pactivity 33 * @category test 34 * @copyright 2020 Ferran Recio <ferran@moodle.com> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 * @covers \mod_h5pactivity\xapi\handler 37 */ 38 class handler_test extends \advanced_testcase { 39 40 /** 41 * Setup to ensure that fixtures are loaded. 42 */ 43 public static function setUpBeforeClass(): void { 44 global $CFG; 45 require_once($CFG->dirroot.'/lib/xapi/tests/helper.php'); 46 } 47 48 /** 49 * Generate a valid scenario for each tests. 50 * 51 * @return stdClass an object with all scenario data in it 52 */ 53 private function generate_testing_scenario(): stdClass { 54 55 $this->resetAfterTest(); 56 $this->setAdminUser(); 57 58 $data = new stdClass(); 59 60 $data->course = $this->getDataGenerator()->create_course(); 61 62 // Generate 2 users, one enroled into course and one not. 63 $data->student = $this->getDataGenerator()->create_and_enrol($data->course, 'student'); 64 $data->otheruser = $this->getDataGenerator()->create_user(); 65 66 // H5P activity. 67 $data->activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $data->course]); 68 $data->context = context_module::instance($data->activity->cmid); 69 70 $data->xapihandler = handler::create('mod_h5pactivity'); 71 $this->assertNotEmpty($data->xapihandler); 72 $this->assertInstanceOf('\mod_h5pactivity\xapi\handler', $data->xapihandler); 73 74 $this->setUser($data->student); 75 76 return $data; 77 } 78 79 /** 80 * Test for xapi_handler with valid statements. 81 */ 82 public function test_xapi_handler() { 83 global $DB; 84 85 $data = $this->generate_testing_scenario(); 86 $xapihandler = $data->xapihandler; 87 $context = $data->context; 88 $student = $data->student; 89 $otheruser = $data->otheruser; 90 91 // Check we have 0 entries in the attempts tables. 92 $count = $DB->count_records('h5pactivity_attempts'); 93 $this->assertEquals(0, $count); 94 $count = $DB->count_records('h5pactivity_attempts_results'); 95 $this->assertEquals(0, $count); 96 97 $statements = $this->generate_statements($context, $student); 98 99 // Insert first statement. 100 $event = $xapihandler->statement_to_event($statements[0]); 101 $this->assertNotNull($event); 102 $count = $DB->count_records('h5pactivity_attempts'); 103 $this->assertEquals(1, $count); 104 $count = $DB->count_records('h5pactivity_attempts_results'); 105 $this->assertEquals(1, $count); 106 107 // Insert second statement. 108 $event = $xapihandler->statement_to_event($statements[1]); 109 $this->assertNotNull($event); 110 $count = $DB->count_records('h5pactivity_attempts'); 111 $this->assertEquals(1, $count); 112 $count = $DB->count_records('h5pactivity_attempts_results'); 113 $this->assertEquals(2, $count); 114 115 // Insert again first statement. 116 $event = $xapihandler->statement_to_event($statements[0]); 117 $this->assertNotNull($event); 118 $count = $DB->count_records('h5pactivity_attempts'); 119 $this->assertEquals(2, $count); 120 $count = $DB->count_records('h5pactivity_attempts_results'); 121 $this->assertEquals(3, $count); 122 123 // Insert again second statement. 124 $event = $xapihandler->statement_to_event($statements[1]); 125 $this->assertNotNull($event); 126 $count = $DB->count_records('h5pactivity_attempts'); 127 $this->assertEquals(2, $count); 128 $count = $DB->count_records('h5pactivity_attempts_results'); 129 $this->assertEquals(4, $count); 130 } 131 132 /** 133 * Testing wrong statements scenarios. 134 * 135 * @dataProvider xapi_handler_errors_data 136 * @param bool $hasverb valid verb 137 * @param bool $hasdefinition generate definition 138 * @param bool $hasresult generate result 139 * @param bool $hascontext valid context 140 * @param bool $hasuser valid user 141 * @param bool $generateattempt if generates an empty attempt 142 */ 143 public function test_xapi_handler_errors(bool $hasverb, bool $hasdefinition, bool $hasresult, 144 bool $hascontext, bool $hasuser, bool $generateattempt) { 145 global $DB, $CFG; 146 147 $data = $this->generate_testing_scenario(); 148 $xapihandler = $data->xapihandler; 149 $context = $data->context; 150 $student = $data->student; 151 $otheruser = $data->otheruser; 152 153 // Check we have 0 entries in the attempts tables. 154 $count = $DB->count_records('h5pactivity_attempts'); 155 $this->assertEquals(0, $count); 156 $count = $DB->count_records('h5pactivity_attempts_results'); 157 $this->assertEquals(0, $count); 158 159 $statement = new statement(); 160 if ($hasverb) { 161 $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed')); 162 } else { 163 $statement->set_verb(item_verb::create_from_id('cook')); 164 } 165 $definition = null; 166 if ($hasdefinition) { 167 $definition = item_definition::create_from_data((object)[ 168 'interactionType' => 'compound', 169 'correctResponsesPattern' => '1', 170 ]); 171 } 172 if ($hascontext) { 173 $statement->set_object(item_activity::create_from_id($context->id, $definition)); 174 } else { 175 $statement->set_object(item_activity::create_from_id('paella', $definition)); 176 } 177 if ($hasresult) { 178 $statement->set_result(item_result::create_from_data((object)[ 179 'completion' => true, 180 'success' => true, 181 'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1], 182 ])); 183 } 184 if ($hasuser) { 185 $statement->set_actor(item_agent::create_from_user($student)); 186 } else { 187 $statement->set_actor(item_agent::create_from_user($otheruser)); 188 } 189 190 $event = $xapihandler->statement_to_event($statement); 191 $this->assertNull($event); 192 // No enties should be generated. 193 $count = $DB->count_records('h5pactivity_attempts'); 194 $attempts = ($generateattempt) ? 1 : 0; 195 $this->assertEquals($attempts, $count); 196 $count = $DB->count_records('h5pactivity_attempts_results'); 197 $this->assertEquals(0, $count); 198 } 199 200 /** 201 * Data provider for data request creation tests. 202 * 203 * @return array 204 */ 205 public function xapi_handler_errors_data(): array { 206 return [ 207 // Invalid Definitions and results possibilities. 208 'Invalid definition and result' => [ 209 true, false, false, true, true, false 210 ], 211 'Invalid result' => [ 212 true, true, false, true, true, false 213 ], 214 'Invalid definition (generate empty attempt)' => [ 215 true, false, true, true, true, true 216 ], 217 // Invalid verb possibilities. 218 'Invalid verb, definition and result' => [ 219 false, false, false, true, true, false 220 ], 221 'Invalid verb and result' => [ 222 false, true, false, true, true, false 223 ], 224 'Invalid verb and result' => [ 225 false, false, true, true, true, false 226 ], 227 // Invalid context possibilities. 228 'Invalid definition, result and context' => [ 229 true, false, false, false, true, false 230 ], 231 'Invalid result' => [ 232 true, true, false, false, true, false 233 ], 234 'Invalid result and context' => [ 235 true, false, true, false, true, false 236 ], 237 'Invalid verb, definition result and context' => [ 238 false, false, false, false, true, false 239 ], 240 'Invalid verb, result and context' => [ 241 false, true, false, false, true, false 242 ], 243 'Invalid verb, result and context' => [ 244 false, false, true, false, true, false 245 ], 246 // Invalid user possibilities. 247 'Invalid definition, result and user' => [ 248 true, false, false, true, false, false 249 ], 250 'Invalid result and user' => [ 251 true, true, false, true, false, false 252 ], 253 'Invalid definition and user' => [ 254 true, false, true, true, false, false 255 ], 256 'Invalid verb, definition, result and user' => [ 257 false, false, false, true, false, false 258 ], 259 'Invalid verb, result and user' => [ 260 false, true, false, true, false, false 261 ], 262 'Invalid verb, result and user' => [ 263 false, false, true, true, false, false 264 ], 265 'Invalid definition, result, context and user' => [ 266 true, false, false, false, false, false 267 ], 268 'Invalid result, context and user' => [ 269 true, true, false, false, false, false 270 ], 271 'Invalid definition, context and user' => [ 272 true, false, true, false, false, false 273 ], 274 'Invalid verb, definition, result, context and user' => [ 275 false, false, false, false, false, false 276 ], 277 'Invalid verb, result, context and user' => [ 278 false, true, false, false, false, false 279 ], 280 'Invalid verb, result, context and user' => [ 281 false, false, true, false, false, false 282 ], 283 ]; 284 } 285 286 /** 287 * Test xapi_handler stored statements. 288 */ 289 public function test_stored_statements() { 290 global $DB; 291 292 $data = $this->generate_testing_scenario(); 293 $xapihandler = $data->xapihandler; 294 $context = $data->context; 295 $student = $data->student; 296 $otheruser = $data->otheruser; 297 $activity = $data->activity; 298 299 // Check we have 0 entries in the attempts tables. 300 $count = $DB->count_records('h5pactivity_attempts'); 301 $this->assertEquals(0, $count); 302 $count = $DB->count_records('h5pactivity_attempts_results'); 303 $this->assertEquals(0, $count); 304 305 $statements = $this->generate_statements($context, $student); 306 307 // Insert statements. 308 $stored = $xapihandler->process_statements($statements); 309 $this->assertCount(2, $stored); 310 $this->assertEquals(true, $stored[0]); 311 $this->assertEquals(true, $stored[1]); 312 $count = $DB->count_records('h5pactivity_attempts'); 313 $this->assertEquals(1, $count); 314 $count = $DB->count_records('h5pactivity_attempts_results'); 315 $this->assertEquals(2, $count); 316 317 // Validate stored data. 318 $attempts = $DB->get_records('h5pactivity_attempts'); 319 $attempt = array_shift($attempts); 320 $statement = $statements[0]; 321 $data = $statement->get_result()->get_data(); 322 $this->assertEquals(1, $attempt->attempt); 323 $this->assertEquals($student->id, $attempt->userid); 324 $this->assertEquals($activity->id, $attempt->h5pactivityid); 325 $this->assertEquals($data->score->raw, $attempt->rawscore); 326 $this->assertEquals($data->score->max, $attempt->maxscore); 327 $this->assertEquals($statement->get_result()->get_duration(), $attempt->duration); 328 $this->assertEquals($data->completion, $attempt->completion); 329 $this->assertEquals($data->success, $attempt->success); 330 331 $results = $DB->get_records('h5pactivity_attempts_results'); 332 foreach ($results as $result) { 333 $statement = (empty($result->subcontent)) ? $statements[0] : $statements[1]; 334 $xapiresult = $statement->get_result()->get_data(); 335 $xapiobject = $statement->get_object()->get_data(); 336 $this->assertEquals($attempt->id, $result->attemptid); 337 $this->assertEquals($xapiobject->definition->interactionType, $result->interactiontype); 338 $this->assertEquals($xapiresult->score->raw, $result->rawscore); 339 $this->assertEquals($xapiresult->score->max, $result->maxscore); 340 $this->assertEquals($statement->get_result()->get_duration(), $result->duration); 341 $this->assertEquals($xapiresult->completion, $result->completion); 342 $this->assertEquals($xapiresult->success, $result->success); 343 } 344 } 345 346 /** 347 * Returns a basic xAPI statements simulating a H5P content. 348 * 349 * @param context_module $context activity context 350 * @param stdClass $user user record 351 * @return statement[] array of xAPI statements 352 */ 353 private function generate_statements(context_module $context, stdClass $user): array { 354 $statements = []; 355 356 $statement = new statement(); 357 $statement->set_actor(item_agent::create_from_user($user)); 358 $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed')); 359 $definition = item_definition::create_from_data((object)[ 360 'interactionType' => 'compound', 361 'correctResponsesPattern' => '1', 362 ]); 363 $statement->set_object(item_activity::create_from_id($context->id, $definition)); 364 $statement->set_result(item_result::create_from_data((object)[ 365 'completion' => true, 366 'success' => true, 367 'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1], 368 'duration' => 'PT25S', 369 ])); 370 $statements[] = $statement; 371 372 $statement = new statement(); 373 $statement->set_actor(item_agent::create_from_user($user)); 374 $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed')); 375 $definition = item_definition::create_from_data((object)[ 376 'interactionType' => 'matching', 377 'correctResponsesPattern' => '1', 378 ]); 379 $statement->set_object(item_activity::create_from_id($context->id.'?subContentId=111-222-333', $definition)); 380 $statement->set_result(item_result::create_from_data((object)[ 381 'completion' => true, 382 'success' => true, 383 'score' => (object) ['min' => 0, 'max' => 1, 'raw' => 0, 'scaled' => 0], 384 'duration' => 'PT20S', 385 ])); 386 $statements[] = $statement; 387 388 return $statements; 389 } 390 391 /** 392 * Test validate_state method. 393 */ 394 public function test_validate_state(): void { 395 global $DB; 396 397 $this->resetAfterTest(); 398 399 /** @var \core_h5p_generator $generator */ 400 $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); 401 402 // Create a valid H5P activity with a valid xAPI state. 403 $course = $this->getDataGenerator()->create_course(); 404 $user = $this->getDataGenerator()->create_and_enrol($course, 'student'); 405 $this->setUser($user); 406 $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); 407 $coursecontext = \context_course::instance($course->id); 408 $activitycontext = \context_module::instance($activity->cmid); 409 $component = 'mod_h5pactivity'; 410 $filerecord = [ 411 'contextid' => $activitycontext->id, 412 'component' => $component, 413 'filearea' => 'package', 414 'itemid' => 0, 415 'filepath' => '/', 416 'filename' => 'dummy.h5p', 417 'addxapistate' => true, 418 ]; 419 $generator->generate_h5p_data(false, $filerecord); 420 421 $handler = handler::create($component); 422 // Change the method visibility for validate_state in order to test it. 423 $method = new \ReflectionMethod(handler::class, 'validate_state'); 424 $method->setAccessible(true); 425 426 // The activity id should be numeric. 427 $state = test_helper::create_state(['activity' => item_activity::create_from_id('AA')]); 428 $result = $method->invoke($handler, $state); 429 $this->assertFalse($result); 430 431 // The activity id should exist. 432 $state = test_helper::create_state(); 433 $result = $method->invoke($handler, $state); 434 $this->assertFalse($result); 435 436 // The given activity should be H5P activity. 437 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course]); 438 $state = test_helper::create_state([ 439 'activity' => item_activity::create_from_id($forum->cmid), 440 ]); 441 $result = $method->invoke($handler, $state); 442 $this->assertFalse($result); 443 444 // Tracking should be enabled for the H5P activity. 445 $state = test_helper::create_state([ 446 'activity' => item_activity::create_from_id($activitycontext->id), 447 'component' => $component, 448 ]); 449 $result = $method->invoke($handler, $state); 450 $this->assertTrue($result); 451 452 // So, when tracking is disabled, the state won't be considered valid. 453 $activity2 = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course, 'enabletracking' => 0]); 454 $activitycontext2 = \context_module::instance($activity2->cmid); 455 $state = test_helper::create_state([ 456 'activity' => item_activity::create_from_id($activitycontext2->id), 457 'component' => $component, 458 ]); 459 $result = $method->invoke($handler, $state); 460 $this->assertFalse($result); 461 462 // The user should have permission to submit. 463 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 464 assign_capability('mod/h5pactivity:submit', CAP_PROHIBIT, $studentrole->id, $coursecontext->id); 465 // Empty all the caches that may be affected by this change. 466 accesslib_clear_all_caches_for_unit_testing(); 467 \course_modinfo::clear_instance_cache(); 468 $state = test_helper::create_state([ 469 'activity' => item_activity::create_from_id($activitycontext->id), 470 'component' => $component, 471 ]); 472 $result = $method->invoke($handler, $state); 473 $this->assertFalse($result); 474 } 475 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body