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 * Privacy provider tests. 19 * 20 * @package mod_quiz 21 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 use core_privacy\local\metadata\collection; 26 use core_privacy\local\request\deletion_criteria; 27 use core_privacy\local\request\writer; 28 use mod_quiz\privacy\provider; 29 use mod_quiz\privacy\helper; 30 31 defined('MOODLE_INTERNAL') || die(); 32 33 global $CFG; 34 require_once($CFG->dirroot . '/question/tests/privacy_helper.php'); 35 36 /** 37 * Privacy provider tests class. 38 * 39 * @package mod_quiz 40 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class mod_quiz_privacy_provider_testcase extends \core_privacy\tests\provider_testcase { 44 45 use core_question_privacy_helper; 46 47 /** 48 * Test that a user who has no data gets no contexts 49 */ 50 public function test_get_contexts_for_userid_no_data() { 51 global $USER; 52 $this->resetAfterTest(); 53 $this->setAdminUser(); 54 55 $contextlist = provider::get_contexts_for_userid($USER->id); 56 $this->assertEmpty($contextlist); 57 } 58 59 /** 60 * Test for provider::get_contexts_for_userid() when there is no quiz attempt at all. 61 */ 62 public function test_get_contexts_for_userid_no_attempt_with_override() { 63 global $DB; 64 $this->resetAfterTest(true); 65 66 $course = $this->getDataGenerator()->create_course(); 67 $user = $this->getDataGenerator()->create_user(); 68 69 // Make a quiz with an override. 70 $this->setUser(); 71 $quiz = $this->create_test_quiz($course); 72 $DB->insert_record('quiz_overrides', [ 73 'quiz' => $quiz->id, 74 'userid' => $user->id, 75 'timeclose' => 1300, 76 'timelimit' => null, 77 ]); 78 79 $cm = get_coursemodule_from_instance('quiz', $quiz->id); 80 $context = \context_module::instance($cm->id); 81 82 // Fetch the contexts - only one context should be returned. 83 $this->setUser(); 84 $contextlist = provider::get_contexts_for_userid($user->id); 85 $this->assertCount(1, $contextlist); 86 $this->assertEquals($context, $contextlist->current()); 87 } 88 89 /** 90 * The export function should handle an empty contextlist properly. 91 */ 92 public function test_export_user_data_no_data() { 93 global $USER; 94 $this->resetAfterTest(); 95 $this->setAdminUser(); 96 97 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( 98 \core_user::get_user($USER->id), 99 'mod_quiz', 100 [] 101 ); 102 103 provider::export_user_data($approvedcontextlist); 104 $this->assertDebuggingNotCalled(); 105 106 // No data should have been exported. 107 $writer = \core_privacy\local\request\writer::with_context(\context_system::instance()); 108 $this->assertFalse($writer->has_any_data_in_any_context()); 109 } 110 111 /** 112 * The delete function should handle an empty contextlist properly. 113 */ 114 public function test_delete_data_for_user_no_data() { 115 global $USER; 116 $this->resetAfterTest(); 117 $this->setAdminUser(); 118 119 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( 120 \core_user::get_user($USER->id), 121 'mod_quiz', 122 [] 123 ); 124 125 provider::delete_data_for_user($approvedcontextlist); 126 $this->assertDebuggingNotCalled(); 127 } 128 129 /** 130 * Export + Delete quiz data for a user who has made a single attempt. 131 */ 132 public function test_user_with_data() { 133 global $DB; 134 $this->resetAfterTest(true); 135 136 $course = $this->getDataGenerator()->create_course(); 137 $user = $this->getDataGenerator()->create_user(); 138 $otheruser = $this->getDataGenerator()->create_user(); 139 140 // Make a quiz with an override. 141 $this->setUser(); 142 $quiz = $this->create_test_quiz($course); 143 $DB->insert_record('quiz_overrides', [ 144 'quiz' => $quiz->id, 145 'userid' => $user->id, 146 'timeclose' => 1300, 147 'timelimit' => null, 148 ]); 149 150 // Run as the user and make an attempt on the quiz. 151 list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $user); 152 $this->attempt_quiz($quiz, $otheruser); 153 $context = $quizobj->get_context(); 154 155 // Fetch the contexts - only one context should be returned. 156 $this->setUser(); 157 $contextlist = provider::get_contexts_for_userid($user->id); 158 $this->assertCount(1, $contextlist); 159 $this->assertEquals($context, $contextlist->current()); 160 161 // Perform the export and check the data. 162 $this->setUser($user); 163 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( 164 \core_user::get_user($user->id), 165 'mod_quiz', 166 $contextlist->get_contextids() 167 ); 168 provider::export_user_data($approvedcontextlist); 169 170 // Ensure that the quiz data was exported correctly. 171 $writer = writer::with_context($context); 172 $this->assertTrue($writer->has_any_data()); 173 174 $quizdata = $writer->get_data([]); 175 $this->assertEquals($quizobj->get_quiz_name(), $quizdata->name); 176 177 // Every module has an intro. 178 $this->assertTrue(isset($quizdata->intro)); 179 180 // Fetch the attempt data. 181 $attempt = $attemptobj->get_attempt(); 182 $attemptsubcontext = [ 183 get_string('attempts', 'mod_quiz'), 184 $attempt->attempt, 185 ]; 186 $attemptdata = writer::with_context($context)->get_data($attemptsubcontext); 187 188 $attempt = $attemptobj->get_attempt(); 189 $this->assertTrue(isset($attemptdata->state)); 190 $this->assertEquals(\quiz_attempt::state_name($attemptobj->get_state()), $attemptdata->state); 191 $this->assertTrue(isset($attemptdata->timestart)); 192 $this->assertTrue(isset($attemptdata->timefinish)); 193 $this->assertTrue(isset($attemptdata->timemodified)); 194 $this->assertFalse(isset($attemptdata->timemodifiedoffline)); 195 $this->assertFalse(isset($attemptdata->timecheckstate)); 196 197 $this->assertTrue(isset($attemptdata->grade)); 198 $this->assertEquals(100.00, $attemptdata->grade->grade); 199 200 // Check that the exported question attempts are correct. 201 $attemptsubcontext = helper::get_quiz_attempt_subcontext($attemptobj->get_attempt(), $user); 202 $this->assert_question_attempt_exported( 203 $context, 204 $attemptsubcontext, 205 \question_engine::load_questions_usage_by_activity($attemptobj->get_uniqueid()), 206 quiz_get_review_options($quiz, $attemptobj->get_attempt(), $context), 207 $user 208 ); 209 210 // Delete the data and check it is removed. 211 $this->setUser(); 212 provider::delete_data_for_user($approvedcontextlist); 213 $this->expectException(\dml_missing_record_exception::class); 214 \quiz_attempt::create($attemptobj->get_quizid()); 215 } 216 217 /** 218 * Export + Delete quiz data for a user who has made a single attempt. 219 */ 220 public function test_user_with_preview() { 221 global $DB; 222 $this->resetAfterTest(true); 223 224 // Make a quiz. 225 $course = $this->getDataGenerator()->create_course(); 226 $user = $this->getDataGenerator()->create_user(); 227 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); 228 229 $quiz = $quizgenerator->create_instance([ 230 'course' => $course->id, 231 'questionsperpage' => 0, 232 'grade' => 100.0, 233 'sumgrades' => 2, 234 ]); 235 236 // Create a couple of questions. 237 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 238 $cat = $questiongenerator->create_question_category(); 239 240 $saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); 241 quiz_add_quiz_question($saq->id, $quiz); 242 $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id)); 243 quiz_add_quiz_question($numq->id, $quiz); 244 245 // Run as the user and make an attempt on the quiz. 246 $this->setUser($user); 247 $starttime = time(); 248 $quizobj = quiz::create($quiz->id, $user->id); 249 $context = $quizobj->get_context(); 250 251 $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); 252 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); 253 254 // Start the attempt. 255 $attempt = quiz_create_attempt($quizobj, 1, false, $starttime, true, $user->id); 256 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime); 257 quiz_attempt_save_started($quizobj, $quba, $attempt); 258 259 // Answer the questions. 260 $attemptobj = quiz_attempt::create($attempt->id); 261 262 $tosubmit = [ 263 1 => ['answer' => 'frog'], 264 2 => ['answer' => '3.14'], 265 ]; 266 267 $attemptobj->process_submitted_actions($starttime, false, $tosubmit); 268 269 // Finish the attempt. 270 $attemptobj = quiz_attempt::create($attempt->id); 271 $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); 272 $attemptobj->process_finish($starttime, false); 273 274 // Fetch the contexts - no context should be returned. 275 $this->setUser(); 276 $contextlist = provider::get_contexts_for_userid($user->id); 277 $this->assertCount(0, $contextlist); 278 } 279 280 /** 281 * Export + Delete quiz data for a user who has made a single attempt. 282 */ 283 public function test_delete_data_for_all_users_in_context() { 284 global $DB; 285 $this->resetAfterTest(true); 286 287 $course = $this->getDataGenerator()->create_course(); 288 $user = $this->getDataGenerator()->create_user(); 289 $otheruser = $this->getDataGenerator()->create_user(); 290 291 // Make a quiz with an override. 292 $this->setUser(); 293 $quiz = $this->create_test_quiz($course); 294 $DB->insert_record('quiz_overrides', [ 295 'quiz' => $quiz->id, 296 'userid' => $user->id, 297 'timeclose' => 1300, 298 'timelimit' => null, 299 ]); 300 301 // Run as the user and make an attempt on the quiz. 302 list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $user); 303 list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $otheruser); 304 305 // Create another quiz and questions, and repeat the data insertion. 306 $this->setUser(); 307 $otherquiz = $this->create_test_quiz($course); 308 $DB->insert_record('quiz_overrides', [ 309 'quiz' => $otherquiz->id, 310 'userid' => $user->id, 311 'timeclose' => 1300, 312 'timelimit' => null, 313 ]); 314 315 // Run as the user and make an attempt on the quiz. 316 list($otherquizobj, $otherquba, $otherattemptobj) = $this->attempt_quiz($otherquiz, $user); 317 list($otherquizobj, $otherquba, $otherattemptobj) = $this->attempt_quiz($otherquiz, $otheruser); 318 319 // Delete all data for all users in the context under test. 320 $this->setUser(); 321 $context = $quizobj->get_context(); 322 provider::delete_data_for_all_users_in_context($context); 323 324 // The quiz attempt should have been deleted from this quiz. 325 $this->assertCount(0, $DB->get_records('quiz_attempts', ['quiz' => $quizobj->get_quizid()])); 326 $this->assertCount(0, $DB->get_records('quiz_overrides', ['quiz' => $quizobj->get_quizid()])); 327 $this->assertCount(0, $DB->get_records('question_attempts', ['questionusageid' => $quba->get_id()])); 328 329 // But not for the other quiz. 330 $this->assertNotCount(0, $DB->get_records('quiz_attempts', ['quiz' => $otherquizobj->get_quizid()])); 331 $this->assertNotCount(0, $DB->get_records('quiz_overrides', ['quiz' => $otherquizobj->get_quizid()])); 332 $this->assertNotCount(0, $DB->get_records('question_attempts', ['questionusageid' => $otherquba->get_id()])); 333 } 334 335 /** 336 * Export + Delete quiz data for a user who has made a single attempt. 337 */ 338 public function test_wrong_context() { 339 global $DB; 340 $this->resetAfterTest(true); 341 342 $course = $this->getDataGenerator()->create_course(); 343 $user = $this->getDataGenerator()->create_user(); 344 345 // Make a choice. 346 $this->setUser(); 347 $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); 348 $choice = $plugingenerator->create_instance(['course' => $course->id]); 349 $cm = get_coursemodule_from_instance('choice', $choice->id); 350 $context = \context_module::instance($cm->id); 351 352 // Fetch the contexts - no context should be returned. 353 $this->setUser(); 354 $contextlist = provider::get_contexts_for_userid($user->id); 355 $this->assertCount(0, $contextlist); 356 357 // Perform the export and check the data. 358 $this->setUser($user); 359 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( 360 \core_user::get_user($user->id), 361 'mod_quiz', 362 [$context->id] 363 ); 364 provider::export_user_data($approvedcontextlist); 365 366 // Ensure that nothing was exported. 367 $writer = writer::with_context($context); 368 $this->assertFalse($writer->has_any_data_in_any_context()); 369 370 $this->setUser(); 371 372 $dbwrites = $DB->perf_get_writes(); 373 374 // Perform a deletion with the approved contextlist containing an incorrect context. 375 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist( 376 \core_user::get_user($user->id), 377 'mod_quiz', 378 [$context->id] 379 ); 380 provider::delete_data_for_user($approvedcontextlist); 381 $this->assertEquals($dbwrites, $DB->perf_get_writes()); 382 $this->assertDebuggingNotCalled(); 383 384 // Perform a deletion of all data in the context. 385 provider::delete_data_for_all_users_in_context($context); 386 $this->assertEquals($dbwrites, $DB->perf_get_writes()); 387 $this->assertDebuggingNotCalled(); 388 } 389 390 /** 391 * Create a test quiz for the specified course. 392 * 393 * @param \stdClass $course 394 * @return array 395 */ 396 protected function create_test_quiz($course) { 397 global $DB; 398 399 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); 400 401 $quiz = $quizgenerator->create_instance([ 402 'course' => $course->id, 403 'questionsperpage' => 0, 404 'grade' => 100.0, 405 'sumgrades' => 2, 406 ]); 407 408 // Create a couple of questions. 409 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 410 $cat = $questiongenerator->create_question_category(); 411 412 $saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); 413 quiz_add_quiz_question($saq->id, $quiz); 414 $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id)); 415 quiz_add_quiz_question($numq->id, $quiz); 416 417 return $quiz; 418 } 419 420 /** 421 * Answer questions for a quiz + user. 422 * 423 * @param \stdClass $quiz 424 * @param \stdClass $user 425 * @return array 426 */ 427 protected function attempt_quiz($quiz, $user) { 428 $this->setUser($user); 429 430 $starttime = time(); 431 $quizobj = quiz::create($quiz->id, $user->id); 432 $context = $quizobj->get_context(); 433 434 $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); 435 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); 436 437 // Start the attempt. 438 $attempt = quiz_create_attempt($quizobj, 1, false, $starttime, false, $user->id); 439 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime); 440 quiz_attempt_save_started($quizobj, $quba, $attempt); 441 442 // Answer the questions. 443 $attemptobj = quiz_attempt::create($attempt->id); 444 445 $tosubmit = [ 446 1 => ['answer' => 'frog'], 447 2 => ['answer' => '3.14'], 448 ]; 449 450 $attemptobj->process_submitted_actions($starttime, false, $tosubmit); 451 452 // Finish the attempt. 453 $attemptobj = quiz_attempt::create($attempt->id); 454 $attemptobj->process_finish($starttime, false); 455 456 $this->setUser(); 457 458 return [$quizobj, $quba, $attemptobj]; 459 } 460 461 /** 462 * Test for provider::get_users_in_context(). 463 */ 464 public function test_get_users_in_context() { 465 global $DB; 466 $this->resetAfterTest(true); 467 468 $course = $this->getDataGenerator()->create_course(); 469 $user = $this->getDataGenerator()->create_user(); 470 $anotheruser = $this->getDataGenerator()->create_user(); 471 $extrauser = $this->getDataGenerator()->create_user(); 472 473 // Make a quiz. 474 $this->setUser(); 475 $quiz = $this->create_test_quiz($course); 476 477 // Create an override for user1. 478 $DB->insert_record('quiz_overrides', [ 479 'quiz' => $quiz->id, 480 'userid' => $user->id, 481 'timeclose' => 1300, 482 'timelimit' => null, 483 ]); 484 485 // Make an attempt on the quiz as user2. 486 list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $anotheruser); 487 $context = $quizobj->get_context(); 488 489 // Fetch users - user1 and user2 should be returned. 490 $userlist = new \core_privacy\local\request\userlist($context, 'mod_quiz'); 491 \mod_quiz\privacy\provider::get_users_in_context($userlist); 492 $this->assertEqualsCanonicalizing( 493 [$user->id, $anotheruser->id], 494 $userlist->get_userids()); 495 } 496 497 /** 498 * Test for provider::delete_data_for_users(). 499 */ 500 public function test_delete_data_for_users() { 501 global $DB; 502 $this->resetAfterTest(true); 503 504 $user1 = $this->getDataGenerator()->create_user(); 505 $user2 = $this->getDataGenerator()->create_user(); 506 $user3 = $this->getDataGenerator()->create_user(); 507 508 $course1 = $this->getDataGenerator()->create_course(); 509 $course2 = $this->getDataGenerator()->create_course(); 510 511 // Make a quiz in each course. 512 $quiz1 = $this->create_test_quiz($course1); 513 $quiz2 = $this->create_test_quiz($course2); 514 515 // Attempt quiz1 as user1 and user2. 516 list($quiz1obj) = $this->attempt_quiz($quiz1, $user1); 517 $this->attempt_quiz($quiz1, $user2); 518 519 // Create an override in quiz1 for user3. 520 $DB->insert_record('quiz_overrides', [ 521 'quiz' => $quiz1->id, 522 'userid' => $user3->id, 523 'timeclose' => 1300, 524 'timelimit' => null, 525 ]); 526 527 // Attempt quiz2 as user1. 528 $this->attempt_quiz($quiz2, $user1); 529 530 // Delete the data for user1 and user3 in course1 and check it is removed. 531 $quiz1context = $quiz1obj->get_context(); 532 $approveduserlist = new \core_privacy\local\request\approved_userlist($quiz1context, 'mod_quiz', 533 [$user1->id, $user3->id]); 534 provider::delete_data_for_users($approveduserlist); 535 536 // Only the attempt of user2 should be remained in quiz1. 537 $this->assertEquals( 538 [$user2->id], 539 $DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz1->id]) 540 ); 541 542 // The attempt that user1 made in quiz2 should be remained. 543 $this->assertEquals( 544 [$user1->id], 545 $DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz2->id]) 546 ); 547 548 // The quiz override in quiz1 that we had for user3 should be deleted. 549 $this->assertEquals( 550 [], 551 $DB->get_fieldset_select('quiz_overrides', 'userid', 'quiz = ?', [$quiz1->id]) 552 ); 553 } 554 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body