Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [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 * This file contains unit test related to xAPI library. 19 * 20 * @package core_xapi 21 * @copyright 2020 Ferran Recio 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace core_xapi\external; 25 26 use core_xapi\xapi_exception; 27 use core_xapi\test_helper; 28 use core_xapi\external\post_statement; 29 use core_xapi\local\statement; 30 use core_xapi\local\statement\item_agent; 31 use core_xapi\local\statement\item_group; 32 use core_xapi\local\statement\item_verb; 33 use core_xapi\local\statement\item_activity; 34 use externallib_advanced_testcase; 35 use stdClass; 36 use external_api; 37 38 defined('MOODLE_INTERNAL') || die(); 39 40 global $CFG; 41 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 42 43 /** 44 * Unit tests for xAPI statement processing webservice. 45 * 46 * @package core_xapi 47 * @since Moodle 3.9 48 * @copyright 2020 Ferran Recio 49 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 50 */ 51 class post_statement_testcase extends externallib_advanced_testcase { 52 53 /** @var test_helper for generating valid xapi statements. */ 54 private $testhelper; 55 56 /** 57 * Setup to ensure that fixtures are loaded. 58 */ 59 public static function setupBeforeClass(): void { 60 global $CFG; 61 require_once($CFG->dirroot.'/lib/xapi/tests/helper.php'); 62 } 63 64 /** 65 * Setup test. 66 */ 67 public function setUp(): void { 68 global $CFG; 69 // We disable group actors on the test xapi_handler. 70 $CFG->xapitestforcegroupactors = false; 71 } 72 73 /** 74 * Return a xAPI external webservice class to operate. 75 * 76 * The test needs to fake a component in order to test without 77 * using a real one. This way if in the future any component 78 * implement it's xAPI handler this test will continue working. 79 * 80 * @return post_statement the external class 81 */ 82 private function get_extenal_class(): post_statement { 83 $ws = new class extends post_statement { 84 protected static function validate_component(string $component): void { 85 if ($component != 'fake_component') { 86 parent::validate_component($component); 87 } 88 } 89 }; 90 return $ws; 91 } 92 93 /** 94 * This function do all checks from a standard post_statements request. 95 * 96 * The reason for this function is because statements crafting (special in error 97 * scenarios) is complicated to do via data providers because every test need a specific 98 * testing conditions. For this reason alls tests creates a scenario and then uses this 99 * function to check the results. 100 * 101 * @param string $component component name 102 * @param mixed $data data to encode and send to post_statement 103 * @param array $expected expected results (i empty an exception is expected) 104 */ 105 private function post_statements_data(string $component, $data, array $expected) { 106 global $USER; 107 108 $testhelper = new test_helper(); 109 $testhelper->init_log(); 110 111 // If no result is expected we will just incur in exception. 112 if (empty($expected)) { 113 $this->expectException(xapi_exception::class); 114 } else { 115 $this->preventResetByRollback(); // Logging waits till the transaction gets committed. 116 } 117 118 $json = json_encode($data); 119 120 $external = $this->get_extenal_class(); 121 $result = $external::execute($component, $json); 122 $result = external_api::clean_returnvalue($external::execute_returns(), $result); 123 124 // Check results. 125 $this->assertCount(count($expected), $result); 126 foreach ($expected as $key => $expect) { 127 $this->assertEquals($expect, $result[$key]); 128 } 129 130 // Check log entries. 131 $log = $testhelper->get_last_log_entry(); 132 $this->assertNotEmpty($log); 133 134 // Validate statement information on log. 135 $value = $log->get_name(); 136 $this->assertEquals($value, 'xAPI test statement'); 137 $value = $log->get_description(); 138 // Due to logstore limitation, event must use a real component (core_xapi). 139 $this->assertEquals($value, "User '{$USER->id}' send a statement to component 'core_xapi'"); 140 } 141 142 /** 143 * Return a valid statement object with the params passed. 144 * 145 * All tests are based on craft different types os statements. This function 146 * is made to provent redundant code on the test. 147 * 148 * @param array $items array of overriden statement items (default []) 149 * @return statement the resulting statement 150 */ 151 private function get_valid_statement(array $items = []): statement { 152 global $USER; 153 154 $actor = $items['actor'] ?? item_agent::create_from_user($USER); 155 $verb = $items['verb'] ?? item_verb::create_from_id('cook'); 156 $object = $items['object'] ?? item_activity::create_from_id('paella'); 157 158 $statement = new statement(); 159 $statement->set_actor($actor); 160 $statement->set_verb($verb); 161 $statement->set_object($object); 162 163 return $statement; 164 } 165 166 /** 167 * Testing different component names on valid statements. 168 * 169 * @dataProvider components_provider 170 * @param string $component component name 171 * @param array $expected expected results 172 */ 173 public function test_component_names(string $component, array $expected) { 174 175 $this->resetAfterTest(); 176 177 // Scenario. 178 $this->setAdminUser(); 179 180 // Perform test. 181 $data = $this->get_valid_statement(); 182 $this->post_statements_data ($component, $data, $expected); 183 } 184 185 /** 186 * Data provider for the test_component_names tests. 187 * 188 * @return array 189 */ 190 public function components_provider() : array { 191 return [ 192 'Inexistent component' => [ 193 'inexistent_component', [] 194 ], 195 'Compatible component' => [ 196 'fake_component', [true] 197 ], 198 'Incompatible component' => [ 199 'core_xapi', [] 200 ], 201 ]; 202 } 203 204 /** 205 * Testing raw JSON encoding. 206 * 207 * This test is used for wrong json format and empty structures. 208 * 209 * @dataProvider invalid_json_provider 210 * @param string $json json string to send 211 */ 212 public function test_invalid_json(string $json) { 213 214 $this->resetAfterTest(); 215 216 // Scenario. 217 $this->setAdminUser(); 218 219 // Perform test. 220 $testhelper = new test_helper(); 221 $testhelper->init_log(); 222 223 // If no result is expected we will just incur in exception. 224 $this->expectException(xapi_exception::class); 225 226 $external = $this->get_extenal_class(); 227 $result = $external::execute('fake_component', $json); 228 $result = external_api::clean_returnvalue($external::execute_returns(), $result); 229 } 230 231 /** 232 * Data provider for the test_components tests. 233 * 234 * @return array 235 */ 236 public function invalid_json_provider() : array { 237 return [ 238 'Wrong json' => [ 239 'This is not { a json object /' 240 ], 241 'Empty string json' => [ 242 '' 243 ], 244 'Empty array json' => [ 245 '[]' 246 ], 247 'Invalid single statement json' => [ 248 '{"actor":{"objectType":"Agent","mbox":"noemail@moodle.org"},"verb":{"id":"InvalidVerb"}' 249 .',"object":{"objectType":"Activity","id":"somethingwrong"}}' 250 ], 251 'Invalid multiple statement json' => [ 252 '[{"actor":{"objectType":"Agent","mbox":"noemail@moodle.org"},"verb":{"id":"InvalidVerb"}' 253 .',"object":{"objectType":"Activity","id":"somethingwrong"}}]' 254 ], 255 ]; 256 } 257 258 /** 259 * Testing agent (user) statements. 260 * 261 * This function test several scenarios using different combinations 262 * of statement rejection motives. Some motives produces a full batch 263 * rejection (exception) and other can leed to indivual rejection on 264 * each statement. For example,try to post a statement without $USER 265 * in it produces a full batch rejection, while using an invalid 266 * verb on one statement just reject that specific statement 267 * That is the expected behaviour. 268 * 269 * @dataProvider statement_provider 270 * @param bool $multiple if send multiple statements (adds one valid statement) 271 * @param bool $validactor if the actor used is valid 272 * @param bool $validverb if the verb used is valid 273 * @param array $expected expected results 274 */ 275 public function test_statements_agent(bool $multiple, bool $validactor, bool $validverb, array $expected) { 276 global $USER; 277 278 $this->resetAfterTest(); 279 280 $this->setAdminUser(); 281 282 $other = $this->getDataGenerator()->create_user(); 283 284 $info = []; 285 286 // Setup actor. 287 if ($validactor) { 288 $info['actor'] = item_agent::create_from_user($USER); 289 } else { 290 $info['actor'] = item_agent::create_from_user($other); 291 } 292 293 // Setup verb. 294 if (!$validverb) { 295 $info['verb'] = item_verb::create_from_id('invalid'); 296 } 297 298 $data = $this->get_valid_statement($info); 299 300 if ($multiple) { 301 $data = [ 302 $this->get_valid_statement(), 303 $data, 304 ]; 305 } 306 307 // Perform test. 308 $this->post_statements_data ('fake_component', $data, $expected); 309 } 310 311 /** 312 * Testing group statements. 313 * 314 * This function test several scenarios using different combinations 315 * of statement rejection motives. Some motives produces a full batch 316 * rejection (exception) and other can leed to indivual rejection on 317 * each statement. For example,try to post a statement without $USER 318 * in it produces a full batch rejection, while using an invalid 319 * verb on one statement just reject that specific statement 320 * That is the expected behaviour. 321 * 322 * @dataProvider statement_provider 323 * @param bool $multiple if send multiple statements (adds one valid statement) 324 * @param bool $validactor if the actor used is valid 325 * @param bool $validverb if the verb used is valid 326 * @param array $expected expected results 327 */ 328 public function test_statements_group(bool $multiple, bool $validactor, bool $validverb, array $expected) { 329 global $USER, $CFG; 330 331 $this->resetAfterTest(); 332 333 $this->setAdminUser(); 334 335 $other = $this->getDataGenerator()->create_user(); 336 337 $info = []; 338 339 // Enable group mode in the handle. 340 $CFG->xapitestforcegroupactors = true; 341 342 // Create one course and 1 group. 343 $course = $this->getDataGenerator()->create_course(); 344 $this->getDataGenerator()->enrol_user($USER->id, $course->id); 345 $this->getDataGenerator()->enrol_user($other->id, $course->id); 346 347 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); 348 $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $other->id)); 349 350 if ($validactor) { 351 // Add $USER into a group to make group valid for processing. 352 $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $USER->id)); 353 } 354 $info['actor'] = item_group::create_from_group($group); 355 356 // Setup verb. 357 if (!$validverb) { 358 $info['verb'] = item_verb::create_from_id('invalid'); 359 } 360 361 $data = $this->get_valid_statement($info); 362 363 if ($multiple) { 364 $data = [ 365 $this->get_valid_statement(), 366 $data, 367 ]; 368 } 369 370 // Perform test. 371 $this->post_statements_data ('fake_component', $data, $expected); 372 } 373 374 /** 375 * Data provider for the test_components tests. 376 * 377 * @return array 378 */ 379 public function statement_provider() : array { 380 return [ 381 // Single statement with group statements enabled. 382 'Single, Valid actor, valid verb' => [ 383 false, true, true, [true] 384 ], 385 'Single, Invalid actor, valid verb' => [ 386 false, false, true, [] 387 ], 388 'Single, Valid actor, invalid verb' => [ 389 false, true, false, [] 390 ], 391 'Single, Inalid actor, invalid verb' => [ 392 false, false, false, [] 393 ], 394 // Multi statement with group statements enabled. 395 'Multiple, Valid actor, valid verb' => [ 396 true, true, true, [true, true] 397 ], 398 'Multiple, Invalid actor, valid verb' => [ 399 true, false, true, [] 400 ], 401 'Multiple, Valid actor, invalid verb' => [ 402 true, true, false, [true, false] 403 ], 404 'Multiple, Inalid actor, invalid verb' => [ 405 true, false, false, [] 406 ], 407 ]; 408 } 409 410 /** 411 * Test posting group statements to a handler without group actor support. 412 * 413 * Try to use group statement in components that not support this feature 414 * causes a full statements batch rejection. 415 * 416 * @dataProvider group_statement_provider 417 * @param bool $usegroup1 if the 1st statement must be groupal 418 * @param bool $usegroup2 if the 2nd statement must be groupal 419 * @param array $expected expected results 420 */ 421 public function test_group_disabled(bool $usegroup1, bool $usegroup2, array $expected) { 422 global $USER; 423 424 $this->resetAfterTest(); 425 426 $this->setAdminUser(); 427 428 // Create one course and 1 group. 429 $course = $this->getDataGenerator()->create_course(); 430 $this->getDataGenerator()->enrol_user($USER->id, $course->id); 431 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); 432 $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $USER->id)); 433 434 $info = ['actor' => item_group::create_from_group($group)]; 435 436 $groupstatement = $this->get_valid_statement($info); 437 $agentstatement = $this->get_valid_statement(); 438 439 $data = []; 440 $data[] = ($usegroup1) ? $groupstatement : $agentstatement; 441 $data[] = ($usegroup2) ? $groupstatement : $agentstatement; 442 443 // Perform test. 444 $this->post_statements_data ('fake_component', $data, $expected); 445 } 446 447 /** 448 * Data provider for the test_components tests. 449 * 450 * @return array 451 */ 452 public function group_statement_provider() : array { 453 return [ 454 // Single statement with group statements enabled. 455 'Group statement + group statement without group support' => [ 456 true, true, [] 457 ], 458 'Group statement + agent statement without group support' => [ 459 true, false, [] 460 ], 461 'Agent statement + group statement without group support' => [ 462 true, false, [] 463 ], 464 'Agent statement + agent statement without group support' => [ 465 false, false, [true, true] 466 ], 467 ]; 468 } 469 470 /** 471 * Test posting a statements batch not accepted by handler. 472 * 473 * If all statements from a batch are rejectes by the plugin the full 474 * batch is considered rejected and an exception is returned. 475 */ 476 public function test_full_batch_rejected() { 477 $this->resetAfterTest(); 478 479 $this->setAdminUser(); 480 481 $info = ['verb' => item_verb::create_from_id('invalid')]; 482 483 $statement = $this->get_valid_statement($info); 484 485 $data = [$statement, $statement]; 486 487 // Perform test. 488 $this->post_statements_data ('fake_component', $data, []); 489 } 490 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body