Merge branch 'MDL-62277-master' of git://github.com/bmbrands/moodle
[moodle.git] / mod / assign / tests / privacy_test.php
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/>.
17 /**
18  * Base class for unit tests for mod_assign.
19  *
20  * @package    mod_assign
21  * @copyright  2018 Adrian Greeve <adrian@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace mod_assign\tests;
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->dirroot . '/mod/assign/locallib.php');
32 use \core_privacy\tests\provider_testcase;
33 use \core_privacy\local\request\writer;
34 use \core_privacy\local\request\approved_contextlist;
35 use \mod_assign\privacy\provider;
37 /**
38  * Unit tests for mod/assign/classes/privacy/
39  *
40  * @copyright  2018 Adrian Greeve <adrian@moodle.com>
41  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42  */
43 class mod_assign_privacy_testcase extends provider_testcase {
45     /**
46      * Convenience method for creating a submission.
47      *
48      * @param  assign  $assign The assign object
49      * @param  stdClass  $user The user object
50      * @param  string  $submissiontext Submission text
51      * @param  integer $attemptnumber The attempt number
52      * @return object A submission object.
53      */
54     protected function create_submission($assign, $user, $submissiontext, $attemptnumber = 0) {
55         $submission = $assign->get_user_submission($user->id, true, $attemptnumber);
56         $submission->onlinetext_editor = ['text' => $submissiontext,
57                                          'format' => FORMAT_MOODLE];
59         $this->setUser($user);
60         $notices = [];
61         $assign->save_submission($submission, $notices);
62         return $submission;
63     }
65     /**
66      * Convenience function to create an instance of an assignment.
67      *
68      * @param array $params Array of parameters to pass to the generator
69      * @return assign The assign class.
70      */
71     protected function create_instance($params = array()) {
72         $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
73         $instance = $generator->create_instance($params);
74         $cm = get_coursemodule_from_instance('assign', $instance->id);
75         $context = \context_module::instance($cm->id);
76         return new \assign($context, $cm, $params['course']);
77     }
79     /**
80      * Test that getting the contexts for a user works.
81      */
82     public function test_get_contexts_for_userid() {
83         global $DB;
84         $this->resetAfterTest();
86         $course1 = $this->getDataGenerator()->create_course();
87         $course2 = $this->getDataGenerator()->create_course();
88         $course3 = $this->getDataGenerator()->create_course();
90         $user1 = $this->getDataGenerator()->create_user();
91         $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
92         $this->getDataGenerator()->enrol_user($user1->id, $course3->id, 'student');
93         // Need a second user to create content in other assignments.
94         $user2 = $this->getDataGenerator()->create_user();
95         $this->getDataGenerator()->enrol_user($user2->id, $course2->id, 'student');
97         // Create multiple assignments.
98         // Assignment with a text submission.
99         $assign1 = $this->create_instance(['course' => $course1]);
100         // Assignment two in a different course that the user is not enrolled in.
101         $assign2 = $this->create_instance(['course' => $course2]);
102         // Assignment three has an entry in the override table.
103         $assign3 = $this->create_instance(['course' => $course3, 'cutoffdate' => time()]);
104         // Assignment four - blind marking.
105         $assign4 = $this->create_instance(['course' => $course1, 'blindmarking' => 1]);
106         // Assignment five - user flags.
107         $assign5 = $this->create_instance(['course' => $course3]);
109         // Override has to be manually inserted into the DB.
110         $overridedata = new \stdClass();
111         $overridedata->assignid = $assign3->get_instance()->id;
112         $overridedata->userid = $user1->id;
113         $overridedata->duedate = time();
114         $DB->insert_record('assign_overrides', $overridedata);
115         // Assign unique id for blind marking in assignment four for user 1.
116         \assign::get_uniqueid_for_user_static($assign4->get_instance()->id, $user1->id);
117         // Create an entry in the user flags table.
118         $assign5->get_user_flags($user1->id, true);
120         // The user will be in these contexts.
121         $usercontextids = [
122             $assign1->get_context()->id,
123             $assign3->get_context()->id,
124             $assign4->get_context()->id,
125             $assign5->get_context()->id,
126         ];
128         $submission = new \stdClass();
129         $submission->assignment = $assign1->get_instance()->id;
130         $submission->userid = $user1->id;
131         $submission->timecreated = time();
132         $submission->onlinetext_editor = ['text' => 'Submission text',
133                                          'format' => FORMAT_MOODLE];
135         $this->setUser($user1);
136         $notices = [];
137         $assign1->save_submission($submission, $notices);
139         // Create a submission for the second assignment.
140         $submission->assignment = $assign2->get_instance()->id;
141         $submission->userid = $user2->id;
142         $this->setUser($user2);
143         $assign2->save_submission($submission, $notices);
145         $contextlist = provider::get_contexts_for_userid($user1->id);
146         $this->assertEquals(count($usercontextids), count($contextlist->get_contextids()));
147         // There should be no difference between the contexts.
148         $this->assertEmpty(array_diff($usercontextids, $contextlist->get_contextids()));
149     }
151     /**
152      * Test that a student with multiple submissions and grades is returned with the correct data.
153      */
154     public function test_export_user_data_student() {
155         $this->resetAfterTest();
156         $course = $this->getDataGenerator()->create_course();
157         $coursecontext = \context_course::instance($course->id);
159         $user = $this->getDataGenerator()->create_user();
160         $teacher = $this->getDataGenerator()->create_user();
161         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
162         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
163         $assign = $this->create_instance([
164                 'course' => $course,
165                 'name' => 'Assign 1',
166                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
167                 'maxattempts' => 3,
168                 'assignsubmission_onlinetext_enabled' => true,
169                 'assignfeedback_comments_enabled' => true
170             ]);
172         $context = $assign->get_context();
173         // Create some submissions (multiple attempts) for a student.
174         $submissiontext = 'My first submission';
175         $submission = $this->create_submission($assign, $user, $submissiontext);
177         $this->setUser($teacher);
179         $grade1 = '67.00';
180         $teachercommenttext = 'Please try again.';
181         $data = new \stdClass();
182         $data->attemptnumber = 0;
183         $data->grade = $grade1;
184         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
186         // Give the submission a grade.
187         $assign->save_grade($user->id, $data);
189         $submissiontext2 = 'My second submission';
190         $submission = $this->create_submission($assign, $user, $submissiontext2, 1);
192         $this->setUser($teacher);
194         $grade2 = '72.00';
195         $teachercommenttext2 = 'This is better. Thanks.';
196         $data = new \stdClass();
197         $data->attemptnumber = 1;
198         $data->grade = $grade2;
199         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext2, 'format' => FORMAT_MOODLE];
201         // Give the submission a grade.
202         $assign->save_grade($user->id, $data);
204         $writer = writer::with_context($context);
205         $this->assertFalse($writer->has_any_data());
207         // The student should have some text submitted.
208         // Add the course context as well to make sure there is no error.
209         $approvedlist = new approved_contextlist($user, 'mod_assign', [$context->id, $coursecontext->id]);
210         provider::export_user_data($approvedlist);
212         // Check that we have general details about the assignment.
213         $this->assertEquals('Assign 1', $writer->get_data()->name);
214         // Check Submissions.
215         $this->assertEquals($submissiontext, $writer->get_data(['attempt 1', 'Submission Text'])->text);
216         $this->assertEquals($submissiontext2, $writer->get_data(['attempt 2', 'Submission Text'])->text);
217         $this->assertEquals(1, $writer->get_data(['attempt 1', 'submission'])->attemptnumber);
218         $this->assertEquals(2, $writer->get_data(['attempt 2', 'submission'])->attemptnumber);
219         // Check grades.
220         $this->assertEquals($grade1, $writer->get_data(['attempt 1', 'grade'])->grade);
221         $this->assertEquals($grade2, $writer->get_data(['attempt 2', 'grade'])->grade);
222         // Check feedback.
223         $this->assertContains($teachercommenttext, $writer->get_data(['attempt 1', 'Feedback comments'])->commenttext);
224         $this->assertContains($teachercommenttext2, $writer->get_data(['attempt 2', 'Feedback comments'])->commenttext);
225     }
227     /**
228      * Tests the data returned for a teacher.
229      */
230     public function test_export_user_data_teacher() {
231         $this->resetAfterTest();
232         $course = $this->getDataGenerator()->create_course();
233         $coursecontext = \context_course::instance($course->id);
235         $user1 = $this->getDataGenerator()->create_user();
236         $user2 = $this->getDataGenerator()->create_user();
237         $teacher = $this->getDataGenerator()->create_user();
238         $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
239         $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
240         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
241         $assign = $this->create_instance([
242                 'course' => $course,
243                 'name' => 'Assign 1',
244                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
245                 'maxattempts' => 3,
246                 'assignsubmission_onlinetext_enabled' => true,
247                 'assignfeedback_comments_enabled' => true
248             ]);
250         $context = $assign->get_context();
252         // Create and grade some submissions from the students.
253         $submissiontext = 'My first submission';
254         $submission = $this->create_submission($assign, $user1, $submissiontext);
256         $this->setUser($teacher);
258         $grade1 = '54.00';
259         $teachercommenttext = 'Comment on user 1 attempt 1.';
260         $data = new \stdClass();
261         $data->attemptnumber = 0;
262         $data->grade = $grade1;
263         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
265         // Give the submission a grade.
266         $assign->save_grade($user1->id, $data);
268         // Create and grade some submissions from the students.
269         $submissiontext2 = 'My first submission for user 2';
270         $submission = $this->create_submission($assign, $user2, $submissiontext2);
272         $this->setUser($teacher);
274         $grade2 = '56.00';
275         $teachercommenttext2 = 'Comment on user 2 first attempt.';
276         $data = new \stdClass();
277         $data->attemptnumber = 0;
278         $data->grade = $grade2;
279         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext2, 'format' => FORMAT_MOODLE];
281         // Give the submission a grade.
282         $assign->save_grade($user2->id, $data);
284         // Create and grade some submissions from the students.
285         $submissiontext3 = 'My second submission for user 2';
286         $submission = $this->create_submission($assign, $user2, $submissiontext3, 1);
288         $this->setUser($teacher);
290         $grade3 = '83.00';
291         $teachercommenttext3 = 'Comment on user 2 another attempt.';
292         $data = new \stdClass();
293         $data->attemptnumber = 1;
294         $data->grade = $grade3;
295         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext3, 'format' => FORMAT_MOODLE];
297         // Give the submission a grade.
298         $assign->save_grade($user2->id, $data);
300         // Set up some flags.
301         $duedate = time();
302         $flagdata = $assign->get_user_flags($teacher->id, true);
303         $flagdata->mailed = 1;
304         $flagdata->extensionduedate = $duedate;
305         $assign->update_user_flags($flagdata);
307         $writer = writer::with_context($context);
308         $this->assertFalse($writer->has_any_data());
310         // The student should have some text submitted.
311         $approvedlist = new approved_contextlist($teacher, 'mod_assign', [$context->id, $coursecontext->id]);
312         provider::export_user_data($approvedlist);
314         // Check flag metadata.
315         $metadata = $writer->get_all_metadata();
316         $this->assertEquals(\core_privacy\local\request\transform::yesno(1), $metadata['mailed']->value);
317         $this->assertEquals(\core_privacy\local\request\transform::datetime($duedate), $metadata['extensionduedate']->value);
319         // Check for student grades given.
320         $student1grade = $writer->get_data(['studentsubmissions', $user1->id, 'attempt 1', 'grade']);
321         $this->assertEquals($grade1, $student1grade->grade);
322         $student2grade1 = $writer->get_data(['studentsubmissions', $user2->id, 'attempt 1', 'grade']);
323         $this->assertEquals($grade2, $student2grade1->grade);
324         $student2grade2 = $writer->get_data(['studentsubmissions', $user2->id, 'attempt 2', 'grade']);
325         $this->assertEquals($grade3, $student2grade2->grade);
326         // Check for feedback given to students.
327         $this->assertContains($teachercommenttext, $writer->get_data(['studentsubmissions', $user1->id, 'attempt 1',
328                 'Feedback comments'])->commenttext);
329         $this->assertContains($teachercommenttext2, $writer->get_data(['studentsubmissions', $user2->id, 'attempt 1',
330                 'Feedback comments'])->commenttext);
331         $this->assertContains($teachercommenttext3, $writer->get_data(['studentsubmissions', $user2->id, 'attempt 2',
332                 'Feedback comments'])->commenttext);
333     }
335     /**
336      * A test for deleting all user data for a given context.
337      */
338     public function test_delete_data_for_all_users_in_context() {
339         global $DB;
340         $this->resetAfterTest();
341         $course = $this->getDataGenerator()->create_course();
343         $user1 = $this->getDataGenerator()->create_user();
344         $user2 = $this->getDataGenerator()->create_user();
345         $teacher = $this->getDataGenerator()->create_user();
346         $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
347         $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
348         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
349         $assign = $this->create_instance([
350                 'course' => $course,
351                 'name' => 'Assign 1',
352                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
353                 'maxattempts' => 3,
354                 'assignsubmission_onlinetext_enabled' => true,
355                 'assignfeedback_comments_enabled' => true
356             ]);
358         $context = $assign->get_context();
360         // Create and grade some submissions from the students.
361         $submissiontext = 'My first submission';
362         $submission = $this->create_submission($assign, $user1, $submissiontext);
364         $this->setUser($teacher);
366         // Overrides for both students.
367         $overridedata = new \stdClass();
368         $overridedata->assignid = $assign->get_instance()->id;
369         $overridedata->userid = $user1->id;
370         $overridedata->duedate = time();
371         $DB->insert_record('assign_overrides', $overridedata);
372         $overridedata->userid = $user2->id;
373         $DB->insert_record('assign_overrides', $overridedata);
374         assign_update_events($assign);
376         $grade1 = '54.00';
377         $teachercommenttext = 'Comment on user 1 attempt 1.';
378         $data = new \stdClass();
379         $data->attemptnumber = 0;
380         $data->grade = $grade1;
381         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
383         // Give the submission a grade.
384         $assign->save_grade($user1->id, $data);
386         // Create and grade some submissions from the students.
387         $submissiontext2 = 'My first submission for user 2';
388         $submission = $this->create_submission($assign, $user2, $submissiontext2);
390         $this->setUser($teacher);
392         $grade2 = '56.00';
393         $teachercommenttext2 = 'Comment on user 2 first attempt.';
394         $data = new \stdClass();
395         $data->attemptnumber = 0;
396         $data->grade = $grade2;
397         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext2, 'format' => FORMAT_MOODLE];
399         // Give the submission a grade.
400         $assign->save_grade($user2->id, $data);
402         // Create and grade some submissions from the students.
403         $submissiontext3 = 'My second submission for user 2';
404         $submission = $this->create_submission($assign, $user2, $submissiontext3, 1);
406         $this->setUser($teacher);
408         $grade3 = '83.00';
409         $teachercommenttext3 = 'Comment on user 2 another attempt.';
410         $data = new \stdClass();
411         $data->attemptnumber = 1;
412         $data->grade = $grade3;
413         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext3, 'format' => FORMAT_MOODLE];
415         // Give the submission a grade.
416         $assign->save_grade($user2->id, $data);
418         // Delete all user data for this assignment.
419         provider::delete_data_for_all_users_in_context($context);
421         // Check all relevant tables.
422         $records = $DB->get_records('assign_submission');
423         $this->assertEmpty($records);
424         $records = $DB->get_records('assign_grades');
425         $this->assertEmpty($records);
426         $records = $DB->get_records('assignsubmission_onlinetext');
427         $this->assertEmpty($records);
428         $records = $DB->get_records('assignfeedback_comments');
429         $this->assertEmpty($records);
431         // Check that overrides and the calendar events are deleted.
432         $records = $DB->get_records('event');
433         $this->assertEmpty($records);
434         $records = $DB->get_records('assign_overrides');
435         $this->assertEmpty($records);
436     }
438     /**
439      * A test for deleting all user data for one user.
440      */
441     public function test_delete_data_for_user() {
442         global $DB;
443         $this->resetAfterTest();
444         $course = $this->getDataGenerator()->create_course();
446         $coursecontext = \context_course::instance($course->id);
448         $user1 = $this->getDataGenerator()->create_user();
449         $user2 = $this->getDataGenerator()->create_user();
450         $teacher = $this->getDataGenerator()->create_user();
451         $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
452         $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
453         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
454         $assign = $this->create_instance([
455                 'course' => $course,
456                 'name' => 'Assign 1',
457                 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
458                 'maxattempts' => 3,
459                 'assignsubmission_onlinetext_enabled' => true,
460                 'assignfeedback_comments_enabled' => true
461             ]);
463         $context = $assign->get_context();
465         // Create and grade some submissions from the students.
466         $submissiontext = 'My first submission';
467         $submission1 = $this->create_submission($assign, $user1, $submissiontext);
469         $this->setUser($teacher);
471         // Overrides for both students.
472         $overridedata = new \stdClass();
473         $overridedata->assignid = $assign->get_instance()->id;
474         $overridedata->userid = $user1->id;
475         $overridedata->duedate = time();
476         $DB->insert_record('assign_overrides', $overridedata);
477         $overridedata->userid = $user2->id;
478         $DB->insert_record('assign_overrides', $overridedata);
479         assign_update_events($assign);
481         $grade1 = '54.00';
482         $teachercommenttext = 'Comment on user 1 attempt 1.';
483         $data = new \stdClass();
484         $data->attemptnumber = 0;
485         $data->grade = $grade1;
486         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext, 'format' => FORMAT_MOODLE];
488         // Give the submission a grade.
489         $assign->save_grade($user1->id, $data);
491         // Create and grade some submissions from the students.
492         $submissiontext2 = 'My first submission for user 2';
493         $submission2 = $this->create_submission($assign, $user2, $submissiontext2);
495         $this->setUser($teacher);
497         $grade2 = '56.00';
498         $teachercommenttext2 = 'Comment on user 2 first attempt.';
499         $data = new \stdClass();
500         $data->attemptnumber = 0;
501         $data->grade = $grade2;
502         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext2, 'format' => FORMAT_MOODLE];
504         // Give the submission a grade.
505         $assign->save_grade($user2->id, $data);
507         // Create and grade some submissions from the students.
508         $submissiontext3 = 'My second submission for user 2';
509         $submission3 = $this->create_submission($assign, $user2, $submissiontext3, 1);
511         $this->setUser($teacher);
513         $grade3 = '83.00';
514         $teachercommenttext3 = 'Comment on user 2 another attempt.';
515         $data = new \stdClass();
516         $data->attemptnumber = 1;
517         $data->grade = $grade3;
518         $data->assignfeedbackcomments_editor = ['text' => $teachercommenttext3, 'format' => FORMAT_MOODLE];
520         // Give the submission a grade.
521         $assign->save_grade($user2->id, $data);
523         // Delete user 2's data.
524         $approvedlist = new approved_contextlist($user2, 'mod_assign', [$context->id, $coursecontext->id]);
525         provider::delete_data_for_user($approvedlist);
527         // Check all relevant tables.
528         $records = $DB->get_records('assign_submission');
529         foreach ($records as $record) {
530             $this->assertEquals($user1->id, $record->userid);
531             $this->assertNotEquals($user2->id, $record->userid);
532         }
533         $records = $DB->get_records('assign_grades');
534         foreach ($records as $record) {
535             $this->assertEquals($user1->id, $record->userid);
536             $this->assertNotEquals($user2->id, $record->userid);
537         }
538         $records = $DB->get_records('assignsubmission_onlinetext');
539         $this->assertCount(1, $records);
540         $record = array_shift($records);
541         // The only submission is for user 1.
542         $this->assertEquals($submission1->id, $record->submission);
543         $records = $DB->get_records('assignfeedback_comments');
544         $this->assertCount(1, $records);
545         $record = array_shift($records);
546         // The only record is the feedback comment for user 1.
547         $this->assertEquals($teachercommenttext, $record->commenttext);
549         // Check calendar events as well as assign overrides.
550         $records = $DB->get_records('event');
551         $this->assertCount(1, $records);
552         $record = array_shift($records);
553         // The remaining event should be for user 1.
554         $this->assertEquals($user1->id, $record->userid);
555         // Now for assign_overrides
556         $records = $DB->get_records('assign_overrides');
557         $this->assertCount(1, $records);
558         $record = array_shift($records);
559         // The remaining event should be for user 1.
560         $this->assertEquals($user1->id, $record->userid);
561     }