See Release Notes
Long Term Support Release
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 * Provides the {@link mod_workshop_privacy_provider_testcase} class. 19 * 20 * @package mod_workshop 21 * @category test 22 * @copyright 2018 David Mudrák <david@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 global $CFG; 29 30 use core_privacy\local\request\writer; 31 32 /** 33 * Unit tests for the privacy API implementation. 34 * 35 * @copyright 2018 David Mudrák <david@moodle.com> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class mod_workshop_privacy_provider_testcase extends advanced_testcase { 39 40 /** @var testing_data_generator */ 41 protected $generator; 42 43 /** @var mod_workshop_generator */ 44 protected $workshopgenerator; 45 46 /** @var stdClass */ 47 protected $course1; 48 49 /** @var stdClass */ 50 protected $course2; 51 52 /** @var stdClass */ 53 protected $student1; 54 55 /** @var stdClass */ 56 protected $student2; 57 58 /** @var stdClass */ 59 protected $student3; 60 61 /** @var stdClass */ 62 protected $teacher4; 63 64 /** @var stdClass first workshop in course1 */ 65 protected $workshop11; 66 67 /** @var stdClass second workshop in course1 */ 68 protected $workshop12; 69 70 /** @var stdClass first workshop in course2 */ 71 protected $workshop21; 72 73 /** @var int ID of the submission in workshop11 by student1 */ 74 protected $submission111; 75 76 /** @var int ID of the submission in workshop12 by student1 */ 77 protected $submission121; 78 79 /** @var int ID of the submission in workshop12 by student2 */ 80 protected $submission122; 81 82 /** @var int ID of the submission in workshop21 by student2 */ 83 protected $submission212; 84 85 /** @var int ID of the assessment of submission111 by student1 */ 86 protected $assessment1111; 87 88 /** @var int ID of the assessment of submission111 by student2 */ 89 protected $assessment1112; 90 91 /** @var int ID of the assessment of submission111 by student3 */ 92 protected $assessment1113; 93 94 /** @var int ID of the assessment of submission121 by student2 */ 95 protected $assessment1212; 96 97 /** @var int ID of the assessment of submission212 by student1 */ 98 protected $assessment2121; 99 100 /** 101 * Set up the test environment. 102 * 103 * course1 104 * | 105 * +--workshop11 (first digit matches the course, second is incremental) 106 * | | 107 * | +--submission111 (first two digits match the workshop, last one matches the author) 108 * | | 109 * | +--assessment1111 (first three digits match the submission, last one matches the reviewer) 110 * | +--assessment1112 111 * | +--assessment1113 112 * | 113 * +--workshop12 114 * | 115 * +--submission121 116 * | | 117 * | +--assessment1212 118 * | 119 * +--submission122 120 * 121 * etc. 122 */ 123 protected function setUp() { 124 global $DB; 125 $this->resetAfterTest(); 126 $this->setAdminUser(); 127 128 $this->generator = $this->getDataGenerator(); 129 $this->workshopgenerator = $this->generator->get_plugin_generator('mod_workshop'); 130 131 $this->course1 = $this->generator->create_course(); 132 $this->course2 = $this->generator->create_course(); 133 134 $this->workshop11 = $this->generator->create_module('workshop', [ 135 'course' => $this->course1, 136 'name' => 'Workshop11', 137 ]); 138 $DB->set_field('workshop', 'phase', 50, ['id' => $this->workshop11->id]); 139 140 $this->workshop12 = $this->generator->create_module('workshop', ['course' => $this->course1]); 141 $this->workshop21 = $this->generator->create_module('workshop', ['course' => $this->course2]); 142 143 $this->student1 = $this->generator->create_user(); 144 $this->student2 = $this->generator->create_user(); 145 $this->student3 = $this->generator->create_user(); 146 $this->teacher4 = $this->generator->create_user(); 147 148 $this->submission111 = $this->workshopgenerator->create_submission($this->workshop11->id, $this->student1->id); 149 $this->submission121 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student1->id, 150 ['gradeoverby' => $this->teacher4->id]); 151 $this->submission122 = $this->workshopgenerator->create_submission($this->workshop12->id, $this->student2->id); 152 $this->submission212 = $this->workshopgenerator->create_submission($this->workshop21->id, $this->student2->id); 153 154 $this->assessment1111 = $this->workshopgenerator->create_assessment($this->submission111, $this->student1->id, [ 155 'grade' => null, 156 ]); 157 $this->assessment1112 = $this->workshopgenerator->create_assessment($this->submission111, $this->student2->id, [ 158 'grade' => 92, 159 ]); 160 $this->assessment1113 = $this->workshopgenerator->create_assessment($this->submission111, $this->student3->id); 161 162 $this->assessment1212 = $this->workshopgenerator->create_assessment($this->submission121, $this->student2->id, [ 163 'feedbackauthor' => 'This is what student 2 thinks about submission 121', 164 'feedbackreviewer' => 'This is what the teacher thinks about this assessment', 165 ]); 166 167 $this->assessment2121 = $this->workshopgenerator->create_assessment($this->submission212, $this->student1->id, [ 168 'grade' => 68, 169 'gradinggradeover' => 80, 170 'gradinggradeoverby' => $this->teacher4->id, 171 'feedbackauthor' => 'This is what student 1 thinks about submission 212', 172 'feedbackreviewer' => 'This is what the teacher thinks about this assessment', 173 ]); 174 } 175 176 /** 177 * Test {@link \mod_workshop\privacy\provider::get_contexts_for_userid()} implementation. 178 */ 179 public function test_get_contexts_for_userid() { 180 181 $cm11 = get_coursemodule_from_instance('workshop', $this->workshop11->id); 182 $cm12 = get_coursemodule_from_instance('workshop', $this->workshop12->id); 183 $cm21 = get_coursemodule_from_instance('workshop', $this->workshop21->id); 184 185 $context11 = context_module::instance($cm11->id); 186 $context12 = context_module::instance($cm12->id); 187 $context21 = context_module::instance($cm21->id); 188 189 // Student1 has data in workshop11 (author + self reviewer), workshop12 (author) and workshop21 (reviewer). 190 $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student1->id); 191 $this->assertInstanceOf(\core_privacy\local\request\contextlist::class, $contextlist); 192 $this->assertEquals([$context11->id, $context12->id, $context21->id], $contextlist->get_contextids(), '', 0.0, 10, true); 193 194 // Student2 has data in workshop11 (reviewer), workshop12 (reviewer) and workshop21 (author). 195 $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student2->id); 196 $this->assertEquals([$context11->id, $context12->id, $context21->id], $contextlist->get_contextids(), '', 0.0, 10, true); 197 198 // Student3 has data in workshop11 (reviewer). 199 $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->student3->id); 200 $this->assertEquals([$context11->id], $contextlist->get_contextids(), '', 0.0, 10, true); 201 202 // Teacher4 has data in workshop12 (gradeoverby) and workshop21 (gradinggradeoverby). 203 $contextlist = \mod_workshop\privacy\provider::get_contexts_for_userid($this->teacher4->id); 204 $this->assertEquals([$context21->id, $context12->id], $contextlist->get_contextids(), '', 0.0, 10, true); 205 } 206 207 /** 208 * Test {@link \mod_workshop\privacy\provider::get_users_in_context()} implementation. 209 */ 210 public function test_get_users_in_context() { 211 212 $cm11 = get_coursemodule_from_instance('workshop', $this->workshop11->id); 213 $cm12 = get_coursemodule_from_instance('workshop', $this->workshop12->id); 214 $cm21 = get_coursemodule_from_instance('workshop', $this->workshop21->id); 215 216 $context11 = context_module::instance($cm11->id); 217 $context12 = context_module::instance($cm12->id); 218 $context21 = context_module::instance($cm21->id); 219 220 // Users in the workshop11. 221 $userlist11 = new \core_privacy\local\request\userlist($context11, 'mod_workshop'); 222 \mod_workshop\privacy\provider::get_users_in_context($userlist11); 223 $expected11 = [ 224 $this->student1->id, // Student1 has data in workshop11 (author + self reviewer). 225 $this->student2->id, // Student2 has data in workshop11 (reviewer). 226 $this->student3->id, // Student3 has data in workshop11 (reviewer). 227 ]; 228 $actual11 = $userlist11->get_userids(); 229 $this->assertEquals($expected11, $actual11, '', 0, 10, true); 230 231 // Users in the workshop12. 232 $userlist12 = new \core_privacy\local\request\userlist($context12, 'mod_workshop'); 233 \mod_workshop\privacy\provider::get_users_in_context($userlist12); 234 $expected12 = [ 235 $this->student1->id, // Student1 has data in workshop12 (author). 236 $this->student2->id, // Student2 has data in workshop12 (reviewer). 237 $this->teacher4->id, // Teacher4 has data in workshop12 (gradeoverby). 238 ]; 239 $actual12 = $userlist12->get_userids(); 240 $this->assertEquals($expected12, $actual12, '', 0, 10, true); 241 242 // Users in the workshop21. 243 $userlist21 = new \core_privacy\local\request\userlist($context21, 'mod_workshop'); 244 \mod_workshop\privacy\provider::get_users_in_context($userlist21); 245 $expected21 = [ 246 $this->student1->id, // Student1 has data in workshop21 (reviewer). 247 $this->student2->id, // Student2 has data in workshop21 (author). 248 $this->teacher4->id, // Teacher4 has data in workshop21 (gradinggradeoverby). 249 ]; 250 $actual21 = $userlist21->get_userids(); 251 $this->assertEquals($expected21, $actual21, '', 0, 10, true); 252 } 253 254 /** 255 * Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation. 256 */ 257 public function test_export_user_data_1() { 258 259 $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [ 260 \context_module::instance($this->workshop11->cmid)->id, 261 \context_module::instance($this->workshop12->cmid)->id, 262 ]); 263 264 \mod_workshop\privacy\provider::export_user_data($contextlist); 265 266 $writer = writer::with_context(\context_module::instance($this->workshop11->cmid)); 267 268 $workshop = $writer->get_data([]); 269 $this->assertEquals('Workshop11', $workshop->name); 270 $this->assertObjectHasAttribute('phase', $workshop); 271 272 $mysubmission = $writer->get_data([ 273 get_string('mysubmission', 'mod_workshop'), 274 ]); 275 276 $mysubmissionselfassessmentwithoutgrade = $writer->get_data([ 277 get_string('mysubmission', 'mod_workshop'), 278 get_string('assessments', 'mod_workshop'), 279 $this->assessment1111, 280 ]); 281 $this->assertNull($mysubmissionselfassessmentwithoutgrade->grade); 282 $this->assertEquals(get_string('yes'), $mysubmissionselfassessmentwithoutgrade->selfassessment); 283 284 $mysubmissionassessmentwithgrade = $writer->get_data([ 285 get_string('mysubmission', 'mod_workshop'), 286 get_string('assessments', 'mod_workshop'), 287 $this->assessment1112, 288 ]); 289 $this->assertEquals(92, $mysubmissionassessmentwithgrade->grade); 290 $this->assertEquals(get_string('no'), $mysubmissionassessmentwithgrade->selfassessment); 291 292 $mysubmissionassessmentwithoutgrade = $writer->get_data([ 293 get_string('mysubmission', 'mod_workshop'), 294 get_string('assessments', 'mod_workshop'), 295 $this->assessment1113, 296 ]); 297 $this->assertEquals(null, $mysubmissionassessmentwithoutgrade->grade); 298 $this->assertEquals(get_string('no'), $mysubmissionassessmentwithoutgrade->selfassessment); 299 300 $myassessments = $writer->get_data([ 301 get_string('myassessments', 'mod_workshop'), 302 ]); 303 $this->assertEmpty($myassessments); 304 } 305 306 /** 307 * Test {@link \mod_workshop\privacy\provider::export_user_data()} implementation. 308 */ 309 public function test_export_user_data_2() { 310 311 $contextlist = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_workshop', [ 312 \context_module::instance($this->workshop11->cmid)->id, 313 ]); 314 315 \mod_workshop\privacy\provider::export_user_data($contextlist); 316 317 $writer = writer::with_context(\context_module::instance($this->workshop11->cmid)); 318 319 $assessedsubmission = $writer->get_related_data([ 320 get_string('myassessments', 'mod_workshop'), 321 $this->assessment1112, 322 ], 'submission'); 323 $this->assertEquals(get_string('no'), $assessedsubmission->myownsubmission); 324 } 325 326 /** 327 * Test {@link \mod_workshop\privacy\provider::delete_data_for_all_users_in_context()} implementation. 328 */ 329 public function test_delete_data_for_all_users_in_context() { 330 global $DB; 331 332 $this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id])); 333 334 // Passing a non-module context does nothing. 335 \mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_course::instance($this->course1->id)); 336 $this->assertTrue($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id])); 337 338 // Passing a workshop context removes all data. 339 \mod_workshop\privacy\provider::delete_data_for_all_users_in_context(\context_module::instance($this->workshop11->cmid)); 340 $this->assertFalse($DB->record_exists('workshop_submissions', ['workshopid' => $this->workshop11->id])); 341 } 342 343 /** 344 * Test {@link \mod_workshop\privacy\provider::delete_data_for_user()} implementation. 345 */ 346 public function test_delete_data_for_user() { 347 global $DB; 348 349 $student1submissions = $DB->get_records('workshop_submissions', [ 350 'workshopid' => $this->workshop12->id, 351 'authorid' => $this->student1->id, 352 ]); 353 354 $student2submissions = $DB->get_records('workshop_submissions', [ 355 'workshopid' => $this->workshop12->id, 356 'authorid' => $this->student2->id, 357 ]); 358 359 $this->assertNotEmpty($student1submissions); 360 $this->assertNotEmpty($student2submissions); 361 362 foreach ($student1submissions as $submission) { 363 $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title); 364 } 365 366 foreach ($student2submissions as $submission) { 367 $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title); 368 } 369 370 $contextlist = new \core_privacy\local\request\approved_contextlist($this->student1, 'mod_workshop', [ 371 \context_module::instance($this->workshop12->cmid)->id, 372 \context_module::instance($this->workshop21->cmid)->id, 373 ]); 374 375 \mod_workshop\privacy\provider::delete_data_for_user($contextlist); 376 377 $student1submissions = $DB->get_records('workshop_submissions', [ 378 'workshopid' => $this->workshop12->id, 379 'authorid' => $this->student1->id, 380 ]); 381 382 $student2submissions = $DB->get_records('workshop_submissions', [ 383 'workshopid' => $this->workshop12->id, 384 'authorid' => $this->student2->id, 385 ]); 386 387 $this->assertNotEmpty($student1submissions); 388 $this->assertNotEmpty($student2submissions); 389 390 foreach ($student1submissions as $submission) { 391 $this->assertEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title); 392 } 393 394 foreach ($student2submissions as $submission) { 395 $this->assertNotEquals(get_string('privacy:request:delete:title', 'mod_workshop'), $submission->title); 396 } 397 398 $student1assessments = $DB->get_records('workshop_assessments', [ 399 'submissionid' => $this->submission212, 400 'reviewerid' => $this->student1->id, 401 ]); 402 $this->assertNotEmpty($student1assessments); 403 404 foreach ($student1assessments as $assessment) { 405 // In Moodle, feedback is seen to belong to the recipient user. 406 $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor); 407 $this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer); 408 // We delete what we can without affecting others' grades. 409 $this->assertEquals(68, $assessment->grade); 410 } 411 412 $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($student1submissions)); 413 $this->assertNotEmpty($assessments); 414 415 foreach ($assessments as $assessment) { 416 if ($assessment->reviewerid == $this->student1->id) { 417 $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor); 418 $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer); 419 420 } else { 421 $this->assertEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackauthor); 422 $this->assertNotEquals(get_string('privacy:request:delete:content', 'mod_workshop'), $assessment->feedbackreviewer); 423 } 424 } 425 } 426 427 /** 428 * Test {@link \mod_workshop\privacy\provider::delete_data_for_users()} implementation. 429 */ 430 public function test_delete_data_for_users() { 431 global $DB; 432 433 // Student1 has submissions in two workshops. 434 $this->assertFalse($this->is_submission_erased($this->submission111)); 435 $this->assertFalse($this->is_submission_erased($this->submission121)); 436 437 // Student1 has self-assessed one their submission. 438 $this->assertFalse($this->is_given_assessment_erased($this->assessment1111)); 439 $this->assertFalse($this->is_received_assessment_erased($this->assessment1111)); 440 441 // Student2 and student3 peer-assessed student1's submission. 442 $this->assertFalse($this->is_given_assessment_erased($this->assessment1112)); 443 $this->assertFalse($this->is_given_assessment_erased($this->assessment1113)); 444 445 // Delete data owned by student1 and student3 in the workshop11. 446 447 $context11 = \context_module::instance($this->workshop11->cmid); 448 449 $approveduserlist = new \core_privacy\local\request\approved_userlist($context11, 'mod_workshop', [ 450 $this->student1->id, 451 $this->student3->id, 452 ]); 453 \mod_workshop\privacy\provider::delete_data_for_users($approveduserlist); 454 455 // Student1's submission is erased in workshop11 but not in the other workshop12. 456 $this->assertTrue($this->is_submission_erased($this->submission111)); 457 $this->assertFalse($this->is_submission_erased($this->submission121)); 458 459 // Student1's self-assessment is erased. 460 $this->assertTrue($this->is_given_assessment_erased($this->assessment1111)); 461 $this->assertTrue($this->is_received_assessment_erased($this->assessment1111)); 462 463 // Student1's received peer-assessments are also erased because they are "owned" by the recipient of the assessment. 464 $this->assertTrue($this->is_received_assessment_erased($this->assessment1112)); 465 $this->assertTrue($this->is_received_assessment_erased($this->assessment1113)); 466 467 // Student2's owned data in the given assessment are not erased. 468 $this->assertFalse($this->is_given_assessment_erased($this->assessment1112)); 469 470 // Student3's owned data in the given assessment were erased because she/he was in the userlist. 471 $this->assertTrue($this->is_given_assessment_erased($this->assessment1113)); 472 473 // Personal data in other contexts are not affected. 474 $this->assertFalse($this->is_submission_erased($this->submission121)); 475 $this->assertFalse($this->is_given_assessment_erased($this->assessment2121)); 476 $this->assertFalse($this->is_received_assessment_erased($this->assessment2121)); 477 } 478 479 /** 480 * Check if the given submission has the author's personal data erased. 481 * 482 * @param int $submissionid Identifier of the submission. 483 * @return boolean 484 */ 485 protected function is_submission_erased(int $submissionid) { 486 global $DB; 487 488 $submission = $DB->get_record('workshop_submissions', ['id' => $submissionid], 'id, title, content', MUST_EXIST); 489 490 $titledeleted = $submission->title === get_string('privacy:request:delete:title', 'mod_workshop'); 491 $contentdeleted = $submission->content === get_string('privacy:request:delete:content', 'mod_workshop'); 492 493 if ($titledeleted && $contentdeleted) { 494 return true; 495 496 } else { 497 return false; 498 } 499 } 500 501 /** 502 * Check is the received assessment has recipient's (author's) personal data erased. 503 * 504 * @param int $assessmentid Identifier of the assessment. 505 * @return boolean 506 */ 507 protected function is_received_assessment_erased(int $assessmentid) { 508 global $DB; 509 510 $assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackauthor', MUST_EXIST); 511 512 if ($assessment->feedbackauthor === get_string('privacy:request:delete:content', 'mod_workshop')) { 513 return true; 514 515 } else { 516 return false; 517 } 518 } 519 520 /** 521 * Check is the given assessment has reviewer's personal data erased. 522 * 523 * @param int $assessmentid Identifier of the assessment. 524 * @return boolean 525 */ 526 protected function is_given_assessment_erased(int $assessmentid) { 527 global $DB; 528 529 $assessment = $DB->get_record('workshop_assessments', ['id' => $assessmentid], 'id, feedbackreviewer', MUST_EXIST); 530 531 if ($assessment->feedbackreviewer === get_string('privacy:request:delete:content', 'mod_workshop')) { 532 return true; 533 534 } else { 535 return false; 536 } 537 } 538 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body