Proyectos de Subversion Moodle

Rev

Rev 1 | | 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
 * Unit tests for grade/lib.php.
19
 *
20
 * @package   core_grades
21
 * @category  test
22
 * @copyright 2016 Jun Pataleta
23
 * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
24
 */
25
namespace core_grades;
26
 
27
use assign;
28
use cm_info;
29
use grade_item;
30
use grade_plugin_return;
31
use grade_report_grader;
32
 
33
defined('MOODLE_INTERNAL') || die();
34
 
35
global $CFG;
36
require_once($CFG->dirroot . '/grade/lib.php');
37
 
38
/**
39
 * Unit tests for grade/lib.php.
40
 *
41
 * @package   core_grades
42
 * @category  test
43
 * @copyright 2016 Jun Pataleta <jun@moodle.com>
44
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45
 */
46
class lib_test extends \advanced_testcase {
47
 
48
    /**
49
     * Test can_output_item.
50
     */
11 efrain 51
    public function test_can_output_item(): void {
1 efrain 52
        $this->resetAfterTest();
53
 
54
        $generator = $this->getDataGenerator();
55
 
56
        // Course level grade category.
57
        $course = $generator->create_course();
58
        // Grade tree looks something like:
59
        // - Test course    (Rendered).
60
        $gradetree = \grade_category::fetch_course_tree($course->id);
61
        $this->assertTrue(\grade_tree::can_output_item($gradetree));
62
 
63
        // Add a grade category with default settings.
64
        $generator->create_grade_category(array('courseid' => $course->id));
65
        // Grade tree now looks something like:
66
        // - Test course n        (Rendered).
67
        // -- Grade category n    (Rendered).
68
        $gradetree = \grade_category::fetch_course_tree($course->id);
69
        $this->assertNotEmpty($gradetree['children']);
70
        foreach ($gradetree['children'] as $child) {
71
            $this->assertTrue(\grade_tree::can_output_item($child));
72
        }
73
 
74
        // Add a grade category with grade type = None.
75
        $nototalcategory = 'No total category';
76
        $nototalparams = [
77
            'courseid' => $course->id,
78
            'fullname' => $nototalcategory,
79
            'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN
80
        ];
81
        $nototal = $generator->create_grade_category($nototalparams);
82
        $catnototal = \grade_category::fetch(array('id' => $nototal->id));
83
        // Set the grade type of the grade item associated to the grade category.
84
        $catitemnototal = $catnototal->load_grade_item();
85
        $catitemnototal->gradetype = GRADE_TYPE_NONE;
86
        $catitemnototal->update();
87
 
88
        // Grade tree looks something like:
89
        // - Test course n        (Rendered).
90
        // -- Grade category n    (Rendered).
91
        // -- No total category   (Not rendered).
92
        $gradetree = \grade_category::fetch_course_tree($course->id);
93
        foreach ($gradetree['children'] as $child) {
94
            if ($child['object']->fullname == $nototalcategory) {
95
                $this->assertFalse(\grade_tree::can_output_item($child));
96
            } else {
97
                $this->assertTrue(\grade_tree::can_output_item($child));
98
            }
99
        }
100
 
101
        // Add another grade category with default settings under 'No total category'.
102
        $normalinnototalparams = [
103
            'courseid' => $course->id,
104
            'fullname' => 'Normal category in no total category',
105
            'parent' => $nototal->id
106
        ];
107
        $generator->create_grade_category($normalinnototalparams);
108
 
109
        // Grade tree looks something like:
110
        // - Test course n                           (Rendered).
111
        // -- Grade category n                       (Rendered).
112
        // -- No total category                      (Rendered).
113
        // --- Normal category in no total category  (Rendered).
114
        $gradetree = \grade_category::fetch_course_tree($course->id);
115
        foreach ($gradetree['children'] as $child) {
116
            // All children are now visible.
117
            $this->assertTrue(\grade_tree::can_output_item($child));
118
            if (!empty($child['children'])) {
119
                foreach ($child['children'] as $grandchild) {
120
                    $this->assertTrue(\grade_tree::can_output_item($grandchild));
121
                }
122
            }
123
        }
124
 
125
        // Add a grade category with grade type = None.
126
        $nototalcategory2 = 'No total category 2';
127
        $nototal2params = [
128
            'courseid' => $course->id,
129
            'fullname' => $nototalcategory2,
130
            'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN
131
        ];
132
        $nototal2 = $generator->create_grade_category($nototal2params);
133
        $catnototal2 = \grade_category::fetch(array('id' => $nototal2->id));
134
        // Set the grade type of the grade item associated to the grade category.
135
        $catitemnototal2 = $catnototal2->load_grade_item();
136
        $catitemnototal2->gradetype = GRADE_TYPE_NONE;
137
        $catitemnototal2->update();
138
 
139
        // Add a category with no total under 'No total category'.
140
        $nototalinnototalcategory = 'Category with no total in no total category';
141
        $nototalinnototalparams = [
142
            'courseid' => $course->id,
143
            'fullname' => $nototalinnototalcategory,
144
            'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN,
145
            'parent' => $nototal2->id
146
        ];
147
        $nototalinnototal = $generator->create_grade_category($nototalinnototalparams);
148
        $catnototalinnototal = \grade_category::fetch(array('id' => $nototalinnototal->id));
149
        // Set the grade type of the grade item associated to the grade category.
150
        $catitemnototalinnototal = $catnototalinnototal->load_grade_item();
151
        $catitemnototalinnototal->gradetype = GRADE_TYPE_NONE;
152
        $catitemnototalinnototal->update();
153
 
154
        // Grade tree looks something like:
155
        // - Test course n                                    (Rendered).
156
        // -- Grade category n                                (Rendered).
157
        // -- No total category                               (Rendered).
158
        // --- Normal category in no total category           (Rendered).
159
        // -- No total category 2                             (Not rendered).
160
        // --- Category with no total in no total category    (Not rendered).
161
        $gradetree = \grade_category::fetch_course_tree($course->id);
162
        foreach ($gradetree['children'] as $child) {
163
            if ($child['object']->fullname == $nototalcategory2) {
164
                $this->assertFalse(\grade_tree::can_output_item($child));
165
            } else {
166
                $this->assertTrue(\grade_tree::can_output_item($child));
167
            }
168
            if (!empty($child['children'])) {
169
                foreach ($child['children'] as $grandchild) {
170
                    if ($grandchild['object']->fullname == $nototalinnototalcategory) {
171
                        $this->assertFalse(\grade_tree::can_output_item($grandchild));
172
                    } else {
173
                        $this->assertTrue(\grade_tree::can_output_item($grandchild));
174
                    }
175
                }
176
            }
177
        }
178
    }
179
 
180
    /**
181
     * Tests that ungraded_counts calculates count and sum of grades correctly when there are graded users.
182
     *
183
     * @covers \grade_report::ungraded_counts
184
     */
11 efrain 185
    public function test_ungraded_counts_count_sumgrades(): void {
1 efrain 186
        global $DB;
187
 
188
        $this->resetAfterTest(true);
189
 
190
        $course1 = $this->getDataGenerator()->create_course();
191
        $course2 = $this->getDataGenerator()->create_course();
192
 
193
        $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
194
        $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher'], '*', MUST_EXIST);
195
 
196
        // Custom roles (gradable and non gradable).
197
        $gradeblerole = create_role('New student role', 'gradable',
198
            'Gradable role', 'student');
199
        $nongradeblerole = create_role('New student role', 'nongradable',
200
            'Non gradable role', 'student');
201
 
202
        // Set up gradable roles.
203
        set_config('gradebookroles', $studentrole->id . ',' . $gradeblerole);
204
 
205
        // Create users.
206
 
207
        // These will be gradable users.
208
        $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
209
        $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
210
        $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
211
        $student5 = $this->getDataGenerator()->create_user(['username' => 'student5']);
212
 
213
        // These will be non-gradable users.
214
        $student4 = $this->getDataGenerator()->create_user(['username' => 'student4']);
215
        $student6 = $this->getDataGenerator()->create_user(['username' => 'student6']);
216
        $teacher = $this->getDataGenerator()->create_user(['username' => 'teacher']);
217
 
218
        // Enrol students.
219
        $this->getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id);
220
        $this->getDataGenerator()->enrol_user($student2->id, $course1->id, $studentrole->id);
221
        $this->getDataGenerator()->enrol_user($student3->id, $course1->id, $gradeblerole);
222
 
223
        $this->getDataGenerator()->enrol_user($student5->id, $course1->id, $nongradeblerole);
224
        $this->getDataGenerator()->enrol_user($student6->id, $course1->id, $studentrole->id);
225
        $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id);
226
 
227
        // User that is enrolled in a different course.
228
        $this->getDataGenerator()->enrol_user($student4->id, $course2->id, $studentrole->id);
229
 
230
        // Mark user as deleted.
231
        $student6->deleted = 1;
232
        $DB->update_record('user', $student6);
233
 
234
        // Create grade items in course 1.
235
        $assign1 = $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
236
        $assign2 = $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
237
        $quiz1 = $this->getDataGenerator()->create_module('quiz', ['course' => $course1->id]);
238
 
239
        $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
240
            'itemname'        => 'Grade item1',
241
            'idnumber'        => 'git1',
242
            'courseid'        => $course1->id,
243
        ]));
244
 
245
        // Create grade items in course 2.
246
        $assign3 = $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
247
 
248
        // Grade users in first course.
249
        $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign1->id));
250
        $assigninstance = new assign($cm->context, $cm, $course1);
251
        $grade = $assigninstance->get_user_grade($student1->id, true);
252
        $grade->grade = 40;
253
        $assigninstance->update_grade($grade);
254
 
255
        $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign2->id));
256
        $assigninstance = new assign($cm->context, $cm, $course1);
257
        $grade = $assigninstance->get_user_grade($student3->id, true);
258
        $grade->grade = 50;
259
        $assigninstance->update_grade($grade);
260
 
261
        // Override grade for assignment in gradebook.
262
        $gi = \grade_item::fetch([
263
            'itemtype' => 'mod',
264
            'itemmodule' => 'assign',
265
            'iteminstance' => $cm->instance,
266
            'courseid' => $course1->id
267
        ]);
268
        $gi->update_final_grade($student3->id, 55);
269
 
270
        // Grade user in second course.
271
        $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign3->id));
272
        $assigninstance = new assign($cm->context, $cm, $course2);
273
        $grade = $assigninstance->get_user_grade($student4->id, true);
274
        $grade->grade = 40;
275
        $assigninstance->update_grade($grade);
276
 
277
        $manuaitem->update_final_grade($student1->id, 1);
278
        $manuaitem->update_final_grade($student3->id, 2);
279
 
280
        // Trigger a regrade.
281
        grade_force_full_regrading($course1->id);
282
        grade_force_full_regrading($course2->id);
283
        grade_regrade_final_grades($course1->id);
284
        grade_regrade_final_grades($course2->id);
285
 
286
        // Initialise reports.
287
        $context1 = \context_course::instance($course1->id);
288
        $context2 = \context_course::instance($course2->id);
289
 
290
        $gpr1 = new grade_plugin_return(
291
            [
292
                'type' => 'report',
293
                'plugin' => 'grader',
294
                'course' => $course1,
295
            ]
296
        );
297
 
298
        $gpr2 = new grade_plugin_return(
299
            [
300
                'type' => 'report',
301
                'plugin' => 'grader',
302
                'course' => $course2,
303
            ]
304
        );
305
 
306
        $report1 = new grade_report_grader($course1->id, $gpr1, $context1);
307
        $report2 = new grade_report_grader($course2->id, $gpr2, $context2);
308
 
309
        $ungradedcounts = [];
310
        $ungradedcounts[$course1->id] = $report1->ungraded_counts(false);
311
        $ungradedcounts[$course2->id] = $report2->ungraded_counts(false);
312
 
313
        foreach ($ungradedcounts as $key => $ungradedcount) {
314
            $gradeitems = grade_item::fetch_all(['courseid' => $key]);
315
            if ($key == $course1->id) {
316
                $gradeitemkeys = array_keys($gradeitems);
317
                $ungradedcountskeys = array_keys($ungradedcount['ungradedcounts']);
318
 
319
                // For each grade item there is some student that is not graded yet in course 1.
320
                $this->assertEmpty(array_diff_key($gradeitemkeys, $ungradedcountskeys));
321
 
322
                // Only quiz does not have any grades, the remaning 4 grade items should have some.
323
                // We can do more and match by gradeitem id numbers. But feels like overengeneering.
324
                $this->assertEquals(4, count($ungradedcount['sumarray']));
325
            } else {
326
 
327
                // In course 2 there is one student, and he is graded.
328
                $this->assertEmpty($ungradedcount['ungradedcounts']);
329
 
330
                // There are 2 grade items and they both have some grades.
331
                $this->assertEquals(2, count($ungradedcount['sumarray']));
332
            }
333
 
334
            foreach ($gradeitems as $gradeitem) {
335
                $sumgrades = null;
336
                if (array_key_exists($gradeitem->id, $ungradedcount['ungradedcounts'])) {
337
                    $ungradeditem = $ungradedcount['ungradedcounts'][$gradeitem->id];
338
                    if ($gradeitem->itemtype === 'course') {
339
                        $this->assertEquals(1, $ungradeditem->count);
340
                    } else if ($gradeitem->itemmodule === 'assign') {
341
                        $this->assertEquals(2, $ungradeditem->count);
342
                    } else if ($gradeitem->itemmodule === 'quiz') {
343
                        $this->assertEquals(3, $ungradeditem->count);
344
                    } else if ($gradeitem->itemtype === 'manual') {
345
                        $this->assertEquals(1, $ungradeditem->count);
346
                    }
347
                }
348
 
349
                if (array_key_exists($gradeitem->id, $ungradedcount['sumarray'])) {
350
                    $sumgrades = $ungradedcount['sumarray'][$gradeitem->id];
351
                    if ($gradeitem->itemtype === 'course') {
352
                        if ($key == $course1->id) {
353
                            $this->assertEquals('98.00000', $sumgrades); // 40 + 55 + 1 + 2
354
                        } else {
355
                            $this->assertEquals('40.00000', $sumgrades);
356
                        }
357
                    } else if ($gradeitem->itemmodule === 'assign') {
358
                        if (($gradeitem->itemname === $assign1->name) || ($gradeitem->itemname === $assign3->name)) {
359
                            $this->assertEquals('40.00000', $sumgrades);
360
                        } else {
361
                            $this->assertEquals('55.00000', $sumgrades);
362
                        }
363
                    } else if ($gradeitem->itemtype === 'manual') {
364
                        $this->assertEquals('3.00000', $sumgrades);
365
                    }
366
                }
367
            }
368
        }
369
    }
370
 
371
    /**
372
     * Tests that ungraded_counts calculates count and sum of grades correctly when there are hidden grades.
373
     * @dataProvider ungraded_counts_hidden_grades_data()
374
     * @param bool $hidden Whether to inlcude hidden grades or not.
375
     * @param array $expectedcount expected count value (i.e. number of ugraded grades)
376
     * @param array $expectedsumarray expceted sum of grades
377
     *
378
     * @covers \grade_report::ungraded_counts
379
     */
11 efrain 380
    public function test_ungraded_counts_hidden_grades(bool $hidden, array $expectedcount, array $expectedsumarray): void {
1 efrain 381
        $this->resetAfterTest();
382
 
383
        $course = $this->getDataGenerator()->create_course();
384
 
385
        // Create users.
386
        $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
387
        $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
388
        $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
389
 
390
        // Enrol students.
391
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
392
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
393
        $this->getDataGenerator()->enrol_user($student3->id, $course->id, 'student');
394
 
395
        // Create grade items in course.
396
        $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
397
            'itemname' => 'Grade item1',
398
            'idnumber' => 'git1',
399
            'courseid' => $course->id,
400
        ]));
401
 
402
        // Grade users.
403
        $manuaitem->update_final_grade($student1->id, 1);
404
        $manuaitem->update_final_grade($student3->id, 2);
405
 
406
        // Create a hidden grade.
407
        $manuaitem->update_final_grade($student2->id, 3);
408
        $grade = \grade_grade::fetch(['itemid' => $manuaitem->id, 'userid' => $student2->id]);
409
        $grade->hidden = 1;
410
        $grade->update();
411
 
412
        // Trigger a regrade.
413
        grade_force_full_regrading($course->id);
414
        grade_regrade_final_grades($course->id);
415
 
416
        // Initialise reports.
417
        $context = \context_course::instance($course->id);
418
 
419
        $gpr = new grade_plugin_return(
420
            [
421
                'type' => 'report',
422
                'plugin' => 'grader',
423
                'course' => $course,
424
            ]
425
        );
426
 
427
        $report = new grade_report_grader($course->id, $gpr, $context);
428
 
429
        $ungradedcounts = $report->ungraded_counts(false, $hidden);
430
 
431
        $gradeitems = grade_item::fetch_all(['courseid' => $course->id]);
432
 
433
        foreach ($gradeitems as $gradeitem) {
434
            $sumgrades = null;
435
            if (array_key_exists($gradeitem->id, $ungradedcounts['ungradedcounts'])) {
436
                $ungradeditem = $ungradedcounts['ungradedcounts'][$gradeitem->id];
437
                if ($gradeitem->itemtype === 'course') {
438
                    $this->assertEquals($expectedcount['course'], $ungradeditem->count);
439
                } else if ($gradeitem->itemtype === 'manual') {
440
                    $this->assertEquals($expectedcount['Grade item1'], $ungradeditem->count);
441
                }
442
            }
443
 
444
            if (array_key_exists($gradeitem->id, $ungradedcounts['sumarray'])) {
445
                $sumgrades = $ungradedcounts['sumarray'][$gradeitem->id];
446
                if ($gradeitem->itemtype === 'course') {
447
                    $this->assertEquals($expectedsumarray['course'], $sumgrades);
448
                } else if ($gradeitem->itemtype === 'manual') {
449
                    $this->assertEquals($expectedsumarray['Grade item1'], $sumgrades);
450
                }
451
            }
452
        }
453
    }
454
 
455
    /**
456
     * Data provider for test_ungraded_counts_hidden_grades
457
     *
458
     * @return array of testing scenarios
459
     */
460
    public function ungraded_counts_hidden_grades_data(): array {
461
        return [
462
            'nohidden' => [
463
                'hidden' => false,
464
                'count' => ['course' => 1, 'Grade item1' => 1],
465
                'sumarray' => ['course' => 6.00000, 'Grade item1' => 3.00000],
466
            ],
467
            'includehidden' => [
468
                'hidden' => true,
469
                'count' => ['course' => 1, 'Grade item1' => 2],
470
                'sumarray' => ['course' => 6.00000, 'Grade item1' => 6.00000],
471
            ],
472
        ];
473
    }
474
 
475
    /**
476
     * Tests that ungraded_counts calculates count and sum of grades correctly for groups when there are graded users.
477
     *
478
     * @covers \grade_report::ungraded_counts
479
     */
11 efrain 480
    public function test_ungraded_count_sumgrades_groups(): void {
1 efrain 481
        global $DB;
482
 
483
        $this->resetAfterTest();
484
 
485
        $course = $this->getDataGenerator()->create_course();
486
 
487
        $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
488
 
489
        // Create users.
490
        $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
491
        $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
492
        $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
493
 
494
        // Enrol students.
495
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
496
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
497
        $this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);
498
 
499
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
500
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
501
        $this->getDataGenerator()->create_group_member(['userid' => $student1->id, 'groupid' => $group1->id]);
502
        $this->getDataGenerator()->create_group_member(['userid' => $student2->id, 'groupid' => $group2->id]);
503
        $this->getDataGenerator()->create_group_member(['userid' => $student3->id, 'groupid' => $group2->id]);
504
        $DB->set_field('course', 'groupmode', SEPARATEGROUPS, ['id' => $course->id]);
505
 
506
        // Create grade items.
507
        $assign1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
508
        $assign2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
509
        $quiz1 = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
510
 
511
        $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
512
            'itemname'        => 'Grade item1',
513
            'idnumber'        => 'git1',
514
            'courseid'        => $course->id,
515
        ]));
516
 
517
        // Grade users.
518
        $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign1->id));
519
        $assigninstance = new assign($cm->context, $cm, $course);
520
        $grade = $assigninstance->get_user_grade($student1->id, true);
521
        $grade->grade = 40;
522
        $assigninstance->update_grade($grade);
523
 
524
        $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign2->id));
525
        $assigninstance = new assign($cm->context, $cm, $course);
526
        $grade = $assigninstance->get_user_grade($student3->id, true);
527
        $grade->grade = 50;
528
        $assigninstance->update_grade($grade);
529
 
530
        $manuaitem->update_final_grade($student1->id, 1);
531
        $manuaitem->update_final_grade($student3->id, 2);
532
 
533
        // Trigger a regrade.
534
        grade_force_full_regrading($course->id);
535
        grade_regrade_final_grades($course->id);
536
 
537
        // Initialise report.
538
        $context = \context_course::instance($course->id);
539
 
540
        $gpr1 = new grade_plugin_return(
541
            [
542
                'type' => 'report',
543
                'plugin' => 'grader',
544
                'course' => $course,
545
                'groupid' => $group1->id,
546
            ]
547
        );
548
 
549
        $gpr2 = new grade_plugin_return(
550
            [
551
                'type' => 'report',
552
                'plugin' => 'grader',
553
                'course' => $course,
554
                'groupid' => $group2->id,
555
            ]
556
        );
557
 
558
        $report1 = new grade_report_grader($course->id, $gpr1, $context);
559
        $report2 = new grade_report_grader($course->id, $gpr2, $context);
560
 
561
        $ungradedcounts = [];
562
        $ungradedcounts[$group1->id] = $report1->ungraded_counts(true);
563
        $ungradedcounts[$group2->id] = $report2->ungraded_counts(true);
564
 
565
        $gradeitems = grade_item::fetch_all(['courseid' => $course->id]);
566
 
567
        // In group1 there is 1 student and assign1 and quiz1 are not graded for him.
568
        $this->assertEquals(2, count($ungradedcounts[$group1->id]['ungradedcounts']));
569
 
570
        // In group1 manual grade item, assign1 and course total have some grades.
571
        $this->assertEquals(3, count($ungradedcounts[$group1->id]['sumarray']));
572
 
573
        // In group2 student2 has no grades at all so all 5 grade items should present.
574
        $this->assertEquals(5, count($ungradedcounts[$group2->id]['ungradedcounts']));
575
 
576
        // In group2 manual grade item, assign2 and course total have some grades.
577
        $this->assertEquals(3, count($ungradedcounts[$group2->id]['sumarray']));
578
 
579
        foreach ($gradeitems as $gradeitem) {
580
            $sumgrades = null;
581
 
582
            foreach ($ungradedcounts as $key => $ungradedcount) {
583
                if (array_key_exists($gradeitem->id, $ungradedcount['ungradedcounts'])) {
584
                    $ungradeditem = $ungradedcount['ungradedcounts'][$gradeitem->id];
585
                    if ($key == $group1->id) {
586
                        // Both assign2 and quiz1 are not graded for student1.
587
                        $this->assertEquals(1, $ungradeditem->count);
588
                    } else {
589
                        if ($gradeitem->itemtype === 'course') {
590
                            $this->assertEquals(1, $ungradeditem->count);
591
                        } else if ($gradeitem->itemmodule === 'assign') {
592
                            if ($gradeitem->itemname === $assign1->name) {
593
                                // In group2 assign1 is not graded for anyone.
594
                                $this->assertEquals(2, $ungradeditem->count);
595
                            } else {
596
                                // In group2 assign2 is graded for student3.
597
                                $this->assertEquals(1, $ungradeditem->count);
598
                            }
599
                        } else if ($gradeitem->itemmodule === 'quiz') {
600
                            $this->assertEquals(2, $ungradeditem->count);
601
                        } else if ($gradeitem->itemtype === 'manual') {
602
                            $this->assertEquals(1, $ungradeditem->count);
603
                        }
604
                    }
605
                }
606
 
607
                if (array_key_exists($gradeitem->id, $ungradedcount['sumarray'])) {
608
                    $sumgrades = $ungradedcount['sumarray'][$gradeitem->id];
609
                    if ($key == $group1->id) {
610
                        if ($gradeitem->itemtype === 'course') {
611
                            $this->assertEquals('41.00000', $sumgrades);
612
                        } else if ($gradeitem->itemmodule === 'assign') {
613
                            $this->assertEquals('40.00000', $sumgrades);
614
                        } else if ($gradeitem->itemtype === 'manual') {
615
                            $this->assertEquals('1.00000', $sumgrades);
616
                        }
617
                    } else {
618
                        if ($gradeitem->itemtype === 'course') {
619
                            $this->assertEquals('52.00000', $sumgrades);
620
                        } else if ($gradeitem->itemmodule === 'assign') {
621
                            $this->assertEquals('50.00000', $sumgrades);
622
                        } else if ($gradeitem->itemtype === 'manual') {
623
                            $this->assertEquals('2.00000', $sumgrades);
624
                        }
625
                    }
626
                }
627
            }
628
        }
629
    }
630
 
631
    /**
632
     * Tests that ungraded_counts calculates count and sum of grades correctly when there are hidden grades.
633
     * @dataProvider ungraded_counts_only_active_enrol_data()
634
     * @param bool $onlyactive Site setting to show only active users.
635
     * @param int $hascapability Capability constant
636
     * @param bool|null $showonlyactiveenrolpref Show only active user preference.
637
     * @param array $expectedcount expected count value (i.e. number of ugraded grades)
638
     * @param array $expectedsumarray expected sum of grades
639
     *
640
     * @covers \grade_report::ungraded_counts
641
     */
642
    public function test_ungraded_counts_only_active_enrol(bool $onlyactive,
11 efrain 643
            int $hascapability, ?bool $showonlyactiveenrolpref, array $expectedcount, array $expectedsumarray): void {
1 efrain 644
        global $CFG, $DB;
645
 
646
        $this->resetAfterTest();
647
 
648
        $CFG->grade_report_showonlyactiveenrol = $onlyactive;
649
        $course = $this->getDataGenerator()->create_course();
650
 
651
        // Create users.
652
        $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
653
        $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
654
        $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
655
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
656
 
657
        // Enrol students.
658
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
659
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
660
 
661
        // Give teacher 'viewsuspendedusers' capability and set a preference to display suspended users.
662
        $roleteacher = $DB->get_record('role', ['shortname' => 'teacher'], '*', MUST_EXIST);
663
        $coursecontext = \context_course::instance($course->id);
664
        assign_capability('moodle/course:viewsuspendedusers', $hascapability, $roleteacher->id, $coursecontext, true);
665
        set_user_preference('grade_report_showonlyactiveenrol', $showonlyactiveenrolpref, $teacher);
666
        accesslib_clear_all_caches_for_unit_testing();
667
 
668
        $this->setUser($teacher);
669
 
670
        // Suspended student.
671
        $this->getDataGenerator()->enrol_user($student3->id, $course->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
672
 
673
        // Create grade items in course.
674
        $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
675
            'itemname' => 'Grade item1',
676
            'idnumber' => 'git1',
677
            'courseid' => $course->id,
678
        ]));
679
 
680
        // Grade users.
681
        $manuaitem->update_final_grade($student1->id, 1);
682
        $manuaitem->update_final_grade($student3->id, 2);
683
 
684
        // Trigger a regrade.
685
        grade_force_full_regrading($course->id);
686
        grade_regrade_final_grades($course->id);
687
 
688
        // Initialise reports.
689
        $context = \context_course::instance($course->id);
690
 
691
        $gpr = new grade_plugin_return(
692
            [
693
                'type' => 'report',
694
                'plugin' => 'grader',
695
                'course' => $course,
696
            ]
697
        );
698
 
699
        $report = new grade_report_grader($course->id, $gpr, $context);
700
 
701
        $showonlyactiveenrol = $report->show_only_active();
702
        $ungradedcounts = $report->ungraded_counts(false, false, $showonlyactiveenrol);
703
 
704
        $gradeitems = grade_item::fetch_all(['courseid' => $course->id]);
705
 
706
        foreach ($gradeitems as $gradeitem) {
707
            $sumgrades = null;
708
            if (array_key_exists($gradeitem->id, $ungradedcounts['ungradedcounts'])) {
709
                $ungradeditem = $ungradedcounts['ungradedcounts'][$gradeitem->id];
710
                if ($gradeitem->itemtype === 'course') {
711
                    $this->assertEquals($expectedcount['course'], $ungradeditem->count);
712
                } else if ($gradeitem->itemtype === 'manual') {
713
                    $this->assertEquals($expectedcount['Grade item1'], $ungradeditem->count);
714
                }
715
            }
716
 
717
            if (array_key_exists($gradeitem->id, $ungradedcounts['sumarray'])) {
718
                $sumgrades = $ungradedcounts['sumarray'][$gradeitem->id];
719
                if ($gradeitem->itemtype === 'course') {
720
                    $this->assertEquals($expectedsumarray['course'], $sumgrades);
721
                } else if ($gradeitem->itemtype === 'manual') {
722
                    $this->assertEquals($expectedsumarray['Grade item1'], $sumgrades);
723
                }
724
            }
725
        }
726
    }
727
 
728
    /**
729
     * Data provider for test_ungraded_counts_hidden_grades
730
     *
731
     * @return array of testing scenarios
732
     */
733
    public function ungraded_counts_only_active_enrol_data(): array {
734
        return [
735
            'Show only active and no user preference' => [
736
                'onlyactive' => true,
737
                'hascapability' => 1,
738
                'showonlyactiveenrolpref' => null,
739
                'count' => ['course' => 1, 'Grade item1' => 1],
740
                'sumarray' => ['course' => 1, 'Grade item1' => 1.00000],
741
            ],
742
            'Show only active and user preference set to true' => [
743
                'onlyactive' => true,
744
                'hascapability' => 1,
745
                'showonlyactiveenrolpref' => true,
746
                'count' => ['course' => 1, 'Grade item1' => 1],
747
                'sumarray' => ['course' => 1, 'Grade item1' => 1.00000],
748
            ],
749
            'Show only active and user preference set to false' => [
750
                'onlyactive' => true,
751
                'hascapability' => 1,
752
                'showonlyactiveenrolpref' => false,
753
                'count' => ['course' => 1, 'Grade item1' => 1],
754
                'sumarray' => ['course' => 3.00000, 'Grade item1' => 3.00000],
755
            ],
756
            'Include suspended with capability and user preference set to true' => [
757
                'onlyactive' => false,
758
                'hascapability' => 1,
759
                'showonlyactiveenrolpref' => true,
760
                'count' => ['course' => 1, 'Grade item1' => 1],
761
                'sumarray' => ['course' => 1.00000, 'Grade item1' => 1.00000],
762
            ],
763
            'Include suspended with capability and user preference set to false' => [
764
                'onlyactive' => false,
765
                'hascapability' => 1,
766
                'showonlyactiveenrolpref' => false,
767
                'count' => ['course' => 1, 'Grade item1' => 1],
768
                'sumarray' => ['course' => 3.00000, 'Grade item1' => 3.00000],
769
            ],
770
            'Include suspended with capability and no user preference' => [
771
                'onlyactive' => false,
772
                'hascapability' => 1,
773
                'showonlyactiveenrolpref' => null,
774
                'count' => ['course' => 1, 'Grade item1' => 1],
775
                'sumarray' => ['course' => 3.00000, 'Grade item1' => 3.00000],
776
            ],
777
            'Include suspended without capability' => [
778
                'onlyactive' => false,
779
                'hascapability' => -1,
780
                'showonlyactiveenrolpref' => null,
781
                'count' => ['course' => 1, 'Grade item1' => 1],
782
                'sumarray' => ['course' => 1.00000, 'Grade item1' => 1.00000],
783
            ],
784
        ];
785
    }
786
 
787
    /**
788
     * Tests for calculate_average.
789
     * @dataProvider calculate_average_data()
790
     * @param int $meanselection Whether to inlcude all grades or non-empty grades in aggregation.
791
     * @param array $expectedmeancount expected meancount value
792
     * @param array $expectedaverage expceted average value
793
     *
794
     * @covers \grade_report::calculate_average
795
     */
11 efrain 796
    public function test_calculate_average(int $meanselection, array $expectedmeancount, array $expectedaverage): void {
1 efrain 797
        global $DB;
798
 
799
        $this->resetAfterTest(true);
800
 
801
        $course = $this->getDataGenerator()->create_course();
802
 
803
        $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
804
        $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
805
        $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
806
 
807
        $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
808
 
809
        // Enrol students.
810
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
811
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
812
        $this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);
813
 
814
        // Create activities.
815
        $assign1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
816
        $assign2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
817
        $quiz1 = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
818
 
819
        // Grade users.
820
        $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign1->id));
821
        $assigninstance = new assign($cm->context, $cm, $course);
822
        $grade = $assigninstance->get_user_grade($student1->id, true);
823
        $grade->grade = 40;
824
        $assigninstance->update_grade($grade);
825
 
826
        $grade = $assigninstance->get_user_grade($student2->id, true);
827
        $grade->grade = 30;
828
        $assigninstance->update_grade($grade);
829
 
830
        $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign2->id));
831
        $assigninstance = new assign($cm->context, $cm, $course);
832
        $grade = $assigninstance->get_user_grade($student3->id, true);
833
        $grade->grade = 50;
834
        $assigninstance->update_grade($grade);
835
 
836
        $grade = $assigninstance->get_user_grade($student1->id, true);
837
        $grade->grade = 100;
838
        $assigninstance->update_grade($grade);
839
 
840
        // Make a manual grade items.
841
        $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
842
            'itemname'        => 'Grade item1',
843
            'idnumber'        => 'git1',
844
            'courseid'        => $course->id,
845
        ]));
846
        $manuaitem->update_final_grade($student1->id, 1);
847
        $manuaitem->update_final_grade($student3->id, 2);
848
 
849
        // Initialise report.
850
        $context = \context_course::instance($course->id);
851
 
852
        $gpr = new grade_plugin_return(
853
            [
854
                'type' => 'report',
855
                'plugin' => 'grader',
856
                'course' => $course,
857
            ]
858
        );
859
 
860
        $report = new grade_report_grader($course->id, $gpr, $context);
861
 
862
        $ungradedcounts = $report->ungraded_counts(false);
863
        $ungradedcounts['report']['meanselection'] = $meanselection;
864
 
865
        $gradeitems = grade_item::fetch_all(['courseid' => $course->id]);
866
 
867
        foreach ($gradeitems as $gradeitem) {
868
            $name = $gradeitem->itemname . ' ' . $gradeitem->itemtype;
869
            $aggr = $report->calculate_average($gradeitem, $ungradedcounts);
870
 
871
            $this->assertEquals($expectedmeancount[$name], $aggr['meancount']);
872
            $this->assertEquals($expectedaverage[$name], $aggr['average']);
873
        }
874
    }
875
 
876
    /**
877
     * Data provider for test_calculate_average
878
     *
879
     * @return array of testing scenarios
880
     */
881
    public function calculate_average_data(): array {
882
        return [
883
            'Non-empty grades' => [
884
                'meanselection' => 1,
885
                'expectedmeancount' => [' course' => 3, 'Assignment 1 mod' => 2, 'Assignment 2 mod' => 2,
886
                    'Quiz 1 mod' => 0, 'Grade item1 manual' => 2],
887
                'expectedaverage' => [' course' => 73.33333333333333, 'Assignment 1 mod' => 35.0,
888
                    'Assignment 2 mod' => 75.0, 'Quiz 1 mod' => null, 'Grade item1 manual' => 1.5],
889
            ],
890
            'All grades' => [
891
                'meanselection' => 0,
892
                'expectedmeancount' => [' course' => 3, 'Assignment 1 mod' => 3, 'Assignment 2 mod' => 3,
893
                    'Quiz 1 mod' => 3, 'Grade item1 manual' => 3],
894
                'expectedaverage' => [' course' => 73.33333333333333, 'Assignment 1 mod' => 23.333333333333332,
895
                    'Assignment 2 mod' => 50.0, 'Quiz 1 mod' => null, 'Grade item1 manual' => 1.0],
896
            ],
897
        ];
898
    }
899
 
900
    /**
901
     * Tests for item types.
902
     *
903
     * @covers \grade_report::item_types
904
     */
11 efrain 905
    public function test_item_types(): void {
1 efrain 906
        $this->resetAfterTest(true);
907
 
908
        $course1 = $this->getDataGenerator()->create_course();
909
        $course2 = $this->getDataGenerator()->create_course();
910
 
911
        // Create activities.
912
        $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
913
        $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
914
        $this->getDataGenerator()->create_module('quiz', ['course' => $course1->id]);
915
 
916
        $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
917
 
918
        // Create manual grade items.
919
        new \grade_item($this->getDataGenerator()->create_grade_item([
920
            'itemname'        => 'Grade item1',
921
            'idnumber'        => 'git1',
922
            'courseid'        => $course1->id,
923
        ]));
924
 
925
        new \grade_item($this->getDataGenerator()->create_grade_item([
926
            'itemname'        => 'Grade item2',
927
            'idnumber'        => 'git2',
928
            'courseid'        => $course2->id,
929
        ]));
930
 
931
        // Create a grade category (it should not be fetched by item_types).
932
        new \grade_category($this->getDataGenerator()
933
            ->create_grade_category(['courseid' => $course1->id]), false);
934
 
935
        // Initialise reports.
936
        $context = \context_course::instance($course1->id);
937
 
938
        $gpr = new grade_plugin_return(
939
            [
940
                'type' => 'report',
941
                'plugin' => 'grader',
942
                'course' => $course1,
943
            ]
944
        );
945
 
946
        $report1 = new grade_report_grader($course1->id, $gpr, $context);
947
 
948
        $context = \context_course::instance($course2->id);
949
 
950
        $gpr = new grade_plugin_return(
951
            [
952
                'type' => 'report',
953
                'plugin' => 'grader',
954
                'course' => $course2,
955
            ]
956
        );
957
 
958
        $report2 = new grade_report_grader($course2->id, $gpr, $context);
959
 
960
        $gradeitems1 = $report1->item_types();
961
        $gradeitems2 = $report2->item_types();
962
 
963
        $this->assertEquals(3, count($gradeitems1));
964
        $this->assertEquals(2, count($gradeitems2));
965
 
966
        $this->assertArrayHasKey('assign', $gradeitems1);
967
        $this->assertArrayHasKey('quiz', $gradeitems1);
968
        $this->assertArrayHasKey('manual', $gradeitems1);
969
 
970
        $this->assertArrayHasKey('assign', $gradeitems2);
971
        $this->assertArrayHasKey('manual', $gradeitems2);
972
    }
973
 
974
    /**
975
     * Test get_gradable_users() function.
976
     *
977
     * @covers ::get_gradable_users
978
     */
11 efrain 979
    public function test_get_gradable_users(): void {
1 efrain 980
        global $DB;
981
 
982
        $this->setAdminUser();
983
        $this->resetAfterTest(true);
984
 
985
        $roleteacher = $DB->get_record('role', ['shortname' => 'teacher'], '*', MUST_EXIST);
986
 
987
        // Create a course.
988
        $course = $this->getDataGenerator()->create_course();
989
        $coursecontext = \context_course::instance($course->id);
990
        // Create groups.
991
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
992
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
993
        // Create and enrol a teacher and some students into the course.
994
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
995
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
996
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
997
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
998
        // Add student1 and student2 to group1.
999
        $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $student1->id]);
1000
        $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $student2->id]);
1001
        // Add student3 to group2.
1002
        $this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $student3->id]);
1003
 
1004
        // Perform a regrade before creating the report.
1005
        grade_regrade_final_grades($course->id);
1006
        // Should return all gradable users (only students).
1007
        $gradableusers = get_gradable_users($course->id);
1008
        $this->assertEqualsCanonicalizing([$student1->id, $student2->id, $student3->id], array_keys($gradableusers));
1009
 
1010
        // Now, let's suspend the enrolment of student2.
1011
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
1012
        // Should return only the active gradable users (student1 and student3).
1013
        $gradableusers = \grade_report::get_gradable_users($course->id);
1014
        $this->assertEqualsCanonicalizing([$student1->id, $student3->id], array_keys($gradableusers));
1015
 
1016
        // Give teacher 'viewsuspendedusers' capability and set a preference to display suspended users.
1017
        assign_capability('moodle/course:viewsuspendedusers', CAP_ALLOW, $roleteacher->id, $coursecontext, true);
1018
        set_user_preference('grade_report_showonlyactiveenrol', false, $teacher);
1019
        accesslib_clear_all_caches_for_unit_testing();
1020
 
1021
        $this->setUser($teacher);
1022
        // Should return all gradable users (including suspended enrolments).
1023
        $gradableusers = \grade_report::get_gradable_users($course->id);
1024
        $this->assertEqualsCanonicalizing([$student1->id, $student2->id, $student3->id], array_keys($gradableusers));
1025
 
1026
        // Reactivate the course enrolment of student2.
1027
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student', 'manual', 0, 0, ENROL_USER_ACTIVE);
1028
        $this->setAdminUser();
1029
        // Should return all gradable users from group1 (student1 and student2).
1030
        $gradableusers = \grade_report::get_gradable_users($course->id, $group1->id);
1031
        $this->assertEqualsCanonicalizing([$student1->id, $student2->id], array_keys($gradableusers));
1032
        // Should return all gradable users from group2 (student3).
1033
        $gradableusers = \grade_report::get_gradable_users($course->id, $group2->id);
1034
        $this->assertEqualsCanonicalizing([$student3->id], array_keys($gradableusers));
1035
    }
1036
}