MDL-62781 question/privacy: fix tests with CodeRunner is installed
[moodle.git] / question / 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    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
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 core_question\privacy\provider;
30 defined('MOODLE_INTERNAL') || die();
32 global $CFG;
33 require_once($CFG->libdir . '/xmlize.php');
34 require_once(__DIR__ . '/privacy_helper.php');
35 require_once(__DIR__ . '/../engine/tests/helpers.php');
37 /**
38  * Privacy provider tests class.
39  *
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
43  */
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;
49     /**
50      * Prepare a question attempt.
51      *
52      * @return  question_usage_by_activity
53      */
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);
67         return $quba;
68     }
70     /**
71      * Test that calling export_question_usage on a usage belonging to a
72      * different user does not export any data.
73      */
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);
91         // Set the user.
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());
101     }
103     /**
104      * Test that calling export_question_usage on a usage belonging to a
105      * different user but ignoring the user match
106      */
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);
151         }
153         $this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()]));
155         // Disable some options and re-export.
156         writer::reset();
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);
175         }
177         $this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()]));
178     }
180     /**
181      * Test that questions owned by a user are exported and never deleted.
182      */
183     public function test_question_owned_is_handled() {
184         global $DB;
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);
212         }
214         // Run the get_contexts_for_userid as default user.
215         $this->setUser();
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,
224             ];
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),
232             'core_question',
233             $expectedcontexts
234         );
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.
262         $this->setUser();
264         // Find out how many questions are in the question bank to start with.
265         $questioncount = $DB->count_records('question');
267         // The delete functions should do nothing here.
268         $this->assertEquals($questioncount, $DB->count_records('question'));
270         // Delete for all users in context.
271         provider::delete_data_for_all_users_in_context($expectedcontext);
272         $this->assertEquals($questioncount, $DB->count_records('question'));
274         provider::delete_data_for_user($approvedcontextlist);
275         $this->assertEquals($questioncount, $DB->count_records('question'));
276     }
278     /**
279      * Deleting questions should only unset their created and modified user.
280      */
281     public function test_question_delete_data_for_user_anonymised() {
282         global $DB;
283         $this->resetAfterTest(true);
285         $user = \core_user::get_user_by_username('admin');
286         $otheruser = $this->getDataGenerator()->create_user();
288         $course = $this->getDataGenerator()->create_course();
289         $context = \context_course::instance($course->id);
290         $othercourse = $this->getDataGenerator()->create_course();
291         $othercontext = \context_course::instance($othercourse->id);
293         // Create a couple of questions.
294         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
295         $cat = $questiongenerator->create_question_category([
296             'contextid' => $context->id,
297         ]);
298         $othercat = $questiongenerator->create_question_category([
299             'contextid' => $othercontext->id,
300         ]);
302         // Create questions:
303         // Q1 - Created by the UUT, Modified by UUT.
304         // Q2 - Created by the UUT, Modified by the other user.
305         // Q3 - Created by the other user, Modified by UUT
306         // Q4 - Created by the other user, Modified by the other user.
307         // Q5 - Created by the UUT, Modified by the UUT, but in a different context.
308         $this->setUser($user);
309         $q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
310         $q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
312         $this->setUser($otheruser);
313         $questiongenerator->update_question($q2);
314         $q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
315         $q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
317         $this->setUser($user);
318         $questiongenerator->update_question($q3);
319         $q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id));
321         $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
322             $user,
323             'core_question',
324             [$context->id]
325         );
327         // Find out how many questions are in the question bank to start with.
328         $questioncount = $DB->count_records('question');
330         // Delete the data and check it is removed.
331         $this->setUser();
332         provider::delete_data_for_user($approvedcontextlist);
334         $this->assertEquals($questioncount, $DB->count_records('question'));
336         $qrecord = $DB->get_record('question', ['id' => $q1->id]);
337         $this->assertEquals(0, $qrecord->createdby);
338         $this->assertEquals(0, $qrecord->modifiedby);
340         $qrecord = $DB->get_record('question', ['id' => $q2->id]);
341         $this->assertEquals(0, $qrecord->createdby);
342         $this->assertEquals($otheruser->id, $qrecord->modifiedby);
344         $qrecord = $DB->get_record('question', ['id' => $q3->id]);
345         $this->assertEquals($otheruser->id, $qrecord->createdby);
346         $this->assertEquals(0, $qrecord->modifiedby);
348         $qrecord = $DB->get_record('question', ['id' => $q4->id]);
349         $this->assertEquals($otheruser->id, $qrecord->createdby);
350         $this->assertEquals($otheruser->id, $qrecord->modifiedby);
352         $qrecord = $DB->get_record('question', ['id' => $q5->id]);
353         $this->assertEquals($user->id, $qrecord->createdby);
354         $this->assertEquals($user->id, $qrecord->modifiedby);
355     }
357     /**
358      * Deleting questions should only unset their created and modified user for all questions in a context.
359      */
360     public function test_question_delete_data_for_all_users_in_context_anonymised() {
361         global $DB;
362         $this->resetAfterTest(true);
364         $user = \core_user::get_user_by_username('admin');
365         $otheruser = $this->getDataGenerator()->create_user();
367         $course = $this->getDataGenerator()->create_course();
368         $context = \context_course::instance($course->id);
369         $othercourse = $this->getDataGenerator()->create_course();
370         $othercontext = \context_course::instance($othercourse->id);
372         // Create a couple of questions.
373         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
374         $cat = $questiongenerator->create_question_category([
375             'contextid' => $context->id,
376         ]);
377         $othercat = $questiongenerator->create_question_category([
378             'contextid' => $othercontext->id,
379         ]);
381         // Create questions:
382         // Q1 - Created by the UUT, Modified by UUT.
383         // Q2 - Created by the UUT, Modified by the other user.
384         // Q3 - Created by the other user, Modified by UUT
385         // Q4 - Created by the other user, Modified by the other user.
386         // Q5 - Created by the UUT, Modified by the UUT, but in a different context.
387         $this->setUser($user);
388         $q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
389         $q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
391         $this->setUser($otheruser);
392         $questiongenerator->update_question($q2);
393         $q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
394         $q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
396         $this->setUser($user);
397         $questiongenerator->update_question($q3);
398         $q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id));
400         // Find out how many questions are in the question bank to start with.
401         $questioncount = $DB->count_records('question');
403         // Delete the data and check it is removed.
404         $this->setUser();
405         provider::delete_data_for_all_users_in_context($context);
407         $this->assertEquals($questioncount, $DB->count_records('question'));
409         $qrecord = $DB->get_record('question', ['id' => $q1->id]);
410         $this->assertEquals(0, $qrecord->createdby);
411         $this->assertEquals(0, $qrecord->modifiedby);
413         $qrecord = $DB->get_record('question', ['id' => $q2->id]);
414         $this->assertEquals(0, $qrecord->createdby);
415         $this->assertEquals(0, $qrecord->modifiedby);
417         $qrecord = $DB->get_record('question', ['id' => $q3->id]);
418         $this->assertEquals(0, $qrecord->createdby);
419         $this->assertEquals(0, $qrecord->modifiedby);
421         $qrecord = $DB->get_record('question', ['id' => $q4->id]);
422         $this->assertEquals(0, $qrecord->createdby);
423         $this->assertEquals(0, $qrecord->modifiedby);
425         $qrecord = $DB->get_record('question', ['id' => $q5->id]);
426         $this->assertEquals($user->id, $qrecord->createdby);
427         $this->assertEquals($user->id, $qrecord->modifiedby);
428     }