Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace mod_scorm;
18
 
19
use core_external\external_api;
20
use externallib_advanced_testcase;
21
use mod_scorm_external;
22
 
23
defined('MOODLE_INTERNAL') || die();
24
 
25
global $CFG;
26
 
27
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
28
require_once($CFG->dirroot . '/mod/scorm/lib.php');
29
 
30
/**
31
 * SCORM module external functions tests
32
 *
33
 * @package    mod_scorm
34
 * @category   external
35
 * @copyright  2015 Juan Leyva <juan@moodle.com>
36
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 * @since      Moodle 3.0
38
 */
1441 ariadna 39
final class externallib_test extends externallib_advanced_testcase {
1 efrain 40
 
41
    /** @var \stdClass course record. */
42
    protected \stdClass $course;
43
 
44
    /** @var \stdClass activity record. */
45
    protected \stdClass $scorm;
46
 
47
    /** @var \core\context\module context instance. */
48
    protected \core\context\module $context;
49
 
50
    /** @var \stdClass */
51
    protected \stdClass $cm;
52
 
53
    /** @var \stdClass user record. */
54
    protected \stdClass $student;
55
 
56
    /** @var \stdClass user record. */
57
    protected \stdClass $teacher;
58
 
59
    /** @var \stdClass a fieldset object, false or exception if error not found. */
60
    protected \stdClass $studentrole;
61
 
62
    /** @var \stdClass a fieldset object, false or exception if error not found. */
63
    protected \stdClass $teacherrole;
64
 
65
    /**
66
     * Set up for every test
67
     */
68
    public function setUp(): void {
69
        global $DB, $CFG;
1441 ariadna 70
        parent::setUp();
1 efrain 71
        $this->resetAfterTest();
72
        $this->setAdminUser();
73
 
74
        $CFG->enablecompletion = 1;
75
        // Setup test data.
76
        $this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
77
        $this->scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $this->course->id),
78
            array('completion' => 2, 'completionview' => 1));
79
        $this->context = \context_module::instance($this->scorm->cmid);
80
        $this->cm = get_coursemodule_from_instance('scorm', $this->scorm->id);
81
 
82
        // Create users.
83
        $this->student = self::getDataGenerator()->create_user();
84
        $this->teacher = self::getDataGenerator()->create_user();
85
 
86
        // Users enrolments.
87
        $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
88
        $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
89
        $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
90
        $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
91
    }
92
 
93
    /**
94
     * Test view_scorm
95
     */
11 efrain 96
    public function test_view_scorm(): void {
1 efrain 97
        global $DB;
98
 
99
        // Test invalid instance id.
100
        try {
101
            mod_scorm_external::view_scorm(0);
102
            $this->fail('Exception expected due to invalid mod_scorm instance id.');
103
        } catch (\moodle_exception $e) {
104
            $this->assertEquals('invalidrecord', $e->errorcode);
105
        }
106
 
107
        // Test not-enrolled user.
108
        $user = self::getDataGenerator()->create_user();
109
        $this->setUser($user);
110
        try {
111
            mod_scorm_external::view_scorm($this->scorm->id);
112
            $this->fail('Exception expected due to not enrolled user.');
113
        } catch (\moodle_exception $e) {
114
            $this->assertEquals('requireloginerror', $e->errorcode);
115
        }
116
 
117
        // Test user with full capabilities.
118
        $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
119
        $this->getDataGenerator()->enrol_user($user->id, $this->course->id, $this->studentrole->id);
120
 
121
        // Trigger and capture the event.
122
        $sink = $this->redirectEvents();
123
 
124
        $result = mod_scorm_external::view_scorm($this->scorm->id);
125
        $result = external_api::clean_returnvalue(mod_scorm_external::view_scorm_returns(), $result);
126
 
127
        $events = $sink->get_events();
128
        $this->assertCount(1, $events);
129
        $event = array_shift($events);
130
 
131
        // Checking that the event contains the expected values.
132
        $this->assertInstanceOf('\mod_scorm\event\course_module_viewed', $event);
133
        $this->assertEquals($this->context, $event->get_context());
134
        $moodleurl = new \moodle_url('/mod/scorm/view.php', array('id' => $this->cm->id));
135
        $this->assertEquals($moodleurl, $event->get_url());
136
        $this->assertEventContextNotUsed($event);
137
        $this->assertNotEmpty($event->get_name());
138
    }
139
 
140
    /**
141
     * Test get scorm attempt count
142
     */
11 efrain 143
    public function test_mod_scorm_get_scorm_attempt_count_own_empty(): void {
1 efrain 144
        // Set to the student user.
145
        self::setUser($this->student);
146
 
147
        // Retrieve my attempts (should be 0).
148
        $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
149
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
150
        $this->assertEquals(0, $result['attemptscount']);
151
    }
152
 
11 efrain 153
    public function test_mod_scorm_get_scorm_attempt_count_own_with_complete(): void {
1 efrain 154
        // Set to the student user.
155
        self::setUser($this->student);
156
 
157
        // Create attempts.
158
        $scoes = scorm_get_scoes($this->scorm->id);
159
        $sco = array_shift($scoes);
160
        scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
161
        scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
162
 
163
        $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
164
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
165
        $this->assertEquals(2, $result['attemptscount']);
166
    }
167
 
11 efrain 168
    public function test_mod_scorm_get_scorm_attempt_count_own_incomplete(): void {
1 efrain 169
        // Set to the student user.
170
        self::setUser($this->student);
171
 
172
        // Create a complete attempt, and an incomplete attempt.
173
        $scoes = scorm_get_scoes($this->scorm->id);
174
        $sco = array_shift($scoes);
175
        scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
176
        scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 2, 'cmi.core.credit', '0');
177
 
178
        $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id, true);
179
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
180
        $this->assertEquals(1, $result['attemptscount']);
181
    }
182
 
11 efrain 183
    public function test_mod_scorm_get_scorm_attempt_count_others_as_teacher(): void {
1 efrain 184
        // As a teacher.
185
        self::setUser($this->teacher);
186
 
187
        // Create a completed attempt for student.
188
        $scoes = scorm_get_scoes($this->scorm->id);
189
        $sco = array_shift($scoes);
190
        scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
191
 
192
        // I should be able to view the attempts for my students.
193
        $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
194
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
195
        $this->assertEquals(1, $result['attemptscount']);
196
    }
197
 
11 efrain 198
    public function test_mod_scorm_get_scorm_attempt_count_others_as_student(): void {
1 efrain 199
        // Create a second student.
200
        $student2 = self::getDataGenerator()->create_user();
201
        $this->getDataGenerator()->enrol_user($student2->id, $this->course->id, $this->studentrole->id, 'manual');
202
 
203
        // As a student.
204
        self::setUser($student2);
205
 
206
        // I should not be able to view the attempts of another student.
207
        $this->expectException(\required_capability_exception::class);
208
        mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
209
    }
210
 
11 efrain 211
    public function test_mod_scorm_get_scorm_attempt_count_invalid_instanceid(): void {
1 efrain 212
        // As student.
213
        self::setUser($this->student);
214
 
215
        // Test invalid instance id.
216
        $this->expectException(\moodle_exception::class);
217
        mod_scorm_external::get_scorm_attempt_count(0, $this->student->id);
218
    }
219
 
11 efrain 220
    public function test_mod_scorm_get_scorm_attempt_count_invalid_userid(): void {
1 efrain 221
        // As student.
222
        self::setUser($this->student);
223
 
224
        $this->expectException(\moodle_exception::class);
225
        mod_scorm_external::get_scorm_attempt_count($this->scorm->id, -1);
226
    }
227
 
228
    /**
229
     * Test get scorm scoes
230
     */
11 efrain 231
    public function test_mod_scorm_get_scorm_scoes(): void {
1 efrain 232
        global $DB;
233
 
234
        $this->resetAfterTest(true);
235
 
236
        // Create users.
237
        $student = self::getDataGenerator()->create_user();
238
        $teacher = self::getDataGenerator()->create_user();
239
 
240
        // Create courses to add the modules.
241
        $course = self::getDataGenerator()->create_course();
242
 
243
        // First scorm, dates restriction.
244
        $record = new \stdClass();
245
        $record->course = $course->id;
246
        $record->timeopen = time() + DAYSECS;
247
        $record->timeclose = $record->timeopen + DAYSECS;
248
        $scorm = self::getDataGenerator()->create_module('scorm', $record);
249
 
250
        // Set to the student user.
251
        self::setUser($student);
252
 
253
        // Users enrolments.
254
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
255
        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
256
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
257
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
258
 
259
        // Retrieve my scoes, warning!.
260
        try {
261
             mod_scorm_external::get_scorm_scoes($scorm->id);
262
            $this->fail('Exception expected due to invalid dates.');
263
        } catch (\moodle_exception $e) {
264
            $this->assertEquals('notopenyet', $e->errorcode);
265
        }
266
 
267
        $scorm->timeopen = time() - DAYSECS;
268
        $scorm->timeclose = time() - HOURSECS;
269
        $DB->update_record('scorm', $scorm);
270
 
271
        try {
272
             mod_scorm_external::get_scorm_scoes($scorm->id);
273
            $this->fail('Exception expected due to invalid dates.');
274
        } catch (\moodle_exception $e) {
275
            $this->assertEquals('expired', $e->errorcode);
276
        }
277
 
278
        // Retrieve my scoes, user with permission.
279
        self::setUser($teacher);
280
        $result = mod_scorm_external::get_scorm_scoes($scorm->id);
281
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
282
        $this->assertCount(2, $result['scoes']);
283
        $this->assertCount(0, $result['warnings']);
284
 
285
        $scoes = scorm_get_scoes($scorm->id);
286
        $sco = array_shift($scoes);
287
        $sco->extradata = array();
288
        $this->assertEquals((array) $sco, $result['scoes'][0]);
289
 
290
        $sco = array_shift($scoes);
291
        $sco->extradata = array();
292
        $sco->extradata[] = array(
293
            'element' => 'isvisible',
294
            'value' => $sco->isvisible
295
        );
296
        $sco->extradata[] = array(
297
            'element' => 'parameters',
298
            'value' => $sco->parameters
299
        );
300
        unset($sco->isvisible);
301
        unset($sco->parameters);
302
 
303
        // Sort the array (if we don't sort tests will fails for Postgres).
304
        usort($result['scoes'][1]['extradata'], function($a, $b) {
305
            return strcmp($a['element'], $b['element']);
306
        });
307
 
308
        $this->assertEquals((array) $sco, $result['scoes'][1]);
309
 
310
        // Use organization.
311
        $organization = 'golf_sample_default_org';
312
        $result = mod_scorm_external::get_scorm_scoes($scorm->id, $organization);
313
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
314
        $this->assertCount(1, $result['scoes']);
315
        $this->assertEquals($organization, $result['scoes'][0]['organization']);
316
        $this->assertCount(0, $result['warnings']);
317
 
318
        // Test invalid instance id.
319
        try {
320
             mod_scorm_external::get_scorm_scoes(0);
321
            $this->fail('Exception expected due to invalid instance id.');
322
        } catch (\moodle_exception $e) {
323
            $this->assertEquals('invalidrecord', $e->errorcode);
324
        }
325
 
326
    }
327
 
328
    /**
329
     * Test get scorm scoes (with a complex SCORM package)
330
     */
11 efrain 331
    public function test_mod_scorm_get_scorm_scoes_complex_package(): void {
1 efrain 332
        global $CFG;
333
 
334
        // As student.
335
        self::setUser($this->student);
336
 
337
        $record = new \stdClass();
338
        $record->course = $this->course->id;
339
        $record->packagefilepath = $CFG->dirroot.'/mod/scorm/tests/packages/complexscorm.zip';
340
        $scorm = self::getDataGenerator()->create_module('scorm', $record);
341
 
342
        $result = mod_scorm_external::get_scorm_scoes($scorm->id);
343
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
344
        $this->assertCount(9, $result['scoes']);
345
        $this->assertCount(0, $result['warnings']);
346
 
347
        $expectedscoes = array();
348
        $scoreturnstructure = mod_scorm_external::get_scorm_scoes_returns();
349
        $scoes = scorm_get_scoes($scorm->id);
350
        foreach ($scoes as $sco) {
351
            $sco->extradata = array();
352
            foreach ($sco as $element => $value) {
353
                // Add the extra data to the extradata array and remove the object element.
354
                if (!isset($scoreturnstructure->keys['scoes']->content->keys[$element])) {
355
                    $sco->extradata[] = array(
356
                        'element' => $element,
357
                        'value' => $value
358
                    );
359
                    unset($sco->{$element});
360
                }
361
            }
362
            $expectedscoes[] = (array) $sco;
363
        }
364
 
365
        $this->assertEquals($expectedscoes, $result['scoes']);
366
    }
367
 
368
    /*
369
     * Test get scorm user data
370
     */
11 efrain 371
    public function test_mod_scorm_get_scorm_user_data(): void {
1 efrain 372
        global $DB;
373
 
374
        $this->resetAfterTest(true);
375
 
376
        // Create users.
377
        $student1 = self::getDataGenerator()->create_user();
378
        $teacher = self::getDataGenerator()->create_user();
379
 
380
        // Set to the student user.
381
        self::setUser($student1);
382
 
383
        // Create courses to add the modules.
384
        $course = self::getDataGenerator()->create_course();
385
 
386
        // First scorm.
387
        $record = new \stdClass();
388
        $record->course = $course->id;
389
        $scorm = self::getDataGenerator()->create_module('scorm', $record);
390
 
391
        // Users enrolments.
392
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
393
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
394
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id, 'manual');
395
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
396
 
397
        // Create attempts.
398
        $scoes = scorm_get_scoes($scorm->id);
399
        $sco = array_shift($scoes);
400
        scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
401
        scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80');
402
        scorm_insert_track($student1->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
403
 
404
        $result = mod_scorm_external::get_scorm_user_data($scorm->id, 1);
405
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_user_data_returns(), $result);
406
        $this->assertCount(2, $result['data']);
407
        // Find our tracking data.
408
        $found = 0;
409
        foreach ($result['data'] as $scodata) {
410
            foreach ($scodata['userdata'] as $userdata) {
411
                if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
412
                    $found++;
413
                }
414
                if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
415
                    $found++;
416
                }
417
            }
418
        }
419
        $this->assertEquals(2, $found);
420
 
421
        // Test invalid instance id.
422
        try {
423
             mod_scorm_external::get_scorm_user_data(0, 1);
424
            $this->fail('Exception expected due to invalid instance id.');
425
        } catch (\moodle_exception $e) {
426
            $this->assertEquals('invalidrecord', $e->errorcode);
427
        }
428
    }
429
 
430
    /**
431
     * Test insert scorm tracks
432
     */
11 efrain 433
    public function test_mod_scorm_insert_scorm_tracks(): void {
1 efrain 434
        global $DB;
435
 
436
        $this->resetAfterTest(true);
437
 
438
        // Create users.
439
        $student = self::getDataGenerator()->create_user();
440
 
441
        // Create courses to add the modules.
442
        $course = self::getDataGenerator()->create_course();
443
 
444
        // First scorm, dates restriction.
445
        $record = new \stdClass();
446
        $record->course = $course->id;
447
        $record->timeopen = time() + DAYSECS;
448
        $record->timeclose = $record->timeopen + DAYSECS;
449
        $scorm = self::getDataGenerator()->create_module('scorm', $record);
450
 
451
        // Get a SCO.
452
        $scoes = scorm_get_scoes($scorm->id);
453
        $sco = array_shift($scoes);
454
 
455
        // Tracks.
456
        $tracks = array();
457
        $tracks[] = array(
458
            'element' => 'cmi.core.lesson_status',
459
            'value' => 'completed'
460
        );
461
        $tracks[] = array(
462
            'element' => 'cmi.core.score.raw',
463
            'value' => '80'
464
        );
465
 
466
        // Set to the student user.
467
        self::setUser($student);
468
 
469
        // Users enrolments.
470
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
471
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
472
 
473
        // Exceptions first.
474
        try {
475
            mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
476
            $this->fail('Exception expected due to dates');
477
        } catch (\moodle_exception $e) {
478
            $this->assertEquals('notopenyet', $e->errorcode);
479
        }
480
 
481
        $scorm->timeopen = time() - DAYSECS;
482
        $scorm->timeclose = time() - HOURSECS;
483
        $DB->update_record('scorm', $scorm);
484
 
485
        try {
486
            mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
487
            $this->fail('Exception expected due to dates');
488
        } catch (\moodle_exception $e) {
489
            $this->assertEquals('expired', $e->errorcode);
490
        }
491
 
492
        // Test invalid instance id.
493
        try {
494
             mod_scorm_external::insert_scorm_tracks(0, 1, $tracks);
495
            $this->fail('Exception expected due to invalid sco id.');
496
        } catch (\moodle_exception $e) {
497
            $this->assertEquals('cannotfindsco', $e->errorcode);
498
        }
499
 
500
        $scorm->timeopen = 0;
501
        $scorm->timeclose = 0;
502
        $DB->update_record('scorm', $scorm);
503
 
504
        // Retrieve my tracks.
505
        $result = mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
506
        $result = external_api::clean_returnvalue(mod_scorm_external::insert_scorm_tracks_returns(), $result);
507
        $this->assertCount(0, $result['warnings']);
508
        $sql = "SELECT v.id
509
                  FROM {scorm_scoes_value} v
510
                  JOIN {scorm_attempt} a ON a.id = v.attemptid
511
                  WHERE a.userid = :userid AND a.attempt = :attempt AND a.scormid = :scormid AND v.scoid = :scoid";
512
        $params = ['userid' => $student->id, 'scoid' => $sco->id, 'scormid' => $scorm->id, 'attempt' => 1];
513
        $trackids = $DB->get_records_sql($sql, $params);
514
        // We use asort here to prevent problems with ids ordering.
515
        $expectedkeys = array_keys($trackids);
516
        $this->assertEquals(asort($expectedkeys), asort($result['trackids']));
517
    }
518
 
519
    /**
520
     * Test get scorm sco tracks
521
     */
11 efrain 522
    public function test_mod_scorm_get_scorm_sco_tracks(): void {
1 efrain 523
        global $DB;
524
 
525
        $this->resetAfterTest(true);
526
 
527
        // Create users.
528
        $student = self::getDataGenerator()->create_user();
529
        $otherstudent = self::getDataGenerator()->create_user();
530
        $teacher = self::getDataGenerator()->create_user();
531
 
532
        // Set to the student user.
533
        self::setUser($student);
534
 
535
        // Create courses to add the modules.
536
        $course = self::getDataGenerator()->create_course();
537
 
538
        // First scorm.
539
        $record = new \stdClass();
540
        $record->course = $course->id;
541
        $scorm = self::getDataGenerator()->create_module('scorm', $record);
542
 
543
        // Users enrolments.
544
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
545
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
546
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
547
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
548
 
549
        // Create attempts.
550
        $scoes = scorm_get_scoes($scorm->id);
551
        $sco = array_shift($scoes);
552
        scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
553
        scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80');
554
        scorm_insert_track($student->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
555
 
556
        $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 1);
557
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
558
        // 7 default elements + 2 custom ones.
559
        $this->assertCount(9, $result['data']['tracks']);
560
        $this->assertEquals(1, $result['data']['attempt']);
561
        $this->assertCount(0, $result['warnings']);
562
        // Find our tracking data.
563
        $found = 0;
564
        foreach ($result['data']['tracks'] as $userdata) {
565
            if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
566
                $found++;
567
            }
568
            if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
569
                $found++;
570
            }
571
        }
572
        $this->assertEquals(2, $found);
573
 
574
        // Try invalid attempt.
575
        $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 10);
576
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
577
        $this->assertCount(0, $result['data']['tracks']);
578
        $this->assertEquals(10, $result['data']['attempt']);
579
        $this->assertCount(1, $result['warnings']);
580
        $this->assertEquals('notattempted', $result['warnings'][0]['warningcode']);
581
 
582
        // Capabilities check.
583
        try {
584
             mod_scorm_external::get_scorm_sco_tracks($sco->id, $otherstudent->id);
585
            $this->fail('Exception expected due to invalid instance id.');
586
        } catch (\required_capability_exception $e) {
587
            $this->assertEquals('nopermissions', $e->errorcode);
588
        }
589
 
590
        self::setUser($teacher);
591
        // Ommit the attempt parameter, the function should calculate the last attempt.
592
        $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id);
593
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
594
        // 7 default elements + 1 custom one.
595
        $this->assertCount(8, $result['data']['tracks']);
596
        $this->assertEquals(2, $result['data']['attempt']);
597
 
598
        // Test invalid instance id.
599
        try {
600
             mod_scorm_external::get_scorm_sco_tracks(0, 1);
601
            $this->fail('Exception expected due to invalid instance id.');
602
        } catch (\moodle_exception $e) {
603
            $this->assertEquals('cannotfindsco', $e->errorcode);
604
        }
605
        // Invalid user.
606
        try {
607
             mod_scorm_external::get_scorm_sco_tracks($sco->id, 0);
608
            $this->fail('Exception expected due to invalid instance id.');
609
        } catch (\moodle_exception $e) {
610
            $this->assertEquals('invaliduser', $e->errorcode);
611
        }
612
    }
613
 
614
    /*
615
     * Test get scorms by courses
616
     */
11 efrain 617
    public function test_mod_scorm_get_scorms_by_courses(): void {
1 efrain 618
        global $DB;
619
 
620
        $this->resetAfterTest(true);
621
 
622
        // Create users.
623
        $student = self::getDataGenerator()->create_user();
624
        $teacher = self::getDataGenerator()->create_user();
625
 
626
        // Set to the student user.
627
        self::setUser($student);
628
 
629
        // Create courses to add the modules.
630
        $course1 = self::getDataGenerator()->create_course();
631
        $course2 = self::getDataGenerator()->create_course();
632
 
633
        // First scorm.
634
        $record = new \stdClass();
635
        $record->introformat = FORMAT_HTML;
636
        $record->course = $course1->id;
637
        $record->hidetoc = 2;
638
        $record->displayattemptstatus = 2;
639
        $record->skipview = 2;
640
        $scorm1 = self::getDataGenerator()->create_module('scorm', $record);
641
 
642
        // Second scorm.
643
        $record = new \stdClass();
644
        $record->introformat = FORMAT_HTML;
645
        $record->course = $course2->id;
646
        $scorm2 = self::getDataGenerator()->create_module('scorm', $record);
647
 
648
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
649
        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
650
 
651
        // Users enrolments.
652
        $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
653
        $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id, 'manual');
654
 
655
        // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
656
        $enrol = enrol_get_plugin('manual');
657
        $enrolinstances = enrol_get_instances($course2->id, true);
658
        foreach ($enrolinstances as $courseenrolinstance) {
659
            if ($courseenrolinstance->enrol == "manual") {
660
                $instance2 = $courseenrolinstance;
661
                break;
662
            }
663
        }
664
        $enrol->enrol_user($instance2, $student->id, $studentrole->id);
665
 
666
        $returndescription = mod_scorm_external::get_scorms_by_courses_returns();
667
 
668
        // Test open/close dates.
669
 
670
        $timenow = time();
671
        $scorm1->timeopen = $timenow - DAYSECS;
672
        $scorm1->timeclose = $timenow - HOURSECS;
673
        $DB->update_record('scorm', $scorm1);
674
 
675
        $result = mod_scorm_external::get_scorms_by_courses(array($course1->id));
676
        $result = external_api::clean_returnvalue($returndescription, $result);
677
 
678
        // Test default SCORM settings.
679
        $this->assertCount(1, $result['options']);
680
        $this->assertEquals('scormstandard', $result['options'][0]['name']);
681
        $this->assertEquals(0, $result['options'][0]['value']);
682
 
683
        $this->assertCount(1, $result['warnings']);
684
        // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'.
685
        $this->assertCount(8, $result['scorms'][0]);
686
        $this->assertEquals('expired', $result['warnings'][0]['warningcode']);
687
 
688
        $scorm1->timeopen = $timenow + DAYSECS;
689
        $scorm1->timeclose = $scorm1->timeopen + DAYSECS;
690
        $DB->update_record('scorm', $scorm1);
691
 
692
        // Set the SCORM config values.
693
        set_config('scormstandard', 1, 'scorm');
694
 
695
        $result = mod_scorm_external::get_scorms_by_courses(array($course1->id));
696
        $result = external_api::clean_returnvalue($returndescription, $result);
697
 
698
        // Test SCORM settings.
699
        $this->assertCount(1, $result['options']);
700
        $this->assertEquals('scormstandard', $result['options'][0]['name']);
701
        $this->assertEquals(1, $result['options'][0]['value']);
702
 
703
        $this->assertCount(1, $result['warnings']);
704
        // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'.
705
        $this->assertCount(8, $result['scorms'][0]);
706
        $this->assertEquals('notopenyet', $result['warnings'][0]['warningcode']);
707
 
708
        // Reset times.
709
        $scorm1->timeopen = 0;
710
        $scorm1->timeclose = 0;
711
        $DB->update_record('scorm', $scorm1);
712
 
713
        // Create what we expect to be returned when querying the two courses.
714
        // First for the student user.
715
        $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'lang', 'version', 'maxgrade',
716
                                'grademethod', 'whatgrade', 'maxattempt', 'forcecompleted', 'forcenewattempt', 'lastattemptlock',
717
                                'displayattemptstatus', 'displaycoursestructure', 'sha1hash', 'md5hash', 'revision', 'launch',
718
                                'skipview', 'hidebrowse', 'hidetoc', 'nav', 'navpositionleft', 'navpositiontop', 'auto',
719
                                'popup', 'width', 'height', 'timeopen', 'timeclose', 'packagesize',
720
                                'packageurl', 'scormtype', 'reference');
721
 
722
        // Add expected coursemodule and data.
723
        $scorm1->coursemodule = $scorm1->cmid;
724
        $scorm1->section = 0;
725
        $scorm1->visible = true;
726
        $scorm1->groupmode = 0;
727
        $scorm1->groupingid = 0;
728
        $scorm1->lang = '';
729
 
730
        $scorm2->coursemodule = $scorm2->cmid;
731
        $scorm2->section = 0;
732
        $scorm2->visible = true;
733
        $scorm2->groupmode = 0;
734
        $scorm2->groupingid = 0;
735
        $scorm2->lang = '';
736
 
737
        // SCORM size. The same package is used in both SCORMs.
738
        $scormcontext1 = \context_module::instance($scorm1->cmid);
739
        $scormcontext2 = \context_module::instance($scorm2->cmid);
740
        $fs = get_file_storage();
741
        $packagefile = $fs->get_file($scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference);
742
        $packagesize = $packagefile->get_filesize();
743
 
744
        $packageurl1 = \moodle_url::make_webservice_pluginfile_url(
745
                            $scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference)->out(false);
746
        $packageurl2 = \moodle_url::make_webservice_pluginfile_url(
747
                            $scormcontext2->id, 'mod_scorm', 'package', 0, '/', $scorm2->reference)->out(false);
748
 
749
        $scorm1->packagesize = $packagesize;
750
        $scorm1->packageurl = $packageurl1;
751
        $scorm2->packagesize = $packagesize;
752
        $scorm2->packageurl = $packageurl2;
753
 
754
        // Forced to boolean as it is returned as PARAM_BOOL.
755
        $protectpackages = (bool)get_config('scorm', 'protectpackagedownloads');
756
        $expected1 = array('protectpackagedownloads' => $protectpackages);
757
        $expected2 = array('protectpackagedownloads' => $protectpackages);
758
        foreach ($expectedfields as $field) {
759
 
760
            // Since we return the fields used as boolean as PARAM_BOOL instead PARAM_INT we need to force casting here.
761
            // From the returned fields definition we obtain the type expected for the field.
762
            if (empty($returndescription->keys['scorms']->content->keys[$field]->type)) {
763
                continue;
764
            }
765
            $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type;
766
            if ($fieldtype == PARAM_BOOL) {
767
                $expected1[$field] = (bool) $scorm1->{$field};
768
                $expected2[$field] = (bool) $scorm2->{$field};
769
            } else {
770
                $expected1[$field] = $scorm1->{$field};
771
                $expected2[$field] = $scorm2->{$field};
772
            }
773
        }
774
        $expected1['introfiles'] = [];
775
        $expected2['introfiles'] = [];
776
 
777
        $expectedscorms = array();
778
        $expectedscorms[] = $expected2;
779
        $expectedscorms[] = $expected1;
780
 
781
        // Call the external function passing course ids.
782
        $result = mod_scorm_external::get_scorms_by_courses(array($course2->id, $course1->id));
783
        $result = external_api::clean_returnvalue($returndescription, $result);
784
        $this->assertEquals($expectedscorms, $result['scorms']);
785
 
786
        // Call the external function without passing course id.
787
        $result = mod_scorm_external::get_scorms_by_courses();
788
        $result = external_api::clean_returnvalue($returndescription, $result);
789
        $this->assertEquals($expectedscorms, $result['scorms']);
790
 
791
        // Unenrol user from second course and alter expected scorms.
792
        $enrol->unenrol_user($instance2, $student->id);
793
        array_shift($expectedscorms);
794
 
795
        // Call the external function without passing course id.
796
        $result = mod_scorm_external::get_scorms_by_courses();
797
        $result = external_api::clean_returnvalue($returndescription, $result);
798
        $this->assertEquals($expectedscorms, $result['scorms']);
799
 
800
        // Call for the second course we unenrolled the user from, expected warning.
801
        $result = mod_scorm_external::get_scorms_by_courses(array($course2->id));
802
        $this->assertCount(1, $result['options']);
803
        $this->assertCount(1, $result['warnings']);
804
        $this->assertEquals('1', $result['warnings'][0]['warningcode']);
805
        $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
806
 
807
        // Now, try as a teacher for getting all the additional fields.
808
        self::setUser($teacher);
809
 
810
        $additionalfields = array('updatefreq', 'timemodified', 'options',
811
                                    'completionstatusrequired', 'completionscorerequired', 'completionstatusallscos',
812
                                    'autocommit', 'section', 'visible', 'groupmode', 'groupingid');
813
 
814
        foreach ($additionalfields as $field) {
815
            $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type;
816
 
817
            if ($fieldtype == PARAM_BOOL) {
818
                $expectedscorms[0][$field] = (bool) $scorm1->{$field};
819
            } else {
820
                $expectedscorms[0][$field] = $scorm1->{$field};
821
            }
822
        }
823
 
824
        $result = mod_scorm_external::get_scorms_by_courses();
825
        $result = external_api::clean_returnvalue($returndescription, $result);
826
        $this->assertEquals($expectedscorms, $result['scorms']);
827
 
828
        // Even with the SCORM closed in time teacher should retrieve the info.
829
        $scorm1->timeopen = $timenow - DAYSECS;
830
        $scorm1->timeclose = $timenow - HOURSECS;
831
        $DB->update_record('scorm', $scorm1);
832
 
833
        $expectedscorms[0]['timeopen'] = $scorm1->timeopen;
834
        $expectedscorms[0]['timeclose'] = $scorm1->timeclose;
835
 
836
        $result = mod_scorm_external::get_scorms_by_courses();
837
        $result = external_api::clean_returnvalue($returndescription, $result);
838
        $this->assertEquals($expectedscorms, $result['scorms']);
839
 
840
        // Admin also should get all the information.
841
        self::setAdminUser();
842
 
843
        $result = mod_scorm_external::get_scorms_by_courses(array($course1->id));
844
        $result = external_api::clean_returnvalue($returndescription, $result);
845
        $this->assertEquals($expectedscorms, $result['scorms']);
846
    }
847
 
848
    /**
849
     * Test launch_sco
850
     */
11 efrain 851
    public function test_launch_sco(): void {
1 efrain 852
        global $DB;
853
 
854
        // Test invalid instance id.
855
        try {
856
            mod_scorm_external::launch_sco(0);
857
            $this->fail('Exception expected due to invalid mod_scorm instance id.');
858
        } catch (\moodle_exception $e) {
859
            $this->assertEquals('invalidrecord', $e->errorcode);
860
        }
861
 
862
        // Test not-enrolled user.
863
        $user = self::getDataGenerator()->create_user();
864
        $this->setUser($user);
865
        try {
866
            mod_scorm_external::launch_sco($this->scorm->id);
867
            $this->fail('Exception expected due to not enrolled user.');
868
        } catch (\moodle_exception $e) {
869
            $this->assertEquals('requireloginerror', $e->errorcode);
870
        }
871
 
872
        // Test user with full capabilities.
873
        $this->setUser($this->student);
874
 
875
        // Trigger and capture the event.
876
        $sink = $this->redirectEvents();
877
 
878
        $scoes = scorm_get_scoes($this->scorm->id);
879
        foreach ($scoes as $sco) {
880
            // Find launchable SCO.
881
            if ($sco->launch != '') {
882
                break;
883
            }
884
        }
885
 
886
        $result = mod_scorm_external::launch_sco($this->scorm->id, $sco->id);
887
        $result = external_api::clean_returnvalue(mod_scorm_external::launch_sco_returns(), $result);
888
 
889
        $events = $sink->get_events();
890
        $this->assertCount(3, $events);
891
        $event = array_pop($events);
892
 
893
        // Checking that the event contains the expected values.
894
        $this->assertInstanceOf('\mod_scorm\event\sco_launched', $event);
895
        $this->assertEquals($this->context, $event->get_context());
896
        $moodleurl = new \moodle_url('/mod/scorm/player.php', array('cm' => $this->cm->id, 'scoid' => $sco->id));
897
        $this->assertEquals($moodleurl, $event->get_url());
898
        $this->assertEventContextNotUsed($event);
899
        $this->assertNotEmpty($event->get_name());
900
 
901
        $event = array_shift($events);
902
        $this->assertInstanceOf('\core\event\course_module_completion_updated', $event);
903
 
904
        // Check completion status.
905
        $completion = new \completion_info($this->course);
906
        $completiondata = $completion->get_data($this->cm);
907
        $this->assertEquals(COMPLETION_VIEWED, $completiondata->completionstate);
908
 
909
        // Invalid SCO.
910
        try {
911
            mod_scorm_external::launch_sco($this->scorm->id, -1);
912
            $this->fail('Exception expected due to invalid SCO id.');
913
        } catch (\moodle_exception $e) {
914
            $this->assertEquals('cannotfindsco', $e->errorcode);
915
        }
916
    }
917
 
918
    /**
919
     * Test mod_scorm_get_scorm_access_information.
920
     */
11 efrain 921
    public function test_mod_scorm_get_scorm_access_information(): void {
1 efrain 922
        global $DB;
923
 
924
        $this->resetAfterTest(true);
925
 
926
        $student = self::getDataGenerator()->create_user();
927
        $course = self::getDataGenerator()->create_course();
928
        // Create the scorm.
929
        $record = new \stdClass();
930
        $record->course = $course->id;
931
        $scorm = self::getDataGenerator()->create_module('scorm', $record);
932
        $context = \context_module::instance($scorm->cmid);
933
 
934
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
935
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
936
 
937
        self::setUser($student);
938
        $result = mod_scorm_external::get_scorm_access_information($scorm->id);
939
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_access_information_returns(), $result);
940
 
941
        // Check default values for capabilities.
942
        $enabledcaps = array('canskipview', 'cansavetrack', 'canviewscores');
943
 
944
        unset($result['warnings']);
945
        foreach ($result as $capname => $capvalue) {
946
            if (in_array($capname, $enabledcaps)) {
947
                $this->assertTrue($capvalue);
948
            } else {
949
                $this->assertFalse($capvalue);
950
            }
951
        }
952
        // Now, unassign one capability.
953
        unassign_capability('mod/scorm:viewscores', $studentrole->id);
954
        array_pop($enabledcaps);
955
        accesslib_clear_all_caches_for_unit_testing();
956
 
957
        $result = mod_scorm_external::get_scorm_access_information($scorm->id);
958
        $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_access_information_returns(), $result);
959
        unset($result['warnings']);
960
        foreach ($result as $capname => $capvalue) {
961
            if (in_array($capname, $enabledcaps)) {
962
                $this->assertTrue($capvalue);
963
            } else {
964
                $this->assertFalse($capvalue);
965
            }
966
        }
967
    }
968
}