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