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
namespace core_courseformat;
18
 
19
use course_modinfo;
20
use moodle_exception;
21
use stdClass;
22
 
23
/**
24
 * Tests for the stateactions class.
25
 *
26
 * @package    core_courseformat
27
 * @category   test
28
 * @copyright  2021 Sara Arjona (sara@moodle.com)
29
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 * @coversDefaultClass \core_courseformat\stateactions
31
 */
32
class stateactions_test extends \advanced_testcase {
33
    /**
34
     * Helper method to create an activity into a section and add it to the $sections and $activities arrays.
35
     *
36
     * @param int $courseid Course identifier where the activity will be added.
37
     * @param string $type Activity type ('forum', 'assign', ...).
38
     * @param int $section Section number where the activity will be added.
39
     * @param bool $visible Whether the activity will be visible or not.
40
     * @return int the activity cm id
41
     */
42
    private function create_activity(
43
        int $courseid,
44
        string $type,
45
        int $section,
46
        bool $visible = true
47
    ): int {
48
 
49
        $activity = $this->getDataGenerator()->create_module(
50
            $type,
51
            ['course' => $courseid],
52
            [
53
                'section' => $section,
54
                'visible' => $visible
55
            ]
56
        );
57
        return $activity->cmid;
58
    }
59
 
60
    /**
61
     * Helper to create a course and generate a section list.
62
     *
63
     * @param string $format the course format
64
     * @param int $sections the number of sections
65
     * @param int[] $hiddensections the section numbers to hide
66
     * @return stdClass the course object
67
     */
68
    private function create_course(string $format, int $sections, array $hiddensections): stdClass {
69
        global $DB;
70
 
71
        $course = $this->getDataGenerator()->create_course(['numsections' => $sections, 'format' => $format]);
72
        foreach ($hiddensections as $section) {
73
            set_section_visible($course->id, $section, 0);
74
        }
75
 
76
        return $course;
77
    }
78
 
79
    /**
80
     * Return an array if the course references.
81
     *
82
     * This method is used to create alias to sections and other stuff in the dataProviders.
83
     *
84
     * @param stdClass $course the course object
85
     * @return int[] a relation betwee all references and its element id
86
     */
87
    private function course_references(stdClass $course): array {
88
        global $DB;
89
 
90
        $references = [];
91
 
92
        $sectionrecords = $DB->get_records('course_sections', ['course' => $course->id]);
93
        foreach ($sectionrecords as $id => $section) {
94
            $references["section{$section->section}"] = $section->id;
95
        }
96
        $references['course'] = $course->id;
97
        $references['invalidsection'] = -1;
98
        $references['invalidcm'] = -1;
99
 
100
        return $references;
101
    }
102
 
103
    /**
104
     * Translate a references array into current ids.
105
     *
106
     * @param string[] $references the references list
107
     * @param string[] $values the values to translate
108
     * @return int[] the list of ids
109
     */
110
    private function translate_references(array $references, array $values): array {
111
        $result = [];
112
        foreach ($values as $value) {
113
            $result[] = $references[$value];
114
        }
115
        return $result;
116
    }
117
 
118
    /**
119
     * Generate a sorted and summarized list of an state updates message.
120
     *
121
     * It is important to note that the order in the update messages are not important in a real scenario
122
     * because each message affects a specific part of the course state. However, for the PHPUnit test
123
     * have them sorted and classified simplifies the asserts.
124
     *
125
     * @param stateupdates $updateobj the state updates object
126
     * @return array of all data updates.
127
     */
128
    private function summarize_updates(stateupdates $updateobj): array {
129
        // Check state returned after executing given action.
130
        $updatelist = $updateobj->jsonSerialize();
131
 
132
        // Initial summary structure.
133
        $result = [
134
            'create' => [
135
                'course' => [],
136
                'section' => [],
137
                'cm' => [],
138
                'count' => 0,
139
            ],
140
            'put' => [
141
                'course' => [],
142
                'section' => [],
143
                'cm' => [],
144
                'count' => 0,
145
            ],
146
            'remove' => [
147
                'course' => [],
148
                'section' => [],
149
                'cm' => [],
150
                'count' => 0,
151
            ],
152
        ];
153
        foreach ($updatelist as $update) {
154
            if (!isset($result[$update->action])) {
155
                $result[$update->action] = [
156
                    'course' => [],
157
                    'section' => [],
158
                    'cm' => [],
159
                    'count' => 0,
160
                ];
161
            }
162
            $elementid = $update->fields->id ?? 0;
163
            $result[$update->action][$update->name][$elementid] = $update->fields;
164
            $result[$update->action]['count']++;
165
        }
166
        return $result;
167
    }
168
 
169
    /**
170
     * Enrol, set and create the test user depending on the role name.
171
     *
172
     * @param stdClass $course the course data
173
     * @param string $rolename the testing role name
174
     */
175
    private function set_test_user_by_role(stdClass $course, string $rolename) {
176
        if ($rolename == 'admin') {
177
            $this->setAdminUser();
178
        } else {
179
            $user = $this->getDataGenerator()->create_user();
180
            if ($rolename != 'unenroled') {
181
                $this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename);
182
            }
183
            $this->setUser($user);
184
        }
185
    }
186
 
187
    /**
188
     * Test the behaviour course_state.
189
     *
190
     * @dataProvider get_state_provider
191
     * @covers ::course_state
192
     * @covers ::section_state
193
     * @covers ::cm_state
194
     *
195
     * @param string $format The course will be created with this course format.
196
     * @param string $role The role of the user that will execute the method.
197
     * @param string $method the method to call
198
     * @param array $params the ids, targetsection and targetcm to use as params
199
     * @param array $expectedresults List of the course module names expected after calling the method.
200
     * @param bool $expectedexception If this call will raise an exception.
201
 
202
     */
203
    public function test_get_state(
204
        string $format,
205
        string $role,
206
        string $method,
207
        array $params,
208
        array $expectedresults,
209
        bool $expectedexception = false
210
    ): void {
211
 
212
        $this->resetAfterTest();
213
 
214
        // Create a course with 3 sections, 1 of them hidden.
215
        $course = $this->create_course($format, 3, [2]);
216
 
217
        $references = $this->course_references($course);
218
 
219
        // Create and enrol user using given role.
220
        $this->set_test_user_by_role($course, $role);
221
 
222
        // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
223
        $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
224
        $references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
225
        $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
226
        $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
227
 
228
        if ($expectedexception) {
229
            $this->expectException(moodle_exception::class);
230
        }
231
 
232
        // Initialise stateupdates.
233
        $courseformat = course_get_format($course->id);
234
        $updates = new stateupdates($courseformat);
235
 
236
        // Execute given method.
237
        $actions = new stateactions();
238
        $actions->$method(
239
            $updates,
240
            $course,
241
            $this->translate_references($references, $params['ids']),
242
            $references[$params['targetsectionid']] ?? null,
243
            $references[$params['targetcmid']] ?? null
244
        );
245
 
246
        // Format results in a way we can compare easily.
247
        $results = $this->summarize_updates($updates);
248
 
249
        // The state actions does not use create or remove actions because they are designed
250
        // to refresh parts of the state.
251
        $this->assertEquals(0, $results['create']['count']);
252
        $this->assertEquals(0, $results['remove']['count']);
253
 
254
        // Validate we have all the expected entries.
255
        $expectedtotal = count($expectedresults['course']) + count($expectedresults['section']) + count($expectedresults['cm']);
256
        $this->assertEquals($expectedtotal, $results['put']['count']);
257
 
258
        // Validate course, section and cm.
259
        foreach ($expectedresults as $name => $referencekeys) {
260
            foreach ($referencekeys as $referencekey) {
261
                $this->assertArrayHasKey($references[$referencekey], $results['put'][$name]);
262
            }
263
        }
264
    }
265
 
266
    /**
267
     * Data provider for data request creation tests.
268
     *
269
     * @return array the testing scenarios
270
     */
271
    public function get_state_provider(): array {
272
        return array_merge(
273
            $this->course_state_provider('weeks'),
274
            $this->course_state_provider('topics'),
275
            $this->course_state_provider('social'),
276
            $this->section_state_provider('weeks', 'admin'),
277
            $this->section_state_provider('weeks', 'editingteacher'),
278
            $this->section_state_provider('weeks', 'student'),
279
            $this->section_state_provider('topics', 'admin'),
280
            $this->section_state_provider('topics', 'editingteacher'),
281
            $this->section_state_provider('topics', 'student'),
282
            $this->section_state_provider('social', 'admin'),
283
            $this->section_state_provider('social', 'editingteacher'),
284
            $this->section_state_provider('social', 'student'),
285
            $this->cm_state_provider('weeks', 'admin'),
286
            $this->cm_state_provider('weeks', 'editingteacher'),
287
            $this->cm_state_provider('weeks', 'student'),
288
            $this->cm_state_provider('topics', 'admin'),
289
            $this->cm_state_provider('topics', 'editingteacher'),
290
            $this->cm_state_provider('topics', 'student'),
291
            $this->cm_state_provider('social', 'admin'),
292
            $this->cm_state_provider('social', 'editingteacher'),
293
            $this->cm_state_provider('social', 'student'),
294
        );
295
    }
296
 
297
    /**
298
     * Course state data provider.
299
     *
300
     * @param string $format the course format
301
     * @return array the testing scenarios
302
     */
303
    public function course_state_provider(string $format): array {
304
        $expectedexception = ($format === 'social');
305
        return [
306
            // Tests for course_state.
307
            "admin $format course_state" => [
308
                'format' => $format,
309
                'role' => 'admin',
310
                'method' => 'course_state',
311
                'params' => [
312
                    'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
313
                ],
314
                'expectedresults' => [
315
                    'course' => ['course'],
316
                    'section' => ['section0', 'section1', 'section2', 'section3'],
317
                    'cm' => ['cm0', 'cm1', 'cm2', 'cm3'],
318
                ],
319
                'expectedexception' => $expectedexception,
320
            ],
321
            "editingteacher $format course_state" => [
322
                'format' => $format,
323
                'role' => 'editingteacher',
324
                'method' => 'course_state',
325
                'params' => [
326
                    'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
327
                ],
328
                'expectedresults' => [
329
                    'course' => ['course'],
330
                    'section' => ['section0', 'section1', 'section2', 'section3'],
331
                    'cm' => ['cm0', 'cm1', 'cm2', 'cm3'],
332
                ],
333
                'expectedexception' => $expectedexception,
334
            ],
335
            "student $format course_state" => [
336
                'format' => $format,
337
                'role' => 'student',
338
                'method' => 'course_state',
339
                'params' => [
340
                    'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
341
                ],
342
                'expectedresults' => [
343
                    'course' => ['course'],
344
                    'section' => ['section0', 'section1', 'section3'],
345
                    'cm' => ['cm0'],
346
                ],
347
                'expectedexception' => $expectedexception,
348
            ],
349
        ];
350
    }
351
 
352
    /**
353
     * Section state data provider.
354
     *
355
     * @param string $format the course format
356
     * @param string $role the user role
357
     * @return array the testing scenarios
358
     */
359
    public function section_state_provider(string $format, string $role): array {
360
 
361
        // Social format will raise an exception and debug messages because it does not
362
        // use sections and it does not provide a renderer.
363
        $expectedexception = ($format === 'social');
364
 
365
        // All sections and cms that the user can access to.
366
        $usersections = ['section0', 'section1', 'section2', 'section3'];
367
        $usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
368
        if ($role == 'student') {
369
            $usersections = ['section0', 'section1', 'section3'];
370
            $usercms = ['cm0'];
371
        }
372
 
373
        return [
374
            "$role $format section_state no section" => [
375
                'format' => $format,
376
                'role' => $role,
377
                'method' => 'section_state',
378
                'params' => [
379
                    'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
380
                ],
381
                'expectedresults' => [],
382
                'expectedexception' => true,
383
            ],
384
            "$role $format section_state section 0" => [
385
                'format' => $format,
386
                'role' => $role,
387
                'method' => 'section_state',
388
                'params' => [
389
                    'ids' => ['section0'], 'targetsectionid' => null, 'targetcmid' => null
390
                ],
391
                'expectedresults' => [
392
                    'course' => [],
393
                    'section' => array_intersect(['section0'], $usersections),
394
                    'cm' => [],
395
                ],
396
                'expectedexception' => $expectedexception,
397
            ],
398
            "$role $format section_state visible section" => [
399
                'format' => $format,
400
                'role' => $role,
401
                'method' => 'section_state',
402
                'params' => [
403
                    'ids' => ['section1'], 'targetsectionid' => null, 'targetcmid' => null
404
                ],
405
                'expectedresults' => [
406
                    'course' => [],
407
                    'section' => array_intersect(['section1'], $usersections),
408
                    'cm' => array_intersect(['cm0', 'cm1'], $usercms),
409
                ],
410
                'expectedexception' => $expectedexception,
411
            ],
412
            "$role $format section_state hidden section" => [
413
                'format' => $format,
414
                'role' => $role,
415
                'method' => 'section_state',
416
                'params' => [
417
                    'ids' => ['section2'], 'targetsectionid' => null, 'targetcmid' => null
418
                ],
419
                'expectedresults' => [
420
                    'course' => [],
421
                    'section' => array_intersect(['section2'], $usersections),
422
                    'cm' => array_intersect(['cm2', 'cm3'], $usercms),
423
                ],
424
                'expectedexception' => $expectedexception,
425
            ],
426
            "$role $format section_state several sections" => [
427
                'format' => $format,
428
                'role' => $role,
429
                'method' => 'section_state',
430
                'params' => [
431
                    'ids' => ['section1', 'section3'], 'targetsectionid' => null, 'targetcmid' => null
432
                ],
433
                'expectedresults' => [
434
                    'course' => [],
435
                    'section' => array_intersect(['section1', 'section3'], $usersections),
436
                    'cm' => array_intersect(['cm0', 'cm1'], $usercms),
437
                ],
438
                'expectedexception' => $expectedexception,
439
            ],
440
            "$role $format section_state invalid section" => [
441
                'format' => $format,
442
                'role' => $role,
443
                'method' => 'section_state',
444
                'params' => [
445
                    'ids' => ['invalidsection'], 'targetsectionid' => null, 'targetcmid' => null
446
                ],
447
                'expectedresults' => [],
448
                'expectedexception' => true,
449
            ],
450
            "$role $format section_state using target section" => [
451
                'format' => $format,
452
                'role' => $role,
453
                'method' => 'section_state',
454
                'params' => [
455
                    'ids' => ['section1'], 'targetsectionid' => 'section3', 'targetcmid' => null
456
                ],
457
                'expectedresults' => [
458
                    'course' => [],
459
                    'section' => array_intersect(['section1', 'section3'], $usersections),
460
                    'cm' => array_intersect(['cm0', 'cm1'], $usercms),
461
                ],
462
                'expectedexception' => $expectedexception,
463
            ],
464
            "$role $format section_state using target targetcmid" => [
465
                'format' => $format,
466
                'role' => $role,
467
                'method' => 'section_state',
468
                'params' => [
469
                    'ids' => ['section3'], 'targetsectionid' => null, 'targetcmid' => 'cm1'
470
                ],
471
                'expectedresults' => [
472
                    'course' => [],
473
                    'section' => array_intersect(['section3'], $usersections),
474
                    'cm' => array_intersect(['cm1'], $usercms),
475
                ],
476
                'expectedexception' => $expectedexception,
477
            ],
478
        ];
479
    }
480
 
481
    /**
482
     * Course module state data provider.
483
     *
484
     * @param string $format the course format
485
     * @param string $role the user role
486
     * @return array the testing scenarios
487
     */
488
    public function cm_state_provider(string $format, string $role): array {
489
 
490
        // All sections and cms that the user can access to.
491
        $usersections = ['section0', 'section1', 'section2', 'section3'];
492
        $usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
493
        if ($role == 'student') {
494
            $usersections = ['section0', 'section1', 'section3'];
495
            $usercms = ['cm0'];
496
        }
497
 
498
        return [
499
            "$role $format cm_state no cms" => [
500
                'format' => $format,
501
                'role' => $role,
502
                'method' => 'cm_state',
503
                'params' => [
504
                    'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
505
                ],
506
                'expectedresults' => [],
507
                'expectedexception' => true,
508
            ],
509
            "$role $format cm_state visible cm" => [
510
                'format' => $format,
511
                'role' => $role,
512
                'method' => 'cm_state',
513
                'params' => [
514
                    'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => null
515
                ],
516
                'expectedresults' => [
517
                    'course' => [],
518
                    'section' => array_intersect(['section1'], $usersections),
519
                    'cm' => array_intersect(['cm0'], $usercms),
520
                ],
521
                'expectedexception' => false,
522
            ],
523
            "$role $format cm_state hidden cm" => [
524
                'format' => $format,
525
                'role' => $role,
526
                'method' => 'cm_state',
527
                'params' => [
528
                    'ids' => ['cm1'], 'targetsectionid' => null, 'targetcmid' => null
529
                ],
530
                'expectedresults' => [
531
                    'course' => [],
532
                    'section' => array_intersect(['section1'], $usersections),
533
                    'cm' => array_intersect(['cm1'], $usercms),
534
                ],
535
                'expectedexception' => false,
536
            ],
537
            "$role $format cm_state several cm" => [
538
                'format' => $format,
539
                'role' => $role,
540
                'method' => 'cm_state',
541
                'params' => [
542
                    'ids' => ['cm0', 'cm2'], 'targetsectionid' => null, 'targetcmid' => null
543
                ],
544
                'expectedresults' => [
545
                    'course' => [],
546
                    'section' => array_intersect(['section1', 'section2'], $usersections),
547
                    'cm' => array_intersect(['cm0', 'cm2'], $usercms),
548
                ],
549
                'expectedexception' => false,
550
            ],
551
            "$role $format cm_state using targetsection" => [
552
                'format' => $format,
553
                'role' => $role,
554
                'method' => 'cm_state',
555
                'params' => [
556
                    'ids' => ['cm0'], 'targetsectionid' => 'section2', 'targetcmid' => null
557
                ],
558
                'expectedresults' => [
559
                    'course' => [],
560
                    'section' => array_intersect(['section1', 'section2'], $usersections),
561
                    'cm' => array_intersect(['cm0'], $usercms),
562
                ],
563
                'expectedexception' => ($format === 'social'),
564
            ],
565
            "$role $format cm_state using targetcm" => [
566
                'format' => $format,
567
                'role' => $role,
568
                'method' => 'cm_state',
569
                'params' => [
570
                    'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => 'cm3'
571
                ],
572
                'expectedresults' => [
573
                    'course' => [],
574
                    'section' => array_intersect(['section1', 'section2'], $usersections),
575
                    'cm' => array_intersect(['cm0', 'cm3'], $usercms),
576
                ],
577
                'expectedexception' => false,
578
            ],
579
            "$role $format cm_state using an invalid cm" => [
580
                'format' => $format,
581
                'role' => $role,
582
                'method' => 'cm_state',
583
                'params' => [
584
                    'ids' => ['invalidcm'], 'targetsectionid' => null, 'targetcmid' => null
585
                ],
586
                'expectedresults' => [],
587
                'expectedexception' => true,
588
            ],
589
        ];
590
    }
591
 
592
    /**
593
     * Internal method for testing a specific state action.
594
     *
595
     * @param string $method the method to test
596
     * @param string $role the user role
597
     * @param string[] $idrefs the sections or cms id references to be used as method params
598
     * @param bool $expectedexception whether the call should throw an exception
599
     * @param int[] $expectedtotal the expected total number of state indexed by put, remove and create
600
     * @param string|null $coursefield the course field to check
601
     * @param int|string|null $coursevalue the section field value
602
     * @param string|null $sectionfield the section field to check
603
     * @param int|string|null $sectionvalue the section field value
604
     * @param string|null $cmfield the cm field to check
605
     * @param int|string|null $cmvalue the cm field value
606
     * @param string|null $targetsection optional target section reference
607
     * @param string|null $targetcm optional target cm reference
608
     * @return array an array of elements to do extra validations (course, references, results)
609
     */
610
    protected function basic_state_text(
611
        string $method = 'section_hide',
612
        string $role = 'editingteacher',
613
        array $idrefs = [],
614
        bool $expectedexception = false,
615
        array $expectedtotals = [],
616
        ?string $coursefield = null,
617
        $coursevalue = 0,
618
        ?string $sectionfield = null,
619
        $sectionvalue = 0,
620
        ?string $cmfield = null,
621
        $cmvalue = 0,
622
        ?string $targetsection = null,
623
        ?string $targetcm = null
624
    ): array {
625
        $this->resetAfterTest();
626
 
627
        // Create a course with 3 sections, 1 of them hidden.
628
        $course = $this->create_course('topics', 3, [2]);
629
 
630
        $references = $this->course_references($course);
631
 
632
        $user = $this->getDataGenerator()->create_user();
633
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $role);
634
        $this->setUser($user);
635
 
636
        // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
637
        $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
638
        $references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
639
        $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
640
        $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
641
        $references["cm4"] = $this->create_activity($course->id, 'forum', 2, false);
642
        $references["cm5"] = $this->create_activity($course->id, 'wiki', 2, false);
643
 
644
        if ($expectedexception) {
645
            $this->expectException(moodle_exception::class);
646
        }
647
 
648
        // Initialise stateupdates.
649
        $courseformat = course_get_format($course->id);
650
        $updates = new stateupdates($courseformat);
651
 
652
        // Execute the method.
653
        $actions = new stateactions();
654
        $actions->$method(
655
            $updates,
656
            $course,
657
            $this->translate_references($references, $idrefs),
658
            ($targetsection) ? $references[$targetsection] : null,
659
            ($targetcm) ? $references[$targetcm] : null,
660
        );
661
 
662
        // Format results in a way we can compare easily.
663
        $results = $this->summarize_updates($updates);
664
 
665
        // Validate we have all the expected entries.
666
        $this->assertEquals($expectedtotals['create'] ?? 0, $results['create']['count']);
667
        $this->assertEquals($expectedtotals['remove'] ?? 0, $results['remove']['count']);
668
        $this->assertEquals($expectedtotals['put'] ?? 0, $results['put']['count']);
669
 
670
        // Validate course, section and cm.
671
        if (!empty($coursefield)) {
672
            foreach ($results['put']['course'] as $courseid) {
673
                $this->assertEquals($coursevalue, $results['put']['course'][$courseid][$coursefield]);
674
            }
675
        }
676
        if (!empty($sectionfield)) {
677
            foreach ($results['put']['section'] as $section) {
678
                $this->assertEquals($sectionvalue, $section->$sectionfield);
679
            }
680
        }
681
        if (!empty($cmfield)) {
682
            foreach ($results['put']['cm'] as $cm) {
683
                $this->assertEquals($cmvalue, $cm->$cmfield);
684
            }
685
        }
686
        return [
687
            'course' => $course,
688
            'references' => $references,
689
            'results' => $results,
690
        ];
691
    }
692
 
693
    /**
694
     * Test for section_hide
695
     *
696
     * @covers ::section_hide
697
     * @dataProvider basic_role_provider
698
     * @param string $role the user role
699
     * @param bool $expectedexception if it will expect an exception.
700
     */
701
    public function test_section_hide(
702
        string $role = 'editingteacher',
703
        bool $expectedexception = false
704
    ): void {
705
        $this->basic_state_text(
706
            'section_hide',
707
            $role,
708
            ['section1', 'section2', 'section3'],
709
            $expectedexception,
710
            ['put' => 9],
711
            null,
712
            null,
713
            'visible',
714
            0,
715
            null,
716
            null
717
        );
718
    }
719
 
720
    /**
721
     * Test for section_hide
722
     *
723
     * @covers ::section_show
724
     * @dataProvider basic_role_provider
725
     * @param string $role the user role
726
     * @param bool $expectedexception if it will expect an exception.
727
     */
728
    public function test_section_show(
729
        string $role = 'editingteacher',
730
        bool $expectedexception = false
731
    ): void {
732
        $this->basic_state_text(
733
            'section_show',
734
            $role,
735
            ['section1', 'section2', 'section3'],
736
            $expectedexception,
737
            ['put' => 9],
738
            null,
739
            null,
740
            'visible',
741
            1,
742
            null,
743
            null
744
        );
745
    }
746
 
747
    /**
748
     * Test for cm_show
749
     *
750
     * @covers ::cm_show
751
     * @dataProvider basic_role_provider
752
     * @param string $role the user role
753
     * @param bool $expectedexception if it will expect an exception.
754
     */
755
    public function test_cm_show(
756
        string $role = 'editingteacher',
757
        bool $expectedexception = false
758
    ): void {
759
        $this->basic_state_text(
760
            'cm_show',
761
            $role,
762
            ['cm0', 'cm1', 'cm2', 'cm3'],
763
            $expectedexception,
764
            ['put' => 4],
765
            null,
766
            null,
767
            null,
768
            null,
769
            'visible',
770
            1
771
        );
772
    }
773
 
774
    /**
775
     * Test for cm_hide
776
     *
777
     * @covers ::cm_hide
778
     * @dataProvider basic_role_provider
779
     * @param string $role the user role
780
     * @param bool $expectedexception if it will expect an exception.
781
     */
782
    public function test_cm_hide(
783
        string $role = 'editingteacher',
784
        bool $expectedexception = false
785
    ): void {
786
        $this->basic_state_text(
787
            'cm_hide',
788
            $role,
789
            ['cm0', 'cm1', 'cm2', 'cm3'],
790
            $expectedexception,
791
            ['put' => 4],
792
            null,
793
            null,
794
            null,
795
            null,
796
            'visible',
797
 
798
        );
799
    }
800
 
801
    /**
802
     * Test for cm_stealth
803
     *
804
     * @covers ::cm_stealth
805
     * @dataProvider basic_role_provider
806
     * @param string $role the user role
807
     * @param bool $expectedexception if it will expect an exception.
808
     */
809
    public function test_cm_stealth(
810
        string $role = 'editingteacher',
811
        bool $expectedexception = false
812
    ): void {
813
        set_config('allowstealth', 1);
814
        $this->basic_state_text(
815
            'cm_stealth',
816
            $role,
817
            ['cm0', 'cm1', 'cm2', 'cm3'],
818
            $expectedexception,
819
            ['put' => 4],
820
            null,
821
            null,
822
            null,
823
            null,
824
            'stealth',
825
            1
826
        );
827
        // Disable stealth.
828
        set_config('allowstealth', 0);
829
        // When stealth are disabled the validation is a but more complex because they depends
830
        // also on the section visibility (legacy stealth).
831
        $this->basic_state_text(
832
            'cm_stealth',
833
            $role,
834
            ['cm0', 'cm1'],
835
            $expectedexception,
836
            ['put' => 2],
837
            null,
838
            null,
839
            null,
840
            null,
841
            'stealth',
842
 
843
        );
844
        $this->basic_state_text(
845
            'cm_stealth',
846
            $role,
847
            ['cm2', 'cm3'],
848
            $expectedexception,
849
            ['put' => 2],
850
            null,
851
            null,
852
            null,
853
            null,
854
            'stealth',
855
            1
856
        );
857
    }
858
 
859
    /**
860
     * Data provider for basic role tests.
861
     *
862
     * @return array the testing scenarios
863
     */
864
    public function basic_role_provider() {
865
        return [
866
            'editingteacher' => [
867
                'role' => 'editingteacher',
868
                'expectedexception' => false,
869
            ],
870
            'teacher' => [
871
                'role' => 'teacher',
872
                'expectedexception' => true,
873
            ],
874
            'student' => [
875
                'role' => 'student',
876
                'expectedexception' => true,
877
            ],
878
            'guest' => [
879
                'role' => 'guest',
880
                'expectedexception' => true,
881
            ],
882
        ];
883
    }
884
 
885
    /**
886
     * Duplicate course module method.
887
     *
888
     * @covers ::cm_duplicate
889
     * @dataProvider cm_duplicate_provider
890
     * @param string $targetsection the target section (empty for none)
891
     * @param bool $validcms if uses valid cms
892
     * @param string $role the current user role name
893
     * @param bool $expectedexception if the test will raise an exception
894
     */
895
    public function test_cm_duplicate(
896
        string $targetsection = '',
897
        bool $validcms = true,
898
        string $role = 'admin',
899
        bool $expectedexception = false
11 efrain 900
    ): void {
1 efrain 901
        $this->resetAfterTest();
902
 
903
        // Create a course with 3 sections.
904
        $course = $this->create_course('topics', 3, []);
905
 
906
        $references = $this->course_references($course);
907
 
908
        // Create and enrol user using given role.
909
        $this->set_test_user_by_role($course, $role);
910
 
911
        // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
912
        $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
913
        $references["cm1"] = $this->create_activity($course->id, 'page', 2, false);
914
 
915
        if ($expectedexception) {
916
            $this->expectException(moodle_exception::class);
917
        }
918
 
919
        // Initialise stateupdates.
920
        $courseformat = course_get_format($course->id);
921
        $updates = new stateupdates($courseformat);
922
 
923
        // Execute method.
924
        $targetsectionid = (!empty($targetsection)) ? $references[$targetsection] : null;
925
        $cmrefs = ($validcms) ? ['cm0', 'cm1'] : ['invalidcm'];
926
        $actions = new stateactions();
927
        $actions->cm_duplicate(
928
            $updates,
929
            $course,
930
            $this->translate_references($references, $cmrefs),
931
            $targetsectionid,
932
        );
933
 
934
        // Check the new elements in the course structure.
935
        $originalsections = [
936
            'assign' => $references['section1'],
937
            'page' => $references['section2'],
938
        ];
939
        $modinfo = course_modinfo::instance($course);
940
        $cms = $modinfo->get_cms();
941
        $i = 0;
942
        foreach ($cms as $cmid => $cminfo) {
943
            if ($cmid == $references['cm0'] || $cmid == $references['cm1']) {
944
                continue;
945
            }
946
            $references["newcm$i"] = $cmid;
947
            if ($targetsectionid) {
948
                $this->assertEquals($targetsectionid, $cminfo->section);
949
            } else {
950
                $this->assertEquals($originalsections[$cminfo->modname], $cminfo->section);
951
            }
952
            $i++;
953
        }
954
 
955
        // Check the resulting updates.
956
        $results = $this->summarize_updates($updates);
957
 
958
        if ($targetsectionid) {
959
            $this->assertArrayHasKey($references[$targetsection], $results['put']['section']);
960
        } else {
961
            $this->assertArrayHasKey($references['section1'], $results['put']['section']);
962
            $this->assertArrayHasKey($references['section2'], $results['put']['section']);
963
        }
964
        $countcms = ($targetsection == 'section3' || $targetsection === '') ? 2 : 3;
965
        $this->assertCount($countcms, $results['put']['cm']);
966
        $this->assertArrayHasKey($references['newcm0'], $results['put']['cm']);
967
        $this->assertArrayHasKey($references['newcm1'], $results['put']['cm']);
968
    }
969
 
970
    /**
971
     * Duplicate course module data provider.
972
     *
973
     * @return array the testing scenarios
974
     */
975
    public function cm_duplicate_provider(): array {
976
        return [
977
            'valid cms without target section' => [
978
                'targetsection' => '',
979
                'validcms' => true,
980
                'role' => 'admin',
981
                'expectedexception' => false,
982
            ],
983
            'valid cms targeting an empty section' => [
984
                'targetsection' => 'section3',
985
                'validcms' => true,
986
                'role' => 'admin',
987
                'expectedexception' => false,
988
            ],
989
            'valid cms targeting a section with activities' => [
990
                'targetsection' => 'section2',
991
                'validcms' => true,
992
                'role' => 'admin',
993
                'expectedexception' => false,
994
            ],
995
            'invalid cms without target section' => [
996
                'targetsection' => '',
997
                'validcms' => false,
998
                'role' => 'admin',
999
                'expectedexception' => true,
1000
            ],
1001
            'invalid cms with target section' => [
1002
                'targetsection' => 'section3',
1003
                'validcms' => false,
1004
                'role' => 'admin',
1005
                'expectedexception' => true,
1006
            ],
1007
            'student role with target section' => [
1008
                'targetsection' => 'section3',
1009
                'validcms' => true,
1010
                'role' => 'student',
1011
                'expectedexception' => true,
1012
            ],
1013
            'student role without target section' => [
1014
                'targetsection' => '',
1015
                'validcms' => true,
1016
                'role' => 'student',
1017
                'expectedexception' => true,
1018
            ],
1019
            'unrenolled user with target section' => [
1020
                'targetsection' => 'section3',
1021
                'validcms' => true,
1022
                'role' => 'unenroled',
1023
                'expectedexception' => true,
1024
            ],
1025
            'unrenolled user without target section' => [
1026
                'targetsection' => '',
1027
                'validcms' => true,
1028
                'role' => 'unenroled',
1029
                'expectedexception' => true,
1030
            ],
1031
        ];
1032
    }
1033
 
1034
    /**
1035
     * Test for cm_delete
1036
     *
1037
     * @covers ::cm_delete
1038
     * @dataProvider basic_role_provider
1039
     * @param string $role the user role
1040
     * @param bool $expectedexception if it will expect an exception.
1041
     */
1042
    public function test_cm_delete(
1043
        string $role = 'editingteacher',
1044
        bool $expectedexception = false
1045
    ): void {
1046
        $this->resetAfterTest();
1047
        // We want modules to be deleted for good.
1048
        set_config('coursebinenable', 0, 'tool_recyclebin');
1049
 
1050
        $info = $this->basic_state_text(
1051
            'cm_delete',
1052
            $role,
1053
            ['cm2', 'cm3'],
1054
            $expectedexception,
1055
            ['remove' => 2, 'put' => 1],
1056
        );
1057
 
1058
        $course = $info['course'];
1059
        $references = $info['references'];
1060
        $results = $info['results'];
1061
        $courseformat = course_get_format($course->id);
1062
 
1063
        $this->assertArrayNotHasKey($references['cm0'], $results['remove']['cm']);
1064
        $this->assertArrayNotHasKey($references['cm1'], $results['remove']['cm']);
1065
        $this->assertArrayHasKey($references['cm2'], $results['remove']['cm']);
1066
        $this->assertArrayHasKey($references['cm3'], $results['remove']['cm']);
1067
        $this->assertArrayNotHasKey($references['cm4'], $results['remove']['cm']);
1068
        $this->assertArrayNotHasKey($references['cm5'], $results['remove']['cm']);
1069
 
1070
        // Check the new section cm list.
1071
        $newcmlist = $this->translate_references($references, ['cm4', 'cm5']);
1072
        $section = $results['put']['section'][$references['section2']];
1073
        $this->assertEquals($newcmlist, $section->cmlist);
1074
 
1075
        // Check activities are deleted.
1076
        $modinfo = $courseformat->get_modinfo();
1077
        $cms = $modinfo->get_cms();
1078
        $this->assertArrayHasKey($references['cm0'], $cms);
1079
        $this->assertArrayHasKey($references['cm1'], $cms);
1080
        $this->assertArrayNotHasKey($references['cm2'], $cms);
1081
        $this->assertArrayNotHasKey($references['cm3'], $cms);
1082
        $this->assertArrayHasKey($references['cm4'], $cms);
1083
        $this->assertArrayHasKey($references['cm5'], $cms);
1084
    }
1085
 
1086
    /**
1087
     * Test for cm_moveright
1088
     *
1089
     * @covers ::cm_moveright
1090
     * @dataProvider basic_role_provider
1091
     * @param string $role the user role
1092
     * @param bool $expectedexception if it will expect an exception.
1093
     */
1094
    public function test_cm_moveright(
1095
        string $role = 'editingteacher',
1096
        bool $expectedexception = false
1097
    ): void {
1098
        $this->basic_state_text(
1099
            'cm_moveright',
1100
            $role,
1101
            ['cm0', 'cm1', 'cm2', 'cm3'],
1102
            $expectedexception,
1103
            ['put' => 4],
1104
            null,
1105
            null,
1106
            null,
1107
            null,
1108
            'indent',
1109
            1
1110
        );
1111
    }
1112
 
1113
    /**
1114
     * Test for cm_moveleft
1115
     *
1116
     * @covers ::cm_moveleft
1117
     * @dataProvider basic_role_provider
1118
     * @param string $role the user role
1119
     * @param bool $expectedexception if it will expect an exception.
1120
     */
1121
    public function test_cm_moveleft(
1122
        string $role = 'editingteacher',
1123
        bool $expectedexception = false
1124
    ): void {
1125
        $this->basic_state_text(
1126
            'cm_moveleft',
1127
            $role,
1128
            ['cm0', 'cm1', 'cm2', 'cm3'],
1129
            $expectedexception,
1130
            ['put' => 4],
1131
            null,
1132
            null,
1133
            null,
1134
            null,
1135
            'indent',
1136
 
1137
        );
1138
    }
1139
 
1140
    /**
1141
     * Test for cm_nogroups
1142
     *
1143
     * @covers ::cm_nogroups
1144
     * @dataProvider basic_role_provider
1145
     * @param string $role the user role
1146
     * @param bool $expectedexception if it will expect an exception.
1147
     */
1148
    public function test_cm_nogroups(
1149
        string $role = 'editingteacher',
1150
        bool $expectedexception = false
1151
    ): void {
1152
        $this->basic_state_text(
1153
            'cm_nogroups',
1154
            $role,
1155
            ['cm0', 'cm1', 'cm2', 'cm3'],
1156
            $expectedexception,
1157
            ['put' => 4],
1158
            null,
1159
            null,
1160
            null,
1161
            null,
1162
            'groupmode',
1163
            NOGROUPS
1164
        );
1165
    }
1166
 
1167
    /**
1168
     * Test for cm_visiblegroups
1169
     *
1170
     * @covers ::cm_visiblegroups
1171
     * @dataProvider basic_role_provider
1172
     * @param string $role the user role
1173
     * @param bool $expectedexception if it will expect an exception.
1174
     */
1175
    public function test_cm_visiblegroups(
1176
        string $role = 'editingteacher',
1177
        bool $expectedexception = false
1178
    ): void {
1179
        $this->basic_state_text(
1180
            'cm_visiblegroups',
1181
            $role,
1182
            ['cm0', 'cm1', 'cm2', 'cm3'],
1183
            $expectedexception,
1184
            ['put' => 4],
1185
            null,
1186
            null,
1187
            null,
1188
            null,
1189
            'groupmode',
1190
            VISIBLEGROUPS
1191
        );
1192
    }
1193
 
1194
    /**
1195
     * Test for cm_separategroups
1196
     *
1197
     * @covers ::cm_separategroups
1198
     * @dataProvider basic_role_provider
1199
     * @param string $role the user role
1200
     * @param bool $expectedexception if it will expect an exception.
1201
     */
1202
    public function test_cm_separategroups(
1203
        string $role = 'editingteacher',
1204
        bool $expectedexception = false
1205
    ): void {
1206
        $this->basic_state_text(
1207
            'cm_separategroups',
1208
            $role,
1209
            ['cm0', 'cm1', 'cm2', 'cm3'],
1210
            $expectedexception,
1211
            ['put' => 4],
1212
            null,
1213
            null,
1214
            null,
1215
            null,
1216
            'groupmode',
1217
            SEPARATEGROUPS
1218
        );
1219
    }
1220
 
1221
    /**
1222
     * Test for section_move_after
1223
     *
1224
     * @covers ::section_move_after
1225
     * @dataProvider section_move_after_provider
1226
     * @param string[] $sectiontomove the sections to move
1227
     * @param string $targetsection the target section reference
1228
     * @param string[] $finalorder the final sections order
1229
     * @param string[] $updatedcms the list of cms in the state updates
1230
     * @param int $totalputs the total amount of put updates
1231
     */
1232
    public function test_section_move_after(
1233
        array $sectiontomove,
1234
        string $targetsection,
1235
        array $finalorder,
1236
        array $updatedcms,
1237
        int $totalputs
1238
    ): void {
1239
        $this->resetAfterTest();
1240
 
1241
        $course = $this->create_course('topics', 8, []);
1242
 
1243
        $references = $this->course_references($course);
1244
 
1245
        // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
1246
        $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
1247
        $references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
1248
        $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
1249
        $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
1250
        $references["cm4"] = $this->create_activity($course->id, 'forum', 3, false);
1251
        $references["cm5"] = $this->create_activity($course->id, 'wiki', 3, false);
1252
 
1253
        $user = $this->getDataGenerator()->create_user();
1254
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher');
1255
        $this->setUser($user);
1256
 
1257
        // Initialise stateupdates.
1258
        $courseformat = course_get_format($course->id);
1259
        $updates = new stateupdates($courseformat);
1260
 
1261
        // Execute the method.
1262
        $actions = new stateactions();
1263
        $actions->section_move_after(
1264
            $updates,
1265
            $course,
1266
            $this->translate_references($references, $sectiontomove),
1267
            $references[$targetsection]
1268
        );
1269
 
1270
        // Format results in a way we can compare easily.
1271
        $results = $this->summarize_updates($updates);
1272
 
1273
        // Validate we have all the expected entries.
1274
        $this->assertEquals(0, $results['create']['count']);
1275
        $this->assertEquals(0, $results['remove']['count']);
1276
        // Moving a section puts:
1277
        // - The course state.
1278
        // - All sections state.
1279
        // - The cm states related to the moved and target sections.
1280
        $this->assertEquals($totalputs, $results['put']['count']);
1281
 
1282
        // Course state should contain the sorted list of sections (section zero + 8 sections).
1283
        $finalsectionids = $this->translate_references($references, $finalorder);
1284
        $coursestate = reset($results['put']['course']);
1285
        $this->assertEquals($finalsectionids, $coursestate->sectionlist);
1286
        // All sections should be present in the update.
1287
        $this->assertCount(9, $results['put']['section']);
1288
        // Only cms from the affected sections should be updated.
1289
        $cmids = $this->translate_references($references, $updatedcms);
1290
        $cms = $results['put']['cm'];
1291
        foreach ($cmids as $cmid) {
1292
            $this->assertArrayHasKey($cmid, $cms);
1293
        }
1294
    }
1295
 
1296
    /**
1297
     * Provider for test_section_move_after.
1298
     *
1299
     * @return array the testing scenarios
1300
     */
1301
    public function section_move_after_provider(): array {
1302
        return [
1303
            'Move sections down' => [
1304
                'sectiontomove' => ['section2', 'section4'],
1305
                'targetsection' => 'section7',
1306
                'finalorder' => [
1307
                    'section0',
1308
                    'section1',
1309
                    'section3',
1310
                    'section5',
1311
                    'section6',
1312
                    'section7',
1313
                    'section2',
1314
                    'section4',
1315
                    'section8',
1316
                ],
1317
                'updatedcms' => ['cm2', 'cm3'],
1318
                'totalputs' => 12,
1319
            ],
1320
            'Move sections up' => [
1321
                'sectiontomove' => ['section3', 'section5'],
1322
                'targetsection' => 'section1',
1323
                'finalorder' => [
1324
                    'section0',
1325
                    'section1',
1326
                    'section3',
1327
                    'section5',
1328
                    'section2',
1329
                    'section4',
1330
                    'section6',
1331
                    'section7',
1332
                    'section8',
1333
                ],
1334
                'updatedcms' => ['cm0', 'cm1', 'cm4', 'cm5'],
1335
                'totalputs' => 14,
1336
            ],
1337
            'Move sections in the middle' => [
1338
                'sectiontomove' => ['section2', 'section5'],
1339
                'targetsection' => 'section3',
1340
                'finalorder' => [
1341
                    'section0',
1342
                    'section1',
1343
                    'section3',
1344
                    'section2',
1345
                    'section5',
1346
                    'section4',
1347
                    'section6',
1348
                    'section7',
1349
                    'section8',
1350
                ],
1351
                'updatedcms' => ['cm2', 'cm3', 'cm4', 'cm5'],
1352
                'totalputs' => 14,
1353
            ],
1354
            'Move sections on top' => [
1355
                'sectiontomove' => ['section3', 'section5'],
1356
                'targetsection' => 'section0',
1357
                'finalorder' => [
1358
                    'section0',
1359
                    'section3',
1360
                    'section5',
1361
                    'section1',
1362
                    'section2',
1363
                    'section4',
1364
                    'section6',
1365
                    'section7',
1366
                    'section8',
1367
                ],
1368
                'updatedcms' => ['cm4', 'cm5'],
1369
                'totalputs' => 12,
1370
            ],
1371
            'Move sections on bottom' => [
1372
                'sectiontomove' => ['section3', 'section5'],
1373
                'targetsection' => 'section8',
1374
                'finalorder' => [
1375
                    'section0',
1376
                    'section1',
1377
                    'section2',
1378
                    'section4',
1379
                    'section6',
1380
                    'section7',
1381
                    'section8',
1382
                    'section3',
1383
                    'section5',
1384
                ],
1385
                'updatedcms' => ['cm4', 'cm5'],
1386
                'totalputs' => 12,
1387
            ],
1388
        ];
1389
    }
1390
 
1391
    /**
1392
     * Test for section_move_after capability checks.
1393
     *
1394
     * @covers ::section_move_after
1395
     * @dataProvider basic_role_provider
1396
     * @param string $role the user role
1397
     * @param bool $expectedexception if it will expect an exception.
1398
     */
1399
    public function test_section_move_after_capabilities(
1400
        string $role = 'editingteacher',
1401
        bool $expectedexception = false
1402
    ): void {
1403
        $this->resetAfterTest();
1404
        // We want modules to be deleted for good.
1405
        set_config('coursebinenable', 0, 'tool_recyclebin');
1406
 
1407
        $info = $this->basic_state_text(
1408
            'section_move_after',
1409
            $role,
1410
            ['section2'],
1411
            $expectedexception,
1412
            ['put' => 9],
1413
            null,
1414
            0,
1415
            null,
1416
            0,
1417
            null,
1418
            0,
1419
            'section0'
1420
        );
1421
    }
1422
}