Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | 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
 */
1441 ariadna 46
final class provider_test extends \core_privacy\tests\provider_testcase {
1 efrain 47
 
48
    use \core_question_privacy_helper;
49
 
50
    /**
51
     * Test that a user who has no data gets no contexts
52
     */
11 efrain 53
    public function test_get_contexts_for_userid_no_data(): void {
1 efrain 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
     */
11 efrain 65
    public function test_get_contexts_for_userid_no_attempt_with_override(): void {
1 efrain 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
     */
11 efrain 95
    public function test_export_user_data_no_data(): void {
1 efrain 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
     */
11 efrain 117
    public function test_delete_data_for_user_no_data(): void {
1 efrain 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
     */
11 efrain 135
    public function test_user_with_data(): void {
1 efrain 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
     */
11 efrain 224
    public function test_user_with_preview(): void {
1 efrain 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());
1441 ariadna 276
        $attemptobj->process_submit($starttime, false);
277
        $attemptobj->process_grade_submission($starttime);
1 efrain 278
 
279
        // Fetch the contexts - no context should be returned.
280
        $this->setUser();
281
        $contextlist = provider::get_contexts_for_userid($user->id);
282
        $this->assertCount(0, $contextlist);
283
    }
284
 
285
    /**
286
     * Export + Delete quiz data for a user who has made a single attempt.
287
     */
11 efrain 288
    public function test_delete_data_for_all_users_in_context(): void {
1 efrain 289
        global $DB;
290
        $this->resetAfterTest(true);
291
 
292
        $course = $this->getDataGenerator()->create_course();
293
        $user = $this->getDataGenerator()->create_user();
294
        $otheruser = $this->getDataGenerator()->create_user();
295
 
296
        // Make a quiz with an override.
297
        $this->setUser();
298
        $quiz = $this->create_test_quiz($course);
299
        $DB->insert_record('quiz_overrides', [
300
                'quiz' => $quiz->id,
301
                'userid' => $user->id,
302
                'timeclose' => 1300,
303
                'timelimit' => null,
304
            ]);
305
 
306
        // Run as the user and make an attempt on the quiz.
307
        list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $user);
308
        list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $otheruser);
309
 
310
        // Create another quiz and questions, and repeat the data insertion.
311
        $this->setUser();
312
        $otherquiz = $this->create_test_quiz($course);
313
        $DB->insert_record('quiz_overrides', [
314
                'quiz' => $otherquiz->id,
315
                'userid' => $user->id,
316
                'timeclose' => 1300,
317
                'timelimit' => null,
318
            ]);
319
 
320
        // Run as the user and make an attempt on the quiz.
321
        list($otherquizobj, $otherquba, $otherattemptobj) = $this->attempt_quiz($otherquiz, $user);
322
        list($otherquizobj, $otherquba, $otherattemptobj) = $this->attempt_quiz($otherquiz, $otheruser);
323
 
324
        // Delete all data for all users in the context under test.
325
        $this->setUser();
326
        $context = $quizobj->get_context();
327
        provider::delete_data_for_all_users_in_context($context);
328
 
329
        // The quiz attempt should have been deleted from this quiz.
330
        $this->assertCount(0, $DB->get_records('quiz_attempts', ['quiz' => $quizobj->get_quizid()]));
331
        $this->assertCount(0, $DB->get_records('quiz_overrides', ['quiz' => $quizobj->get_quizid()]));
332
        $this->assertCount(0, $DB->get_records('question_attempts', ['questionusageid' => $quba->get_id()]));
333
 
334
        // But not for the other quiz.
335
        $this->assertNotCount(0, $DB->get_records('quiz_attempts', ['quiz' => $otherquizobj->get_quizid()]));
336
        $this->assertNotCount(0, $DB->get_records('quiz_overrides', ['quiz' => $otherquizobj->get_quizid()]));
337
        $this->assertNotCount(0, $DB->get_records('question_attempts', ['questionusageid' => $otherquba->get_id()]));
338
    }
339
 
340
    /**
341
     * Export + Delete quiz data for a user who has made a single attempt.
342
     */
11 efrain 343
    public function test_wrong_context(): void {
1 efrain 344
        global $DB;
345
        $this->resetAfterTest(true);
346
 
347
        $course = $this->getDataGenerator()->create_course();
348
        $user = $this->getDataGenerator()->create_user();
349
 
350
        // Make a choice.
351
        $this->setUser();
352
        $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice');
353
        $choice = $plugingenerator->create_instance(['course' => $course->id]);
354
        $cm = get_coursemodule_from_instance('choice', $choice->id);
355
        $context = \context_module::instance($cm->id);
356
 
357
        // Fetch the contexts - no context should be returned.
358
        $this->setUser();
359
        $contextlist = provider::get_contexts_for_userid($user->id);
360
        $this->assertCount(0, $contextlist);
361
 
362
        // Perform the export and check the data.
363
        $this->setUser($user);
364
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
365
            \core_user::get_user($user->id),
366
            'mod_quiz',
367
            [$context->id]
368
        );
369
        provider::export_user_data($approvedcontextlist);
370
 
371
        // Ensure that nothing was exported.
372
        /** @var \core_privacy\tests\request\content_writer $writer */
373
        $writer = writer::with_context($context);
374
        $this->assertFalse($writer->has_any_data_in_any_context());
375
 
376
        $this->setUser();
377
 
378
        $dbwrites = $DB->perf_get_writes();
379
 
380
        // Perform a deletion with the approved contextlist containing an incorrect context.
381
        $approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
382
            \core_user::get_user($user->id),
383
            'mod_quiz',
384
            [$context->id]
385
        );
386
        provider::delete_data_for_user($approvedcontextlist);
387
        $this->assertEquals($dbwrites, $DB->perf_get_writes());
388
        $this->assertDebuggingNotCalled();
389
 
390
        // Perform a deletion of all data in the context.
391
        provider::delete_data_for_all_users_in_context($context);
392
        $this->assertEquals($dbwrites, $DB->perf_get_writes());
393
        $this->assertDebuggingNotCalled();
394
    }
395
 
396
    /**
397
     * Create a test quiz for the specified course.
398
     *
399
     * @param   \stdClass $course
400
     * @return  \stdClass
401
     */
402
    protected function create_test_quiz($course) {
403
        global $DB;
404
 
405
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
406
 
407
        $quiz = $quizgenerator->create_instance([
408
                'course' => $course->id,
409
                'questionsperpage' => 0,
410
                'grade' => 100.0,
411
                'sumgrades' => 2,
412
            ]);
413
 
414
        // Create a couple of questions.
415
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
416
        $cat = $questiongenerator->create_question_category();
417
 
418
        $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
419
        quiz_add_quiz_question($saq->id, $quiz);
420
        $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
421
        quiz_add_quiz_question($numq->id, $quiz);
422
 
423
        return $quiz;
424
    }
425
 
426
    /**
427
     * Answer questions for a quiz + user.
428
     *
429
     * @param   \stdClass   $quiz
430
     * @param   \stdClass   $user
431
     * @return  array
432
     */
433
    protected function attempt_quiz($quiz, $user) {
434
        $this->setUser($user);
435
 
436
        $starttime = time();
437
        $quizobj = \mod_quiz\quiz_settings::create($quiz->id, $user->id);
438
        $context = $quizobj->get_context();
439
 
440
        $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
441
        $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
442
 
443
        // Start the attempt.
444
        $attempt = quiz_create_attempt($quizobj, 1, false, $starttime, false, $user->id);
445
        quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
446
        quiz_attempt_save_started($quizobj, $quba, $attempt);
447
 
448
        // Answer the questions.
449
        $attemptobj = quiz_attempt::create($attempt->id);
450
 
451
        $tosubmit = [
452
            1 => ['answer' => 'frog'],
453
            2 => ['answer' => '3.14'],
454
        ];
455
 
456
        $attemptobj->process_submitted_actions($starttime, false, $tosubmit);
457
 
458
        // Finish the attempt.
459
        $attemptobj = quiz_attempt::create($attempt->id);
1441 ariadna 460
        $attemptobj->process_submit($starttime, false);
461
        $attemptobj->process_grade_submission($starttime);
1 efrain 462
 
463
        $this->setUser();
464
 
465
        return [$quizobj, $quba, $attemptobj];
466
    }
467
 
468
    /**
469
     * Test for provider::get_users_in_context().
470
     */
11 efrain 471
    public function test_get_users_in_context(): void {
1 efrain 472
        global $DB;
473
        $this->resetAfterTest(true);
474
 
475
        $course = $this->getDataGenerator()->create_course();
476
        $user = $this->getDataGenerator()->create_user();
477
        $anotheruser = $this->getDataGenerator()->create_user();
478
        $extrauser = $this->getDataGenerator()->create_user();
479
 
480
        // Make a quiz.
481
        $this->setUser();
482
        $quiz = $this->create_test_quiz($course);
483
 
484
        // Create an override for user1.
485
        $DB->insert_record('quiz_overrides', [
486
            'quiz' => $quiz->id,
487
            'userid' => $user->id,
488
            'timeclose' => 1300,
489
            'timelimit' => null,
490
        ]);
491
 
492
        // Make an attempt on the quiz as user2.
493
        list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $anotheruser);
494
        $context = $quizobj->get_context();
495
 
496
        // Fetch users - user1 and user2 should be returned.
497
        $userlist = new \core_privacy\local\request\userlist($context, 'mod_quiz');
498
        provider::get_users_in_context($userlist);
499
        $this->assertEqualsCanonicalizing(
500
                [$user->id, $anotheruser->id],
501
                $userlist->get_userids());
502
    }
503
 
504
    /**
505
     * Test for provider::delete_data_for_users().
506
     */
11 efrain 507
    public function test_delete_data_for_users(): void {
1 efrain 508
        global $DB;
509
        $this->resetAfterTest(true);
510
 
511
        $user1 = $this->getDataGenerator()->create_user();
512
        $user2 = $this->getDataGenerator()->create_user();
513
        $user3 = $this->getDataGenerator()->create_user();
514
 
515
        $course1 = $this->getDataGenerator()->create_course();
516
        $course2 = $this->getDataGenerator()->create_course();
517
 
518
        // Make a quiz in each course.
519
        $quiz1 = $this->create_test_quiz($course1);
520
        $quiz2 = $this->create_test_quiz($course2);
521
 
522
        // Attempt quiz1 as user1 and user2.
523
        list($quiz1obj) = $this->attempt_quiz($quiz1, $user1);
524
        $this->attempt_quiz($quiz1, $user2);
525
 
526
        // Create an override in quiz1 for user3.
527
        $DB->insert_record('quiz_overrides', [
528
            'quiz' => $quiz1->id,
529
            'userid' => $user3->id,
530
            'timeclose' => 1300,
531
            'timelimit' => null,
532
        ]);
533
 
534
        // Attempt quiz2 as user1.
535
        $this->attempt_quiz($quiz2, $user1);
536
 
537
        // Delete the data for user1 and user3 in course1 and check it is removed.
538
        $quiz1context = $quiz1obj->get_context();
539
        $approveduserlist = new \core_privacy\local\request\approved_userlist($quiz1context, 'mod_quiz',
540
            [$user1->id, $user3->id]);
541
        provider::delete_data_for_users($approveduserlist);
542
 
543
        // Only the attempt of user2 should be remained in quiz1.
544
        $this->assertEquals(
545
                [$user2->id],
546
                $DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz1->id])
547
        );
548
 
549
        // The attempt that user1 made in quiz2 should be remained.
550
        $this->assertEquals(
551
                [$user1->id],
552
                $DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz2->id])
553
        );
554
 
555
        // The quiz override in quiz1 that we had for user3 should be deleted.
556
        $this->assertEquals(
557
                [],
558
                $DB->get_fieldset_select('quiz_overrides', 'userid', 'quiz = ?', [$quiz1->id])
559
        );
560
    }
561
}