2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Privacy provider tests.
20 * @package core_question
21 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 use core_privacy\local\metadata\collection
;
26 use core_privacy\local\request\deletion_criteria
;
27 use core_privacy\local\request\writer
;
28 use core_question\privacy\provider
;
30 defined('MOODLE_INTERNAL') ||
die();
33 require_once($CFG->libdir
. '/xmlize.php');
34 require_once(__DIR__
. '/privacy_helper.php');
35 require_once(__DIR__
. '/../engine/tests/helpers.php');
38 * Privacy provider tests class.
40 * @package core_question
41 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 class core_question_privacy_provider_testcase
extends \core_privacy\tests\provider_testcase
{
46 // Include the privacy helper which has assertions on it.
47 use core_question_privacy_helper
;
50 * Prepare a question attempt.
52 * @return question_usage_by_activity
54 protected function prepare_question_attempt() {
55 // Create a question with a usage from the current user.
56 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
57 $cat = $questiongenerator->create_question_category();
58 $quba = question_engine
::make_questions_usage_by_activity('core_question_preview', context_system
::instance());
59 $quba->set_preferred_behaviour('deferredfeedback');
60 $questiondata = $questiongenerator->create_question('numerical', null, ['category' => $cat->id
]);
61 $question = question_bank
::load_question($questiondata->id
);
62 $quba->add_question($question);
63 $quba->start_all_questions();
65 question_engine
::save_questions_usage_by_activity($quba);
71 * Test that calling export_question_usage on a usage belonging to a
72 * different user does not export any data.
74 public function test_export_question_usage_no_usage() {
75 $this->resetAfterTest();
77 $quba = $this->prepare_question_attempt();
79 // Create a question with a usage from the current user.
80 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
81 $cat = $questiongenerator->create_question_category();
82 $quba = question_engine
::make_questions_usage_by_activity('core_question_preview', context_system
::instance());
83 $quba->set_preferred_behaviour('deferredfeedback');
84 $questiondata = $questiongenerator->create_question('numerical', null, ['category' => $cat->id
]);
85 $question = question_bank
::load_question($questiondata->id
);
86 $quba->add_question($question);
87 $quba->start_all_questions();
89 question_engine
::save_questions_usage_by_activity($quba);
92 $testuser = $this->getDataGenerator()->create_user();
93 $this->setUser($testuser);
94 $context = $quba->get_owning_context();
95 $options = new \
question_display_options();
97 provider
::export_question_usage($testuser->id
, $context, [], $quba->get_id(), $options, false);
98 $writer = writer
::with_context($context);
100 $this->assertFalse($writer->has_any_data_in_any_context());
104 * Test that calling export_question_usage on a usage belonging to a
105 * different user but ignoring the user match
107 public function test_export_question_usage_with_usage() {
108 $this->resetAfterTest();
110 $quba = $this->prepare_question_attempt();
112 // Create a question with a usage from the current user.
113 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
114 $cat = $questiongenerator->create_question_category();
115 $quba = question_engine
::make_questions_usage_by_activity('core_question_preview', context_system
::instance());
116 $quba->set_preferred_behaviour('deferredfeedback');
118 $questiondata = $questiongenerator->create_question('truefalse', 'true', ['category' => $cat->id
]);
119 $quba->add_question(question_bank
::load_question($questiondata->id
));
120 $questiondata = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id
]);
121 $quba->add_question(question_bank
::load_question($questiondata->id
));
123 // Set the user and answer the questions.
124 $testuser = $this->getDataGenerator()->create_user();
125 $this->setUser($testuser);
127 $quba->start_all_questions();
128 $quba->process_action(1, ['answer' => 1]);
129 $quba->process_action(2, ['answer' => 'cat']);
130 $quba->finish_all_questions();
132 question_engine
::save_questions_usage_by_activity($quba);
134 $context = $quba->get_owning_context();
136 // Export all questions for this attempt.
137 $options = new \
question_display_options();
138 provider
::export_question_usage($testuser->id
, $context, [], $quba->get_id(), $options, true);
139 $writer = writer
::with_context($context);
141 $this->assertTrue($writer->has_any_data_in_any_context());
142 $this->assertTrue($writer->has_any_data());
144 $slots = $quba->get_slots();
145 $this->assertCount(2, $slots);
147 foreach ($slots as $slotno) {
148 $data = $writer->get_data([get_string('questions', 'core_question'), $slotno]);
149 $this->assertNotNull($data);
150 $this->assert_question_slot_equals($quba, $slotno, $options, $data);
153 $this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()]));
155 // Disable some options and re-export.
157 $options = new \
question_display_options();
158 $options->hide_all_feedback();
159 $options->flags
= \question_display_options
::HIDDEN
;
160 $options->marks
= \question_display_options
::HIDDEN
;
162 provider
::export_question_usage($testuser->id
, $context, [], $quba->get_id(), $options, true);
163 $writer = writer
::with_context($context);
165 $this->assertTrue($writer->has_any_data_in_any_context());
166 $this->assertTrue($writer->has_any_data());
168 $slots = $quba->get_slots();
169 $this->assertCount(2, $slots);
171 foreach ($slots as $slotno) {
172 $data = $writer->get_data([get_string('questions', 'core_question'), $slotno]);
173 $this->assertNotNull($data);
174 $this->assert_question_slot_equals($quba, $slotno, $options, $data);
177 $this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()]));
181 * Test that questions owned by a user are exported and never deleted.
183 public function test_question_owned_is_handled() {
185 $this->resetAfterTest();
187 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
189 // Create the two test users.
190 $user = $this->getDataGenerator()->create_user();
191 $otheruser = $this->getDataGenerator()->create_user();
193 // Create one question as each user in diferent contexts.
194 $this->setUser($user);
195 $userdata = $questiongenerator->setup_course_and_questions();
196 $expectedcontext = \context_course
::instance($userdata[1]->id
);
198 $this->setUser($otheruser);
199 $otheruserdata = $questiongenerator->setup_course_and_questions();
200 $unexpectedcontext = \context_course
::instance($otheruserdata[1]->id
);
202 // And create another one where we'll update a question as the test user.
203 $moreotheruserdata = $questiongenerator->setup_course_and_questions();
204 $otherexpectedcontext = \context_course
::instance($moreotheruserdata[1]->id
);
205 $morequestions = $moreotheruserdata[3];
207 // Update the third set of questions.
208 $this->setUser($user);
210 foreach ($morequestions as $question) {
211 $questiongenerator->update_question($question);
214 // Run the get_contexts_for_userid as default user.
217 // There should be two contexts returned - the first course, and the third.
218 $contextlist = provider
::get_contexts_for_userid($user->id
);
219 $this->assertCount(2, $contextlist);
221 $expectedcontexts = [
222 $expectedcontext->id
,
223 $otherexpectedcontext->id
,
225 $this->assertEquals($expectedcontexts, $contextlist->get_contextids(), 'Contexts not equal', 0.0, 10, true);
227 // Run the export_user_Data as the test user.
228 $this->setUser($user);
230 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist
(
231 \core_user
::get_user($user->id
),
235 provider
::export_user_data($approvedcontextlist);
237 // There should be data for the user's question context.
238 $writer = writer
::with_context($expectedcontext);
239 $this->assertTrue($writer->has_any_data());
241 // And for the course we updated.
242 $otherwriter = writer
::with_context($otherexpectedcontext);
243 $this->assertTrue($otherwriter->has_any_data());
245 // But not for the other user's course.
246 $otherwriter = writer
::with_context($unexpectedcontext);
247 $this->assertFalse($otherwriter->has_any_data());
249 // The question data is exported as an XML export in custom files.
250 $writer = writer
::with_context($expectedcontext);
251 $subcontext = [get_string('questionbank', 'core_question')];
253 $exportfile = $writer->get_custom_file($subcontext, 'questions.xml');
254 $this->assertNotEmpty($exportfile);
256 $xmlized = xmlize($exportfile);
257 $xmlquestions = $xmlized['quiz']['#']['question'];
259 $this->assertCount(2, $xmlquestions);
261 // Run the delete functions as default user.
264 // The delete functions should do nothing here.
265 $this->assertCount(6, $DB->get_records('question'));
267 // Delete for all users in context.
268 provider
::delete_data_for_all_users_in_context($expectedcontext);
269 $this->assertCount(6, $DB->get_records('question'));
271 provider
::delete_data_for_user($approvedcontextlist);
272 $this->assertCount(6, $DB->get_records('question'));
276 * Deleting questions should only unset their created and modified user.
278 public function test_question_delete_data_for_user_anonymised() {
280 $this->resetAfterTest(true);
282 $user = \core_user
::get_user_by_username('admin');
283 $otheruser = $this->getDataGenerator()->create_user();
285 $course = $this->getDataGenerator()->create_course();
286 $context = \context_course
::instance($course->id
);
287 $othercourse = $this->getDataGenerator()->create_course();
288 $othercontext = \context_course
::instance($othercourse->id
);
290 // Create a couple of questions.
291 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
292 $cat = $questiongenerator->create_question_category([
293 'contextid' => $context->id
,
295 $othercat = $questiongenerator->create_question_category([
296 'contextid' => $othercontext->id
,
300 // Q1 - Created by the UUT, Modified by UUT.
301 // Q2 - Created by the UUT, Modified by the other user.
302 // Q3 - Created by the other user, Modified by UUT
303 // Q4 - Created by the other user, Modified by the other user.
304 // Q5 - Created by the UUT, Modified by the UUT, but in a different context.
305 $this->setUser($user);
306 $q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
307 $q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
309 $this->setUser($otheruser);
310 $questiongenerator->update_question($q2);
311 $q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
312 $q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
314 $this->setUser($user);
315 $questiongenerator->update_question($q3);
316 $q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id
));
318 $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist
(
324 // Delete the data and check it is removed.
326 provider
::delete_data_for_user($approvedcontextlist);
328 $this->assertCount(5, $DB->get_records('question'));
330 $qrecord = $DB->get_record('question', ['id' => $q1->id
]);
331 $this->assertEquals(0, $qrecord->createdby
);
332 $this->assertEquals(0, $qrecord->modifiedby
);
334 $qrecord = $DB->get_record('question', ['id' => $q2->id
]);
335 $this->assertEquals(0, $qrecord->createdby
);
336 $this->assertEquals($otheruser->id
, $qrecord->modifiedby
);
338 $qrecord = $DB->get_record('question', ['id' => $q3->id
]);
339 $this->assertEquals($otheruser->id
, $qrecord->createdby
);
340 $this->assertEquals(0, $qrecord->modifiedby
);
342 $qrecord = $DB->get_record('question', ['id' => $q4->id
]);
343 $this->assertEquals($otheruser->id
, $qrecord->createdby
);
344 $this->assertEquals($otheruser->id
, $qrecord->modifiedby
);
346 $qrecord = $DB->get_record('question', ['id' => $q5->id
]);
347 $this->assertEquals($user->id
, $qrecord->createdby
);
348 $this->assertEquals($user->id
, $qrecord->modifiedby
);
352 * Deleting questions should only unset their created and modified user for all questions in a context.
354 public function test_question_delete_data_for_all_users_in_context_anonymised() {
356 $this->resetAfterTest(true);
358 $user = \core_user
::get_user_by_username('admin');
359 $otheruser = $this->getDataGenerator()->create_user();
361 $course = $this->getDataGenerator()->create_course();
362 $context = \context_course
::instance($course->id
);
363 $othercourse = $this->getDataGenerator()->create_course();
364 $othercontext = \context_course
::instance($othercourse->id
);
366 // Create a couple of questions.
367 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
368 $cat = $questiongenerator->create_question_category([
369 'contextid' => $context->id
,
371 $othercat = $questiongenerator->create_question_category([
372 'contextid' => $othercontext->id
,
376 // Q1 - Created by the UUT, Modified by UUT.
377 // Q2 - Created by the UUT, Modified by the other user.
378 // Q3 - Created by the other user, Modified by UUT
379 // Q4 - Created by the other user, Modified by the other user.
380 // Q5 - Created by the UUT, Modified by the UUT, but in a different context.
381 $this->setUser($user);
382 $q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
383 $q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
385 $this->setUser($otheruser);
386 $questiongenerator->update_question($q2);
387 $q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
388 $q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id
));
390 $this->setUser($user);
391 $questiongenerator->update_question($q3);
392 $q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id
));
394 // Delete the data and check it is removed.
396 provider
::delete_data_for_all_users_in_context($context);
398 $this->assertCount(5, $DB->get_records('question'));
400 $qrecord = $DB->get_record('question', ['id' => $q1->id
]);
401 $this->assertEquals(0, $qrecord->createdby
);
402 $this->assertEquals(0, $qrecord->modifiedby
);
404 $qrecord = $DB->get_record('question', ['id' => $q2->id
]);
405 $this->assertEquals(0, $qrecord->createdby
);
406 $this->assertEquals(0, $qrecord->modifiedby
);
408 $qrecord = $DB->get_record('question', ['id' => $q3->id
]);
409 $this->assertEquals(0, $qrecord->createdby
);
410 $this->assertEquals(0, $qrecord->modifiedby
);
412 $qrecord = $DB->get_record('question', ['id' => $q4->id
]);
413 $this->assertEquals(0, $qrecord->createdby
);
414 $this->assertEquals(0, $qrecord->modifiedby
);
416 $qrecord = $DB->get_record('question', ['id' => $q5->id
]);
417 $this->assertEquals($user->id
, $qrecord->createdby
);
418 $this->assertEquals($user->id
, $qrecord->modifiedby
);