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