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