Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * 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
 */
24
namespace mod_quiz\privacy;
25
 
26
use core_privacy\local\metadata\collection;
27
use core_privacy\local\request\deletion_criteria;
28
use core_privacy\local\request\writer;
29
use mod_quiz\privacy\provider;
30
use mod_quiz\privacy\helper;
31
use mod_quiz\quiz_attempt;
32
 
33
defined('MOODLE_INTERNAL') || die();
34
 
35
global $CFG;
36
require_once($CFG->dirroot . '/question/tests/privacy_helper.php');
37
 
38
/**
39
 * Privacy provider tests class.
40
 *
41
 * @package    mod_quiz
42
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
43
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44
 * @covers \mod_quiz\privacy\provider
45
 */
46
class provider_test extends \core_privacy\tests\provider_testcase {
47
 
48
    use \core_question_privacy_helper;
49
 
50
    /**
51
     * Test that a user who has no data gets no contexts
52
     */
53
    public function test_get_contexts_for_userid_no_data() {
54
        global $USER;
55
        $this->resetAfterTest();
56
        $this->setAdminUser();
57
 
58
        $contextlist = provider::get_contexts_for_userid($USER->id);
59
        $this->assertEmpty($contextlist);
60
    }
61
 
62
    /**
63
     * Test for provider::get_contexts_for_userid() when there is no quiz attempt at all.
64
     */
65
    public function test_get_contexts_for_userid_no_attempt_with_override() {
66
        global $DB;
67
        $this->resetAfterTest(true);
68
 
69
        $course = $this->getDataGenerator()->create_course();
70
        $user = $this->getDataGenerator()->create_user();
71
 
72
        // Make a quiz with an override.
73
        $this->setUser();
74
        $quiz = $this->create_test_quiz($course);
75
        $DB->insert_record('quiz_overrides', [
76
            'quiz' => $quiz->id,
77
            'userid' => $user->id,
78
            'timeclose' => 1300,
79
            'timelimit' => null,
80
        ]);
81
 
82
        $cm = get_coursemodule_from_instance('quiz', $quiz->id);
83
        $context = \context_module::instance($cm->id);
84
 
85
        // Fetch the contexts - only one context should be returned.
86
        $this->setUser();
87
        $contextlist = provider::get_contexts_for_userid($user->id);
88
        $this->assertCount(1, $contextlist);
89
        $this->assertEquals($context, $contextlist->current());
90
    }
91
 
92
    /**
93
     * The export function should handle an empty contextlist properly.
94
     */
95
    public function test_export_user_data_no_data() {
96
        global $USER;
97
        $this->resetAfterTest();
98
        $this->setAdminUser();
99
 
100
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
101
            \core_user::get_user($USER->id),
102
            'mod_quiz',
103
            []
104
        );
105
 
106
        provider::export_user_data($approvedcontextlist);
107
        $this->assertDebuggingNotCalled();
108
 
109
        // No data should have been exported.
110
        $writer = \core_privacy\local\request\writer::with_context(\context_system::instance());
111
        $this->assertFalse($writer->has_any_data_in_any_context());
112
    }
113
 
114
    /**
115
     * The delete function should handle an empty contextlist properly.
116
     */
117
    public function test_delete_data_for_user_no_data() {
118
        global $USER;
119
        $this->resetAfterTest();
120
        $this->setAdminUser();
121
 
122
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
123
            \core_user::get_user($USER->id),
124
            'mod_quiz',
125
            []
126
        );
127
 
128
        provider::delete_data_for_user($approvedcontextlist);
129
        $this->assertDebuggingNotCalled();
130
    }
131
 
132
    /**
133
     * Export + Delete quiz data for a user who has made a single attempt.
134
     */
135
    public function test_user_with_data() {
136
        global $DB;
137
        $this->resetAfterTest(true);
138
 
139
        $course = $this->getDataGenerator()->create_course();
140
        $user = $this->getDataGenerator()->create_user();
141
        $otheruser = $this->getDataGenerator()->create_user();
142
 
143
        // Make a quiz with an override.
144
        $this->setUser();
145
        $quiz = $this->create_test_quiz($course);
146
        $DB->insert_record('quiz_overrides', [
147
                'quiz' => $quiz->id,
148
                'userid' => $user->id,
149
                'timeclose' => 1300,
150
                'timelimit' => null,
151
            ]);
152
 
153
        // Run as the user and make an attempt on the quiz.
154
        list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $user);
155
        $this->attempt_quiz($quiz, $otheruser);
156
        $context = $quizobj->get_context();
157
 
158
        // Fetch the contexts - only one context should be returned.
159
        $this->setUser();
160
        $contextlist = provider::get_contexts_for_userid($user->id);
161
        $this->assertCount(1, $contextlist);
162
        $this->assertEquals($context, $contextlist->current());
163
 
164
        // Perform the export and check the data.
165
        $this->setUser($user);
166
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
167
            \core_user::get_user($user->id),
168
            'mod_quiz',
169
            $contextlist->get_contextids()
170
        );
171
        provider::export_user_data($approvedcontextlist);
172
 
173
        // Ensure that the quiz data was exported correctly.
174
        /** @var \core_privacy\tests\request\content_writer $writer */
175
        $writer = writer::with_context($context);
176
        $this->assertTrue($writer->has_any_data());
177
 
178
        $quizdata = $writer->get_data([]);
179
        $this->assertEquals($quizobj->get_quiz_name(), $quizdata->name);
180
 
181
        // Every module has an intro.
182
        $this->assertTrue(isset($quizdata->intro));
183
 
184
        // Fetch the attempt data.
185
        $attempt = $attemptobj->get_attempt();
186
        $attemptsubcontext = [
187
            get_string('attempts', 'mod_quiz'),
188
            $attempt->attempt,
189
        ];
190
        $attemptdata = writer::with_context($context)->get_data($attemptsubcontext);
191
 
192
        $attempt = $attemptobj->get_attempt();
193
        $this->assertTrue(isset($attemptdata->state));
194
        $this->assertEquals(quiz_attempt::state_name($attemptobj->get_state()), $attemptdata->state);
195
        $this->assertTrue(isset($attemptdata->timestart));
196
        $this->assertTrue(isset($attemptdata->timefinish));
197
        $this->assertTrue(isset($attemptdata->timemodified));
198
        $this->assertFalse(isset($attemptdata->timemodifiedoffline));
199
        $this->assertFalse(isset($attemptdata->timecheckstate));
200
 
201
        $this->assertTrue(isset($attemptdata->grade));
202
        $this->assertEquals(100.00, $attemptdata->grade->grade);
203
 
204
        // Check that the exported question attempts are correct.
205
        $attemptsubcontext = helper::get_quiz_attempt_subcontext($attemptobj->get_attempt(), $user);
206
        $this->assert_question_attempt_exported(
207
            $context,
208
            $attemptsubcontext,
209
            \question_engine::load_questions_usage_by_activity($attemptobj->get_uniqueid()),
210
            quiz_get_review_options($quiz, $attemptobj->get_attempt(), $context),
211
            $user
212
        );
213
 
214
        // Delete the data and check it is removed.
215
        $this->setUser();
216
        provider::delete_data_for_user($approvedcontextlist);
217
        $this->expectException(\dml_missing_record_exception::class);
218
        quiz_attempt::create($attemptobj->get_quizid());
219
    }
220
 
221
    /**
222
     * Export + Delete quiz data for a user who has made a single attempt.
223
     */
224
    public function test_user_with_preview() {
225
        global $DB;
226
        $this->resetAfterTest(true);
227
 
228
        // Make a quiz.
229
        $course = $this->getDataGenerator()->create_course();
230
        $user = $this->getDataGenerator()->create_user();
231
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
232
 
233
        $quiz = $quizgenerator->create_instance([
234
                'course' => $course->id,
235
                'questionsperpage' => 0,
236
                'grade' => 100.0,
237
                'sumgrades' => 2,
238
            ]);
239
 
240
        // Create a couple of questions.
241
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
242
        $cat = $questiongenerator->create_question_category();
243
 
244
        $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
245
        quiz_add_quiz_question($saq->id, $quiz);
246
        $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
247
        quiz_add_quiz_question($numq->id, $quiz);
248
 
249
        // Run as the user and make an attempt on the quiz.
250
        $this->setUser($user);
251
        $starttime = time();
252
        $quizobj = \mod_quiz\quiz_settings::create($quiz->id, $user->id);
253
        $context = $quizobj->get_context();
254
 
255
        $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
256
        $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
257
 
258
        // Start the attempt.
259
        $attempt = quiz_create_attempt($quizobj, 1, false, $starttime, true, $user->id);
260
        quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
261
        quiz_attempt_save_started($quizobj, $quba, $attempt);
262
 
263
        // Answer the questions.
264
        $attemptobj = quiz_attempt::create($attempt->id);
265
 
266
        $tosubmit = [
267
            1 => ['answer' => 'frog'],
268
            2 => ['answer' => '3.14'],
269
        ];
270
 
271
        $attemptobj->process_submitted_actions($starttime, false, $tosubmit);
272
 
273
        // Finish the attempt.
274
        $attemptobj = quiz_attempt::create($attempt->id);
275
        $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
276
        $attemptobj->process_finish($starttime, false);
277
 
278
        // Fetch the contexts - no context should be returned.
279
        $this->setUser();
280
        $contextlist = provider::get_contexts_for_userid($user->id);
281
        $this->assertCount(0, $contextlist);
282
    }
283
 
284
    /**
285
     * Export + Delete quiz data for a user who has made a single attempt.
286
     */
287
    public function test_delete_data_for_all_users_in_context() {
288
        global $DB;
289
        $this->resetAfterTest(true);
290
 
291
        $course = $this->getDataGenerator()->create_course();
292
        $user = $this->getDataGenerator()->create_user();
293
        $otheruser = $this->getDataGenerator()->create_user();
294
 
295
        // Make a quiz with an override.
296
        $this->setUser();
297
        $quiz = $this->create_test_quiz($course);
298
        $DB->insert_record('quiz_overrides', [
299
                'quiz' => $quiz->id,
300
                'userid' => $user->id,
301
                'timeclose' => 1300,
302
                'timelimit' => null,
303
            ]);
304
 
305
        // Run as the user and make an attempt on the quiz.
306
        list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $user);
307
        list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $otheruser);
308
 
309
        // Create another quiz and questions, and repeat the data insertion.
310
        $this->setUser();
311
        $otherquiz = $this->create_test_quiz($course);
312
        $DB->insert_record('quiz_overrides', [
313
                'quiz' => $otherquiz->id,
314
                'userid' => $user->id,
315
                'timeclose' => 1300,
316
                'timelimit' => null,
317
            ]);
318
 
319
        // Run as the user and make an attempt on the quiz.
320
        list($otherquizobj, $otherquba, $otherattemptobj) = $this->attempt_quiz($otherquiz, $user);
321
        list($otherquizobj, $otherquba, $otherattemptobj) = $this->attempt_quiz($otherquiz, $otheruser);
322
 
323
        // Delete all data for all users in the context under test.
324
        $this->setUser();
325
        $context = $quizobj->get_context();
326
        provider::delete_data_for_all_users_in_context($context);
327
 
328
        // The quiz attempt should have been deleted from this quiz.
329
        $this->assertCount(0, $DB->get_records('quiz_attempts', ['quiz' => $quizobj->get_quizid()]));
330
        $this->assertCount(0, $DB->get_records('quiz_overrides', ['quiz' => $quizobj->get_quizid()]));
331
        $this->assertCount(0, $DB->get_records('question_attempts', ['questionusageid' => $quba->get_id()]));
332
 
333
        // But not for the other quiz.
334
        $this->assertNotCount(0, $DB->get_records('quiz_attempts', ['quiz' => $otherquizobj->get_quizid()]));
335
        $this->assertNotCount(0, $DB->get_records('quiz_overrides', ['quiz' => $otherquizobj->get_quizid()]));
336
        $this->assertNotCount(0, $DB->get_records('question_attempts', ['questionusageid' => $otherquba->get_id()]));
337
    }
338
 
339
    /**
340
     * Export + Delete quiz data for a user who has made a single attempt.
341
     */
342
    public function test_wrong_context() {
343
        global $DB;
344
        $this->resetAfterTest(true);
345
 
346
        $course = $this->getDataGenerator()->create_course();
347
        $user = $this->getDataGenerator()->create_user();
348
 
349
        // Make a choice.
350
        $this->setUser();
351
        $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice');
352
        $choice = $plugingenerator->create_instance(['course' => $course->id]);
353
        $cm = get_coursemodule_from_instance('choice', $choice->id);
354
        $context = \context_module::instance($cm->id);
355
 
356
        // Fetch the contexts - no context should be returned.
357
        $this->setUser();
358
        $contextlist = provider::get_contexts_for_userid($user->id);
359
        $this->assertCount(0, $contextlist);
360
 
361
        // Perform the export and check the data.
362
        $this->setUser($user);
363
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
364
            \core_user::get_user($user->id),
365
            'mod_quiz',
366
            [$context->id]
367
        );
368
        provider::export_user_data($approvedcontextlist);
369
 
370
        // Ensure that nothing was exported.
371
        /** @var \core_privacy\tests\request\content_writer $writer */
372
        $writer = writer::with_context($context);
373
        $this->assertFalse($writer->has_any_data_in_any_context());
374
 
375
        $this->setUser();
376
 
377
        $dbwrites = $DB->perf_get_writes();
378
 
379
        // Perform a deletion with the approved contextlist containing an incorrect context.
380
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
381
            \core_user::get_user($user->id),
382
            'mod_quiz',
383
            [$context->id]
384
        );
385
        provider::delete_data_for_user($approvedcontextlist);
386
        $this->assertEquals($dbwrites, $DB->perf_get_writes());
387
        $this->assertDebuggingNotCalled();
388
 
389
        // Perform a deletion of all data in the context.
390
        provider::delete_data_for_all_users_in_context($context);
391
        $this->assertEquals($dbwrites, $DB->perf_get_writes());
392
        $this->assertDebuggingNotCalled();
393
    }
394
 
395
    /**
396
     * Create a test quiz for the specified course.
397
     *
398
     * @param   \stdClass $course
399
     * @return  \stdClass
400
     */
401
    protected function create_test_quiz($course) {
402
        global $DB;
403
 
404
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
405
 
406
        $quiz = $quizgenerator->create_instance([
407
                'course' => $course->id,
408
                'questionsperpage' => 0,
409
                'grade' => 100.0,
410
                'sumgrades' => 2,
411
            ]);
412
 
413
        // Create a couple of questions.
414
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
415
        $cat = $questiongenerator->create_question_category();
416
 
417
        $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
418
        quiz_add_quiz_question($saq->id, $quiz);
419
        $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
420
        quiz_add_quiz_question($numq->id, $quiz);
421
 
422
        return $quiz;
423
    }
424
 
425
    /**
426
     * Answer questions for a quiz + user.
427
     *
428
     * @param   \stdClass   $quiz
429
     * @param   \stdClass   $user
430
     * @return  array
431
     */
432
    protected function attempt_quiz($quiz, $user) {
433
        $this->setUser($user);
434
 
435
        $starttime = time();
436
        $quizobj = \mod_quiz\quiz_settings::create($quiz->id, $user->id);
437
        $context = $quizobj->get_context();
438
 
439
        $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
440
        $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
441
 
442
        // Start the attempt.
443
        $attempt = quiz_create_attempt($quizobj, 1, false, $starttime, false, $user->id);
444
        quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
445
        quiz_attempt_save_started($quizobj, $quba, $attempt);
446
 
447
        // Answer the questions.
448
        $attemptobj = quiz_attempt::create($attempt->id);
449
 
450
        $tosubmit = [
451
            1 => ['answer' => 'frog'],
452
            2 => ['answer' => '3.14'],
453
        ];
454
 
455
        $attemptobj->process_submitted_actions($starttime, false, $tosubmit);
456
 
457
        // Finish the attempt.
458
        $attemptobj = quiz_attempt::create($attempt->id);
459
        $attemptobj->process_finish($starttime, false);
460
 
461
        $this->setUser();
462
 
463
        return [$quizobj, $quba, $attemptobj];
464
    }
465
 
466
    /**
467
     * Test for provider::get_users_in_context().
468
     */
469
    public function test_get_users_in_context() {
470
        global $DB;
471
        $this->resetAfterTest(true);
472
 
473
        $course = $this->getDataGenerator()->create_course();
474
        $user = $this->getDataGenerator()->create_user();
475
        $anotheruser = $this->getDataGenerator()->create_user();
476
        $extrauser = $this->getDataGenerator()->create_user();
477
 
478
        // Make a quiz.
479
        $this->setUser();
480
        $quiz = $this->create_test_quiz($course);
481
 
482
        // Create an override for user1.
483
        $DB->insert_record('quiz_overrides', [
484
            'quiz' => $quiz->id,
485
            'userid' => $user->id,
486
            'timeclose' => 1300,
487
            'timelimit' => null,
488
        ]);
489
 
490
        // Make an attempt on the quiz as user2.
491
        list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $anotheruser);
492
        $context = $quizobj->get_context();
493
 
494
        // Fetch users - user1 and user2 should be returned.
495
        $userlist = new \core_privacy\local\request\userlist($context, 'mod_quiz');
496
        provider::get_users_in_context($userlist);
497
        $this->assertEqualsCanonicalizing(
498
                [$user->id, $anotheruser->id],
499
                $userlist->get_userids());
500
    }
501
 
502
    /**
503
     * Test for provider::delete_data_for_users().
504
     */
505
    public function test_delete_data_for_users() {
506
        global $DB;
507
        $this->resetAfterTest(true);
508
 
509
        $user1 = $this->getDataGenerator()->create_user();
510
        $user2 = $this->getDataGenerator()->create_user();
511
        $user3 = $this->getDataGenerator()->create_user();
512
 
513
        $course1 = $this->getDataGenerator()->create_course();
514
        $course2 = $this->getDataGenerator()->create_course();
515
 
516
        // Make a quiz in each course.
517
        $quiz1 = $this->create_test_quiz($course1);
518
        $quiz2 = $this->create_test_quiz($course2);
519
 
520
        // Attempt quiz1 as user1 and user2.
521
        list($quiz1obj) = $this->attempt_quiz($quiz1, $user1);
522
        $this->attempt_quiz($quiz1, $user2);
523
 
524
        // Create an override in quiz1 for user3.
525
        $DB->insert_record('quiz_overrides', [
526
            'quiz' => $quiz1->id,
527
            'userid' => $user3->id,
528
            'timeclose' => 1300,
529
            'timelimit' => null,
530
        ]);
531
 
532
        // Attempt quiz2 as user1.
533
        $this->attempt_quiz($quiz2, $user1);
534
 
535
        // Delete the data for user1 and user3 in course1 and check it is removed.
536
        $quiz1context = $quiz1obj->get_context();
537
        $approveduserlist = new \core_privacy\local\request\approved_userlist($quiz1context, 'mod_quiz',
538
            [$user1->id, $user3->id]);
539
        provider::delete_data_for_users($approveduserlist);
540
 
541
        // Only the attempt of user2 should be remained in quiz1.
542
        $this->assertEquals(
543
                [$user2->id],
544
                $DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz1->id])
545
        );
546
 
547
        // The attempt that user1 made in quiz2 should be remained.
548
        $this->assertEquals(
549
                [$user1->id],
550
                $DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz2->id])
551
        );
552
 
553
        // The quiz override in quiz1 that we had for user3 should be deleted.
554
        $this->assertEquals(
555
                [],
556
                $DB->get_fieldset_select('quiz_overrides', 'userid', 'quiz = ?', [$quiz1->id])
557
        );
558
    }
559
}