MDL-61407 mod_quiz: Add initial privacy implementation
[moodle.git] / mod / quiz / tests / privacy_provider_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  * Privacy provider tests.
19  *
20  * @package    mod_quiz
21  * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 use core_privacy\local\metadata\collection;
26 use core_privacy\local\request\deletion_criteria;
27 use core_privacy\local\request\writer;
28 use mod_quiz\privacy\provider;
29 use mod_quiz\privacy\helper;
31 defined('MOODLE_INTERNAL') || die();
33 global $CFG;
34 require_once($CFG->dirroot . '/question/tests/privacy_helper.php');
36 /**
37  * Privacy provider tests class.
38  *
39  * @package    mod_quiz
40  * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
41  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42  */
43 class mod_quiz_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
45     use core_question_privacy_helper;
47     /**
48      * Test that a user who has no data gets no contexts
49      */
50     public function test_get_contexts_for_userid_no_data() {
51         global $USER;
52         $this->resetAfterTest();
53         $this->setAdminUser();
55         $contextlist = provider::get_contexts_for_userid($USER->id);
56         $this->assertEmpty($contextlist);
57     }
59     /**
60      * The export function should handle an empty contextlist properly.
61      */
62     public function test_export_user_data_no_data() {
63         global $USER;
64         $this->resetAfterTest();
65         $this->setAdminUser();
67         $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
68             \core_user::get_user($USER->id),
69             'mod_quiz',
70             []
71         );
73         provider::export_user_data($approvedcontextlist);
74         $this->assertDebuggingNotCalled();
76         // No data should have been exported.
77         $writer = \core_privacy\local\request\writer::with_context(\context_system::instance());
78         $this->assertFalse($writer->has_any_data_in_any_context());
79     }
81     /**
82      * The delete function should handle an empty contextlist properly.
83      */
84     public function test_delete_data_for_user_no_data() {
85         global $USER;
86         $this->resetAfterTest();
87         $this->setAdminUser();
89         $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
90             \core_user::get_user($USER->id),
91             'mod_quiz',
92             []
93         );
95         provider::delete_data_for_user($approvedcontextlist);
96         $this->assertDebuggingNotCalled();
97     }
99     /**
100      * Export + Delete quiz data for a user who has made a single attempt.
101      */
102     public function test_user_with_data() {
103         global $DB;
104         $this->resetAfterTest(true);
106         // Make a quiz.
107         $course = $this->getDataGenerator()->create_course();
108         $user = $this->getDataGenerator()->create_user();
109         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
111         $quiz = $quizgenerator->create_instance([
112                 'course' => $course->id,
113                 'questionsperpage' => 0,
114                 'grade' => 100.0,
115                 'sumgrades' => 2,
116             ]);
118         // Create a couple of questions.
119         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
120         $cat = $questiongenerator->create_question_category();
122         $saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
123         quiz_add_quiz_question($saq->id, $quiz);
124         $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
125         quiz_add_quiz_question($numq->id, $quiz);
127         // Run as the user and make an attempt on the quiz.
128         $this->setUser($user);
129         $starttime = time();
130         $quizobj = quiz::create($quiz->id, $user->id);
131         $context = $quizobj->get_context();
133         $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
134         $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
136         // Start the attempt.
137         $attempt = quiz_create_attempt($quizobj, 1, false, $starttime, false, $user->id);
138         quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
139         quiz_attempt_save_started($quizobj, $quba, $attempt);
141         // Answer the questions.
142         $attemptobj = quiz_attempt::create($attempt->id);
144         $tosubmit = [
145             1 => ['answer' => 'frog'],
146             2 => ['answer' => '3.14'],
147         ];
149         $attemptobj->process_submitted_actions($starttime, false, $tosubmit);
151         // Finish the attempt.
152         $attemptobj = quiz_attempt::create($attempt->id);
153         $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
154         $attemptobj->process_finish($starttime, false);
156         // Fetch the contexts -only one context should be returned.
157         $this->setUser();
158         $contextlist = provider::get_contexts_for_userid($user->id);
159         $this->assertCount(1, $contextlist);
160         $this->assertEquals($context, $contextlist->current());
162         // Perform the export and check the data.
163         $this->setUser($user);
164         $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
165             \core_user::get_user($user->id),
166             'mod_quiz',
167             $contextlist->get_contextids()
168         );
169         provider::export_user_data($approvedcontextlist);
171         // Ensure that the quiz data was exported correctly.
172         $writer = writer::with_context($context);
173         $this->assertTrue($writer->has_any_data());
175         $quizdata = $writer->get_data([]);
176         $this->assertEquals($quizobj->get_quiz_name(), $quizdata->name);
178         // Every module has an intro.
179         $this->assertTrue(isset($quizdata->intro));
181         // Fetch the attempt data.
182         $attemptsubcontext = [
183             get_string('attempts', 'mod_quiz'),
184             $attempt->attempt,
185         ];
186         $attemptdata = writer::with_context($context)->get_data($attemptsubcontext);
188         $attempt = $attemptobj->get_attempt();
189         $this->assertTrue(isset($attemptdata->state));
190         $this->assertEquals(\quiz_attempt::state_name($attemptobj->get_state()), $attemptdata->state);
191         $this->assertTrue(isset($attemptdata->timestart));
192         $this->assertTrue(isset($attemptdata->timefinish));
193         $this->assertTrue(isset($attemptdata->timemodified));
194         $this->assertFalse(isset($attemptdata->timemodifiedoffline));
195         $this->assertFalse(isset($attemptdata->timecheckstate));
197         $this->assertTrue(isset($attemptdata->grade));
198         $this->assertEquals(100.00, $attemptdata->grade->grade);
200         // Check that the exported question attempts are correct.
201         $attemptsubcontext = helper::get_quiz_attempt_subcontext($attemptobj->get_attempt(), $user);
202         $this->assert_question_attempt_exported(
203             $context,
204             $attemptsubcontext,
205             \question_engine::load_questions_usage_by_activity($attemptobj->get_uniqueid()),
206             quiz_get_review_options($quiz, $attemptobj->get_attempt(), $context),
207             $user
208         );
210         // Delete the data and check it is removed.
211         $this->setUser();
212         provider::delete_data_for_user($approvedcontextlist);
213         $this->expectException(\dml_missing_record_exception::class);
214         \quiz_attempt::create($attemptobj->get_quizid());
215     }
217     /**
218      * Export + Delete quiz data for a user who has made a single attempt.
219      */
220     public function test_user_with_preview() {
221         global $DB;
222         $this->resetAfterTest(true);
224         // Make a quiz.
225         $course = $this->getDataGenerator()->create_course();
226         $user = $this->getDataGenerator()->create_user();
227         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
229         $quiz = $quizgenerator->create_instance([
230                 'course' => $course->id,
231                 'questionsperpage' => 0,
232                 'grade' => 100.0,
233                 'sumgrades' => 2,
234             ]);
236         // Create a couple of questions.
237         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
238         $cat = $questiongenerator->create_question_category();
240         $saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
241         quiz_add_quiz_question($saq->id, $quiz);
242         $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
243         quiz_add_quiz_question($numq->id, $quiz);
245         // Run as the user and make an attempt on the quiz.
246         $this->setUser($user);
247         $starttime = time();
248         $quizobj = quiz::create($quiz->id, $user->id);
249         $context = $quizobj->get_context();
251         $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
252         $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
254         // Start the attempt.
255         $attempt = quiz_create_attempt($quizobj, 1, false, $starttime, true, $user->id);
256         quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
257         quiz_attempt_save_started($quizobj, $quba, $attempt);
259         // Answer the questions.
260         $attemptobj = quiz_attempt::create($attempt->id);
262         $tosubmit = [
263             1 => ['answer' => 'frog'],
264             2 => ['answer' => '3.14'],
265         ];
267         $attemptobj->process_submitted_actions($starttime, false, $tosubmit);
269         // Finish the attempt.
270         $attemptobj = quiz_attempt::create($attempt->id);
271         $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
272         $attemptobj->process_finish($starttime, false);
274         // Fetch the contexts - no context should be returned.
275         $this->setUser();
276         $contextlist = provider::get_contexts_for_userid($user->id);
277         $this->assertCount(0, $contextlist);
278     }