Differences Between: [Versions 402 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 core_xapi; 18 19 use core_xapi\local\statement\item_agent; 20 use core_xapi\local\statement\item_activity; 21 use advanced_testcase; 22 23 /** 24 * Contains test cases for testing xAPI state store methods. 25 * 26 * @package core_xapi 27 * @since Moodle 4.2 28 * @covers \core_xapi\state_store 29 * @copyright 2023 Sara Arjona (sara@moodle.com) 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class state_store_test extends advanced_testcase { 33 34 /** 35 * Setup to ensure that fixtures are loaded. 36 */ 37 public static function setUpBeforeClass(): void { 38 global $CFG; 39 require_once($CFG->dirroot.'/lib/xapi/tests/helper.php'); 40 } 41 42 /** 43 * Testing delete method. 44 * 45 * @dataProvider states_provider 46 * @param array $info Array of overriden state data. 47 * @param bool $expected Expected results. 48 * @return void 49 */ 50 public function test_state_store_delete(array $info, bool $expected): void { 51 global $DB; 52 53 $this->resetAfterTest(); 54 55 // Scenario. 56 $this->setAdminUser(); 57 // Add, at least, one xAPI state record to database (with the default values). 58 test_helper::create_state([], true); 59 60 // Get current states in database. 61 $currentstates = $DB->count_records('xapi_states'); 62 63 // Perform test. 64 $component = $info['component'] ?? 'fake_component'; 65 $state = test_helper::create_state($info); 66 $store = new state_store($component); 67 $result = $store->delete($state); 68 69 // Check the state has been removed. 70 $records = $DB->get_records('xapi_states'); 71 $this->assertTrue($result); 72 if ($expected) { 73 $this->assertCount($currentstates - 1, $records); 74 } else if ($expected === 'false') { 75 $this->assertCount($currentstates, $records); 76 } 77 } 78 79 /** 80 * Testing get method. 81 * 82 * @dataProvider states_provider 83 * @param array $info Array of overriden state data. 84 * @param bool $expected Expected results. 85 * @return void 86 */ 87 public function test_state_store_get(array $info, bool $expected): void { 88 $this->resetAfterTest(); 89 90 // Scenario. 91 $this->setAdminUser(); 92 // Add, at least, one xAPI state record to database (with the default values). 93 test_helper::create_state([], true); 94 95 // Perform test. 96 $component = $info['component'] ?? 'fake_component'; 97 $state = test_helper::create_state($info); 98 // Remove statedata from the state object, to guarantee the get method is working as expected. 99 $state->set_state_data(null); 100 $store = new state_store($component); 101 $result = $store->get($state); 102 103 // Check the returned state has the expected values. 104 if ($expected) { 105 $this->assertEquals(json_encode($state->jsonSerialize()), json_encode($result->jsonSerialize())); 106 } else { 107 $this->assertNull($result); 108 } 109 } 110 111 /** 112 * Data provider for the test_state_store_delete and test_state_store_get tests. 113 * 114 * @return array 115 */ 116 public function states_provider() : array { 117 return [ 118 'Existing and valid state' => [ 119 'info' => [], 120 'expected' => true, 121 ], 122 'No state (wrong activityid)' => [ 123 'info' => ['activity' => item_activity::create_from_id('1')], 124 'expected' => false, 125 ], 126 'No state (wrong stateid)' => [ 127 'info' => ['stateid' => 'food'], 128 'expected' => false, 129 ], 130 'No state (wrong component)' => [ 131 'info' => ['component' => 'mod_h5pactivity'], 132 'expected' => false, 133 ], 134 ]; 135 } 136 137 /** 138 * Testing put method. 139 * 140 * @dataProvider put_states_provider 141 * @param array $info Array of overriden state data. 142 * @param string $expected Expected results. 143 * @return void 144 */ 145 public function test_state_store_put(array $info, string $expected): void { 146 global $DB; 147 148 $this->resetAfterTest(); 149 150 // Scenario. 151 $this->setAdminUser(); 152 // Add, at least, one xAPI state record to database (with the default values). 153 test_helper::create_state([], true); 154 155 // Get current states in database. 156 $currentstates = $DB->count_records('xapi_states'); 157 158 // Perform test. 159 $component = $info['component'] ?? 'fake_component'; 160 $state = test_helper::create_state($info); 161 $store = new state_store($component); 162 $result = $store->put($state); 163 164 // Check the state has been added/updated. 165 $this->assertTrue($result); 166 $recordsnum = $DB->count_records('xapi_states'); 167 $params = [ 168 'component' => $component, 169 'userid' => $state->get_user()->id, 170 'itemid' => $state->get_activity_id(), 171 'stateid' => $state->get_state_id(), 172 'registration' => $state->get_registration(), 173 ]; 174 $records = $DB->get_records('xapi_states', $params); 175 $record = reset($records); 176 if ($expected === 'added') { 177 $this->assertEquals($currentstates + 1, $recordsnum); 178 $this->assertEquals($record->timecreated, $record->timemodified); 179 } else if ($expected === 'updated') { 180 $this->assertEquals($currentstates, $recordsnum); 181 $this->assertGreaterThanOrEqual($record->timecreated, $record->timemodified); 182 } 183 184 $this->assertEquals($component, $record->component); 185 $this->assertEquals($state->get_activity_id(), $record->itemid); 186 $this->assertEquals($state->get_user()->id, $record->userid); 187 $this->assertEquals(json_encode($state->jsonSerialize()), $record->statedata); 188 $this->assertEquals($state->get_registration(), $record->registration); 189 } 190 191 /** 192 * Data provider for the test_state_store_put tests. 193 * 194 * @return array 195 */ 196 public function put_states_provider() : array { 197 return [ 198 'Update existing state' => [ 199 'info' => [], 200 'expected' => 'updated', 201 ], 202 'Update existing state (change statedata)' => [ 203 'info' => ['statedata' => '{"progress":0,"answers":[[["BB"],[""]],[{"answers":[]}]],"answered":[true,false]}'], 204 'expected' => 'updated', 205 ], 206 'Add state (with different itemid)' => [ 207 'info' => ['activity' => item_activity::create_from_id('1')], 208 'expected' => 'added', 209 ], 210 'Add state (with different stateid)' => [ 211 'info' => ['stateid' => 'food'], 212 'expected' => 'added', 213 ], 214 'Add state (with different component)' => [ 215 'info' => ['component' => 'mod_h5pactivity'], 216 'expected' => 'added', 217 ], 218 ]; 219 } 220 221 /** 222 * Testing reset method. 223 * 224 * @dataProvider reset_wipe_states_provider 225 * @param array $info Array of overriden state data. 226 * @param int $expected The states that will be reset. 227 * @return void 228 */ 229 public function test_state_store_reset(array $info, int $expected): void { 230 global $DB; 231 232 $this->resetAfterTest(); 233 234 // Scenario. 235 $this->setAdminUser(); 236 $other = $this->getDataGenerator()->create_user(); 237 238 // Add a few xAPI state records to database. 239 test_helper::create_state(['activity' => item_activity::create_from_id('1')], true); 240 test_helper::create_state(['activity' => item_activity::create_from_id('2'), 'stateid' => 'paella'], true); 241 test_helper::create_state([ 242 'activity' => item_activity::create_from_id('3'), 243 'agent' => item_agent::create_from_user($other), 244 'stateid' => 'paella', 245 'registration' => 'ABC', 246 ], true); 247 test_helper::create_state([ 248 'activity' => item_activity::create_from_id('4'), 249 'agent' => item_agent::create_from_user($other), 250 ], true); 251 test_helper::create_state(['activity' => item_activity::create_from_id('5'), 'component' => 'my_component'], true); 252 test_helper::create_state([ 253 'activity' => item_activity::create_from_id('6'), 254 'component' => 'my_component', 255 'stateid' => 'paella', 256 'agent' => item_agent::create_from_user($other), 257 ], true); 258 259 // Get current states in database. 260 $currentstates = $DB->count_records('xapi_states'); 261 262 // Perform test. 263 $component = $info['component'] ?? 'fake_component'; 264 $itemid = $info['activity'] ?? null; 265 $userid = (array_key_exists('agent', $info) && $info['agent'] === 'other') ? $other->id : null; 266 $stateid = $info['stateid'] ?? null; 267 $registration = $info['registration'] ?? null; 268 $store = new state_store($component); 269 $store->reset($itemid, $userid, $stateid, $registration); 270 271 // Check the states haven't been removed. 272 $this->assertCount($currentstates, $DB->get_records('xapi_states')); 273 $records = $DB->get_records_select('xapi_states', 'statedata IS NULL'); 274 $this->assertCount($expected, $records); 275 } 276 277 /** 278 * Testing wipe method. 279 * 280 * @dataProvider reset_wipe_states_provider 281 * @param array $info Array of overriden state data. 282 * @param int $expected The removed states. 283 * @return void 284 */ 285 public function test_state_store_wipe(array $info, int $expected): void { 286 global $DB; 287 288 $this->resetAfterTest(); 289 290 // Scenario. 291 $this->setAdminUser(); 292 $other = $this->getDataGenerator()->create_user(); 293 294 // Add a few xAPI state records to database. 295 test_helper::create_state(['activity' => item_activity::create_from_id('1')], true); 296 test_helper::create_state(['activity' => item_activity::create_from_id('2'), 'stateid' => 'paella'], true); 297 test_helper::create_state([ 298 'activity' => item_activity::create_from_id('3'), 299 'agent' => item_agent::create_from_user($other), 300 'stateid' => 'paella', 301 'registration' => 'ABC', 302 ], true); 303 test_helper::create_state([ 304 'activity' => item_activity::create_from_id('4'), 305 'agent' => item_agent::create_from_user($other), 306 ], true); 307 test_helper::create_state(['activity' => item_activity::create_from_id('5'), 'component' => 'my_component'], true); 308 test_helper::create_state([ 309 'activity' => item_activity::create_from_id('6'), 310 'component' => 'my_component', 311 'stateid' => 'paella', 312 'agent' => item_agent::create_from_user($other), 313 ], true); 314 315 // Get current states in database. 316 $currentstates = $DB->count_records('xapi_states'); 317 318 // Perform test. 319 $component = $info['component'] ?? 'fake_component'; 320 $itemid = $info['activity'] ?? null; 321 $userid = (array_key_exists('agent', $info) && $info['agent'] === 'other') ? $other->id : null; 322 $stateid = $info['stateid'] ?? null; 323 $registration = $info['registration'] ?? null; 324 $store = new state_store($component); 325 $store->wipe($itemid, $userid, $stateid, $registration); 326 327 // Check the states have been removed. 328 $records = $DB->get_records('xapi_states'); 329 $this->assertCount($currentstates - $expected, $records); 330 } 331 332 /** 333 * Data provider for the test_state_store_reset and test_state_store_wipe tests. 334 * 335 * @return array 336 */ 337 public function reset_wipe_states_provider() : array { 338 return [ 339 'With fake_component' => [ 340 'info' => [], 341 'expected' => 4, 342 ], 343 'With my_component' => [ 344 'info' => ['component' => 'my_component'], 345 'expected' => 2, 346 ], 347 'With unexisting_component' => [ 348 'info' => ['component' => 'unexisting_component'], 349 'expected' => 0, 350 ], 351 'Existing activity' => [ 352 'info' => ['activity' => '1'], 353 'expected' => 1, 354 ], 355 'Unexisting activity' => [ 356 'info' => ['activity' => '1111'], 357 'expected' => 0, 358 ], 359 'Existing userid' => [ 360 'info' => ['agent' => 'other'], 361 'expected' => 2, 362 ], 363 'Existing stateid' => [ 364 'info' => ['stateid' => 'paella'], 365 'expected' => 2, 366 ], 367 'Unexisting stateid' => [ 368 'info' => ['stateid' => 'chorizo'], 369 'expected' => 0, 370 ], 371 'Existing registration' => [ 372 'info' => ['registration' => 'ABC'], 373 'expected' => 1, 374 ], 375 'Uxexisting registration' => [ 376 'info' => ['registration' => 'XYZ'], 377 'expected' => 0, 378 ], 379 'Existing stateid combined with activity' => [ 380 'info' => ['activity' => '3', 'stateid' => 'paella'], 381 'expected' => 1, 382 ], 383 'Uxexisting stateid combined with activity' => [ 384 'info' => ['activity' => '1', 'stateid' => 'paella'], 385 'expected' => 0, 386 ], 387 ]; 388 } 389 390 /** 391 * Testing cleanup method. 392 * 393 * @return void 394 */ 395 public function test_state_store_cleanup(): void { 396 global $DB; 397 398 $this->resetAfterTest(); 399 400 // Scenario. 401 $this->setAdminUser(); 402 $other = $this->getDataGenerator()->create_user(); 403 404 // Add a few xAPI state records to database. 405 test_helper::create_state(['activity' => item_activity::create_from_id('1')], true); 406 test_helper::create_state(['activity' => item_activity::create_from_id('2')], true); 407 test_helper::create_state(['activity' => item_activity::create_from_id('3')], true); 408 test_helper::create_state(['activity' => item_activity::create_from_id('4')], true); 409 test_helper::create_state(['activity' => item_activity::create_from_id('5'), 'component' => 'my_component'], true); 410 test_helper::create_state(['activity' => item_activity::create_from_id('6'), 'component' => 'my_component'], true); 411 412 // Get current states in database. 413 $currentstates = $DB->count_records('xapi_states'); 414 415 // Perform test. 416 $component = 'fake_component'; 417 $store = new state_store($component); 418 $store->cleanup(); 419 420 // Check no state has been removed (because the entries are not old enough). 421 $this->assertEquals($currentstates, $DB->count_records('xapi_states')); 422 423 // Make the existing state entries older. 424 $timepast = time() - 2; 425 $DB->set_field('xapi_states', 'timecreated', $timepast); 426 $DB->set_field('xapi_states', 'timemodified', $timepast); 427 428 // Create 1 more state, that shouldn't be removed after the cleanup. 429 test_helper::create_state(['activity' => item_activity::create_from_id('7')], true); 430 431 // Set the config to remove states older than 1 second. 432 set_config('xapicleanupperiod', 1); 433 434 // Check old states for fake_component have been removed. 435 $currentstates = $DB->count_records('xapi_states'); 436 $store->cleanup(); 437 $this->assertEquals($currentstates - 4, $DB->count_records('xapi_states')); 438 $this->assertEquals(1, $DB->count_records('xapi_states', ['component' => $component])); 439 $this->assertEquals(2, $DB->count_records('xapi_states', ['component' => 'my_component'])); 440 } 441 442 /** 443 * Testing get_state_ids method. 444 * 445 * @dataProvider get_state_ids_provider 446 * @param string $component 447 * @param string|null $itemid 448 * @param string|null $registration 449 * @param bool|null $since 450 * @param array $expected the expected result 451 * @return void 452 */ 453 public function test_get_state_ids( 454 string $component, 455 ?string $itemid, 456 ?string $registration, 457 ?bool $since, 458 array $expected, 459 ): void { 460 global $DB, $USER; 461 462 $this->resetAfterTest(); 463 464 // Scenario. 465 $this->setAdminUser(); 466 $other = $this->getDataGenerator()->create_user(); 467 468 // Add a few xAPI state records to database. 469 $states = [ 470 ['activity' => item_activity::create_from_id('1'), 'stateid' => 'aa'], 471 ['activity' => item_activity::create_from_id('1'), 'registration' => 'reg', 'stateid' => 'bb'], 472 ['activity' => item_activity::create_from_id('1'), 'registration' => 'reg2', 'stateid' => 'cc'], 473 ['activity' => item_activity::create_from_id('2'), 'registration' => 'reg', 'stateid' => 'dd'], 474 ['activity' => item_activity::create_from_id('3'), 'stateid' => 'ee'], 475 ['activity' => item_activity::create_from_id('4'), 'component' => 'other', 'stateid' => 'ff'], 476 ]; 477 foreach ($states as $state) { 478 test_helper::create_state($state, true); 479 } 480 481 // Make all existing state entries older except form two. 482 $currenttime = time(); 483 $timepast = $currenttime - 5; 484 $DB->set_field('xapi_states', 'timecreated', $timepast); 485 $DB->set_field('xapi_states', 'timemodified', $timepast); 486 $DB->set_field('xapi_states', 'timemodified', $currenttime, ['stateid' => 'aa']); 487 $DB->set_field('xapi_states', 'timemodified', $currenttime, ['stateid' => 'bb']); 488 $DB->set_field('xapi_states', 'timemodified', $currenttime, ['stateid' => 'dd']); 489 490 // Perform test. 491 $sincetime = ($since) ? $currenttime - 1 : null; 492 $store = new state_store($component); 493 $stateids = $store->get_state_ids($itemid, $USER->id, $registration, $sincetime); 494 sort($stateids); 495 496 $this->assertEquals($expected, $stateids); 497 } 498 499 /** 500 * Data provider for the test_get_state_ids. 501 * 502 * @return array 503 */ 504 public function get_state_ids_provider(): array { 505 return [ 506 'empty_component' => [ 507 'component' => 'empty_component', 508 'itemid' => null, 509 'registration' => null, 510 'since' => null, 511 'expected' => [], 512 ], 513 'filter_by_itemid' => [ 514 'component' => 'fake_component', 515 'itemid' => '1', 516 'registration' => null, 517 'since' => null, 518 'expected' => ['aa', 'bb', 'cc'], 519 ], 520 'filter_by_registration' => [ 521 'component' => 'fake_component', 522 'itemid' => null, 523 'registration' => 'reg', 524 'since' => null, 525 'expected' => ['bb', 'dd'], 526 ], 527 'filter_by_since' => [ 528 'component' => 'fake_component', 529 'itemid' => null, 530 'registration' => null, 531 'since' => true, 532 'expected' => ['aa', 'bb', 'dd'], 533 ], 534 'filter_by_itemid_and_registration' => [ 535 'component' => 'fake_component', 536 'itemid' => '1', 537 'registration' => 'reg', 538 'since' => null, 539 'expected' => ['bb'], 540 ], 541 'filter_by_itemid_registration_since' => [ 542 'component' => 'fake_component', 543 'itemid' => '1', 544 'registration' => 'reg', 545 'since' => true, 546 'expected' => ['bb'], 547 ], 548 'filter_by_registration_since' => [ 549 'component' => 'fake_component', 550 'itemid' => null, 551 'registration' => 'reg', 552 'since' => true, 553 'expected' => ['bb', 'dd'], 554 ], 555 ]; 556 } 557 558 /** 559 * Test delete with a non numeric activity id. 560 * 561 * The default state store only allows integer itemids. 562 * 563 * @dataProvider invalid_activityid_format_provider 564 * @param string $operation the method to execute 565 * @param bool $usestate if the param is a state or the activity id 566 */ 567 public function test_invalid_activityid_format(string $operation, bool $usestate = false): void { 568 $this->resetAfterTest(); 569 $this->setAdminUser(); 570 571 $state = test_helper::create_state([ 572 'activity' => item_activity::create_from_id('notnumeric'), 573 ]); 574 $param = ($usestate) ? $state : 'notnumeric'; 575 576 $this->expectException(xapi_exception::class); 577 $store = new state_store('fake_component'); 578 $store->$operation($param); 579 } 580 581 /** 582 * Data provider for test_invalid_activityid_format. 583 * 584 * @return array 585 */ 586 public function invalid_activityid_format_provider(): array { 587 return [ 588 'delete' => [ 589 'operation' => 'delete', 590 'usestate' => true, 591 ], 592 'get' => [ 593 'operation' => 'get', 594 'usestate' => true, 595 ], 596 'put' => [ 597 'operation' => 'put', 598 'usestate' => true, 599 ], 600 'reset' => [ 601 'operation' => 'reset', 602 'usestate' => false, 603 ], 604 'wipe' => [ 605 'operation' => 'wipe', 606 'usestate' => false, 607 ], 608 'get_state_ids' => [ 609 'operation' => 'get_state_ids', 610 'usestate' => false, 611 ], 612 ]; 613 } 614 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body