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_h5pactivity\xapi;
18
 
19
use \core_xapi\local\statement;
20
use \core_xapi\local\statement\item_agent;
21
use \core_xapi\local\statement\item_activity;
22
use \core_xapi\local\statement\item_definition;
23
use \core_xapi\local\statement\item_verb;
24
use \core_xapi\local\statement\item_result;
25
use context_module;
26
use core_xapi\test_helper;
27
use stdClass;
28
 
29
/**
30
 * Attempt tests class for mod_h5pactivity.
31
 *
32
 * @package    mod_h5pactivity
33
 * @category   test
34
 * @copyright  2020 Ferran Recio <ferran@moodle.com>
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 * @covers     \mod_h5pactivity\xapi\handler
37
 */
11 efrain 38
final class handler_test extends \advanced_testcase {
1 efrain 39
 
40
    /**
41
     * Setup to ensure that fixtures are loaded.
42
     */
43
    public static function setUpBeforeClass(): void {
44
        global $CFG;
45
        require_once($CFG->dirroot.'/lib/xapi/tests/helper.php');
1441 ariadna 46
        parent::setUpBeforeClass();
1 efrain 47
    }
48
 
49
    /**
50
     * Generate a valid scenario for each tests.
51
     *
52
     * @return stdClass an object with all scenario data in it
53
     */
54
    private function generate_testing_scenario(): stdClass {
55
 
56
        $this->resetAfterTest();
57
        $this->setAdminUser();
58
 
59
        $data = new stdClass();
60
 
61
        $data->course = $this->getDataGenerator()->create_course();
62
 
63
        // Generate 2 users, one enroled into course and one not.
64
        $data->student = $this->getDataGenerator()->create_and_enrol($data->course, 'student');
65
        $data->otheruser = $this->getDataGenerator()->create_user();
66
 
67
        // H5P activity.
68
        $data->activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $data->course]);
69
        $data->context = context_module::instance($data->activity->cmid);
70
 
71
        $data->xapihandler = handler::create('mod_h5pactivity');
72
        $this->assertNotEmpty($data->xapihandler);
73
        $this->assertInstanceOf('\mod_h5pactivity\xapi\handler', $data->xapihandler);
74
 
75
        $this->setUser($data->student);
76
 
77
        return $data;
78
    }
79
 
80
    /**
81
     * Test for xapi_handler with valid statements.
82
     */
11 efrain 83
    public function test_xapi_handler(): void {
1 efrain 84
        global $DB;
85
 
86
        $data = $this->generate_testing_scenario();
87
        $xapihandler = $data->xapihandler;
88
        $context = $data->context;
89
        $student = $data->student;
90
        $otheruser = $data->otheruser;
91
 
92
        // Check we have 0 entries in the attempts tables.
93
        $count = $DB->count_records('h5pactivity_attempts');
94
        $this->assertEquals(0, $count);
95
        $count = $DB->count_records('h5pactivity_attempts_results');
96
        $this->assertEquals(0, $count);
97
 
98
        $statements = $this->generate_statements($context, $student);
99
 
100
        // Insert first statement.
101
        $event = $xapihandler->statement_to_event($statements[0]);
102
        $this->assertNotNull($event);
103
        $count = $DB->count_records('h5pactivity_attempts');
104
        $this->assertEquals(1, $count);
105
        $count = $DB->count_records('h5pactivity_attempts_results');
106
        $this->assertEquals(1, $count);
107
 
108
        // Insert second statement.
109
        $event = $xapihandler->statement_to_event($statements[1]);
110
        $this->assertNotNull($event);
111
        $count = $DB->count_records('h5pactivity_attempts');
112
        $this->assertEquals(1, $count);
113
        $count = $DB->count_records('h5pactivity_attempts_results');
114
        $this->assertEquals(2, $count);
115
 
116
        // Insert again first statement.
117
        $event = $xapihandler->statement_to_event($statements[0]);
118
        $this->assertNotNull($event);
119
        $count = $DB->count_records('h5pactivity_attempts');
120
        $this->assertEquals(2, $count);
121
        $count = $DB->count_records('h5pactivity_attempts_results');
122
        $this->assertEquals(3, $count);
123
 
124
        // Insert again second statement.
125
        $event = $xapihandler->statement_to_event($statements[1]);
126
        $this->assertNotNull($event);
127
        $count = $DB->count_records('h5pactivity_attempts');
128
        $this->assertEquals(2, $count);
129
        $count = $DB->count_records('h5pactivity_attempts_results');
130
        $this->assertEquals(4, $count);
131
    }
132
 
133
    /**
134
     * Testing wrong statements scenarios.
135
     *
136
     * @dataProvider xapi_handler_errors_data
137
     * @param bool $hasverb valid verb
138
     * @param bool $hasdefinition generate definition
139
     * @param bool $hasresult generate result
140
     * @param bool $hascontext valid context
141
     * @param bool $hasuser valid user
142
     * @param bool $generateattempt if generates an empty attempt
143
     */
144
    public function test_xapi_handler_errors(bool $hasverb, bool $hasdefinition, bool $hasresult,
11 efrain 145
            bool $hascontext, bool $hasuser, bool $generateattempt): void {
1 efrain 146
        global $DB, $CFG;
147
 
148
        $data = $this->generate_testing_scenario();
149
        $xapihandler = $data->xapihandler;
150
        $context = $data->context;
151
        $student = $data->student;
152
        $otheruser = $data->otheruser;
153
 
154
        // Check we have 0 entries in the attempts tables.
155
        $count = $DB->count_records('h5pactivity_attempts');
156
        $this->assertEquals(0, $count);
157
        $count = $DB->count_records('h5pactivity_attempts_results');
158
        $this->assertEquals(0, $count);
159
 
160
        $statement = new statement();
161
        if ($hasverb) {
162
            $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed'));
163
        } else {
164
            $statement->set_verb(item_verb::create_from_id('cook'));
165
        }
166
        $definition = null;
167
        if ($hasdefinition) {
168
            $definition = item_definition::create_from_data((object)[
169
                'interactionType' => 'compound',
170
                'correctResponsesPattern' => '1',
171
            ]);
172
        }
173
        if ($hascontext) {
174
            $statement->set_object(item_activity::create_from_id($context->id, $definition));
175
        } else {
176
            $statement->set_object(item_activity::create_from_id('paella', $definition));
177
        }
178
        if ($hasresult) {
179
            $statement->set_result(item_result::create_from_data((object)[
180
                'completion' => true,
181
                'success' => true,
182
                'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1],
183
            ]));
184
        }
185
        if ($hasuser) {
186
            $statement->set_actor(item_agent::create_from_user($student));
187
        } else {
188
            $statement->set_actor(item_agent::create_from_user($otheruser));
189
        }
190
 
191
        $event = $xapihandler->statement_to_event($statement);
192
        $this->assertNull($event);
193
        // No enties should be generated.
194
        $count = $DB->count_records('h5pactivity_attempts');
195
        $attempts = ($generateattempt) ? 1 : 0;
196
        $this->assertEquals($attempts, $count);
197
        $count = $DB->count_records('h5pactivity_attempts_results');
198
        $this->assertEquals(0, $count);
199
    }
200
 
201
    /**
202
     * Data provider for data request creation tests.
203
     *
204
     * @return array
205
     */
11 efrain 206
    public static function xapi_handler_errors_data(): array {
1 efrain 207
        return [
208
            // Invalid Definitions and results possibilities.
209
            'Invalid definition and result' => [
210
                true, false, false, true, true, false
211
            ],
212
            'Invalid result' => [
213
                true, true, false, true, true, false
214
            ],
215
            'Invalid definition (generate empty attempt)' => [
216
                true, false, true, true, true, true
217
            ],
218
            // Invalid verb possibilities.
219
            'Invalid verb, definition and result' => [
220
                false, false, false, true, true, false
221
            ],
222
            'Invalid verb and result' => [
223
                false, true, false, true, true, false
224
            ],
11 efrain 225
            'Invalid verb and definition' => [
1 efrain 226
                false, false, true, true, true, false
227
            ],
228
            // Invalid context possibilities.
229
            'Invalid definition, result and context' => [
230
                true, false, false, false, true, false
231
            ],
11 efrain 232
            'Invalid result and context' => [
1 efrain 233
                true, true, false, false, true, false
234
            ],
11 efrain 235
            'Invalid definition and context' => [
1 efrain 236
                true, false, true, false, true, false
237
            ],
238
            'Invalid verb, definition result and context' => [
239
                false, false, false, false, true, false
240
            ],
241
            'Invalid verb, result and context' => [
242
                false, true, false, false, true, false
243
            ],
11 efrain 244
            'Invalid verb, definition and context' => [
1 efrain 245
                false, false, true, false, true, false
246
            ],
247
            // Invalid user possibilities.
248
            'Invalid definition, result and user' => [
249
                true, false, false, true, false, false
250
            ],
251
            'Invalid result and user' => [
252
                true, true, false, true, false, false
253
            ],
254
            'Invalid definition and user' => [
255
                true, false, true, true, false, false
256
            ],
257
            'Invalid verb, definition, result and user' => [
258
                false, false, false, true, false, false
259
            ],
260
            'Invalid verb, result and user' => [
261
                false, true, false, true, false, false
262
            ],
11 efrain 263
            'Invalid verb, definition and user' => [
1 efrain 264
                false, false, true, true, false, false
265
            ],
266
            'Invalid definition, result, context and user' => [
267
                true, false, false, false, false, false
268
            ],
269
            'Invalid result, context and user' => [
270
                true, true, false, false, false, false
271
            ],
272
            'Invalid definition, context and user' => [
273
                true, false, true, false, false, false
274
            ],
275
            'Invalid verb, definition, result, context and user' => [
276
                false, false, false, false, false, false
277
            ],
278
            'Invalid verb, result, context and user' => [
279
                false, true, false, false, false, false
280
            ],
11 efrain 281
            'Invalid verb, definition, context and user' => [
1 efrain 282
                false, false, true, false, false, false
283
            ],
284
        ];
285
    }
286
 
287
    /**
288
     * Test xapi_handler stored statements.
289
     */
11 efrain 290
    public function test_stored_statements(): void {
1 efrain 291
        global $DB;
292
 
293
        $data = $this->generate_testing_scenario();
294
        $xapihandler = $data->xapihandler;
295
        $context = $data->context;
296
        $student = $data->student;
297
        $otheruser = $data->otheruser;
298
        $activity = $data->activity;
299
 
300
        // Check we have 0 entries in the attempts tables.
301
        $count = $DB->count_records('h5pactivity_attempts');
302
        $this->assertEquals(0, $count);
303
        $count = $DB->count_records('h5pactivity_attempts_results');
304
        $this->assertEquals(0, $count);
305
 
306
        $statements = $this->generate_statements($context, $student);
307
 
308
        // Insert statements.
309
        $stored = $xapihandler->process_statements($statements);
310
        $this->assertCount(2, $stored);
311
        $this->assertEquals(true, $stored[0]);
312
        $this->assertEquals(true, $stored[1]);
313
        $count = $DB->count_records('h5pactivity_attempts');
314
        $this->assertEquals(1, $count);
315
        $count = $DB->count_records('h5pactivity_attempts_results');
316
        $this->assertEquals(2, $count);
317
 
318
        // Validate stored data.
319
        $attempts = $DB->get_records('h5pactivity_attempts');
320
        $attempt = array_shift($attempts);
321
        $statement = $statements[0];
322
        $data = $statement->get_result()->get_data();
323
        $this->assertEquals(1, $attempt->attempt);
324
        $this->assertEquals($student->id, $attempt->userid);
325
        $this->assertEquals($activity->id, $attempt->h5pactivityid);
326
        $this->assertEquals($data->score->raw, $attempt->rawscore);
327
        $this->assertEquals($data->score->max, $attempt->maxscore);
328
        $this->assertEquals($statement->get_result()->get_duration(), $attempt->duration);
329
        $this->assertEquals($data->completion, $attempt->completion);
330
        $this->assertEquals($data->success, $attempt->success);
331
 
332
        $results = $DB->get_records('h5pactivity_attempts_results');
333
        foreach ($results as $result) {
334
            $statement = (empty($result->subcontent)) ? $statements[0] : $statements[1];
335
            $xapiresult = $statement->get_result()->get_data();
336
            $xapiobject = $statement->get_object()->get_data();
337
            $this->assertEquals($attempt->id, $result->attemptid);
338
            $this->assertEquals($xapiobject->definition->interactionType, $result->interactiontype);
339
            $this->assertEquals($xapiresult->score->raw, $result->rawscore);
340
            $this->assertEquals($xapiresult->score->max, $result->maxscore);
341
            $this->assertEquals($statement->get_result()->get_duration(), $result->duration);
342
            $this->assertEquals($xapiresult->completion, $result->completion);
343
            $this->assertEquals($xapiresult->success, $result->success);
344
        }
345
    }
346
 
347
    /**
348
     * Returns a basic xAPI statements simulating a H5P content.
349
     *
350
     * @param context_module $context activity context
351
     * @param stdClass $user user record
352
     * @return statement[] array of xAPI statements
353
     */
354
    private function generate_statements(context_module $context, stdClass $user): array {
355
        $statements = [];
356
 
357
        $statement = new statement();
358
        $statement->set_actor(item_agent::create_from_user($user));
359
        $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed'));
360
        $definition = item_definition::create_from_data((object)[
361
            'interactionType' => 'compound',
362
            'correctResponsesPattern' => '1',
363
        ]);
364
        $statement->set_object(item_activity::create_from_id($context->id, $definition));
365
        $statement->set_result(item_result::create_from_data((object)[
366
            'completion' => true,
367
            'success' => true,
368
            'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1],
369
            'duration' => 'PT25S',
370
        ]));
371
        $statements[] = $statement;
372
 
373
        $statement = new statement();
374
        $statement->set_actor(item_agent::create_from_user($user));
375
        $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed'));
376
        $definition = item_definition::create_from_data((object)[
377
            'interactionType' => 'matching',
378
            'correctResponsesPattern' => '1',
379
        ]);
380
        $statement->set_object(item_activity::create_from_id($context->id.'?subContentId=111-222-333', $definition));
381
        $statement->set_result(item_result::create_from_data((object)[
382
            'completion' => true,
383
            'success' => true,
384
            'score' => (object) ['min' => 0, 'max' => 1, 'raw' => 0, 'scaled' => 0],
385
            'duration' => 'PT20S',
386
        ]));
387
        $statements[] = $statement;
388
 
389
        return $statements;
390
    }
391
 
392
    /**
393
     * Test validate_state method.
394
     */
395
    public function test_validate_state(): void {
396
        global $DB;
397
 
398
        $this->resetAfterTest();
399
 
400
        /** @var \core_h5p_generator $generator */
401
        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
402
 
403
        // Create a valid H5P activity with a valid xAPI state.
404
        $course = $this->getDataGenerator()->create_course();
405
        $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
406
        $this->setUser($user);
407
        $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
408
        $coursecontext = \context_course::instance($course->id);
409
        $activitycontext = \context_module::instance($activity->cmid);
410
        $component = 'mod_h5pactivity';
411
        $filerecord = [
412
            'contextid' => $activitycontext->id,
413
            'component' => $component,
414
            'filearea' => 'package',
415
            'itemid' => 0,
416
            'filepath' => '/',
417
            'filename' => 'dummy.h5p',
418
            'addxapistate' => true,
419
        ];
420
        $generator->generate_h5p_data(false, $filerecord);
421
 
422
        $handler = handler::create($component);
423
        // Change the method visibility for validate_state in order to test it.
424
        $method = new \ReflectionMethod(handler::class, 'validate_state');
425
 
426
        // The activity id should be numeric.
427
        $state = test_helper::create_state(['activity' => item_activity::create_from_id('AA')]);
428
        $result = $method->invoke($handler, $state);
429
        $this->assertFalse($result);
430
 
431
        // The activity id should exist.
432
        $state = test_helper::create_state();
433
        $result = $method->invoke($handler, $state);
434
        $this->assertFalse($result);
435
 
436
        // The given activity should be H5P activity.
437
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course]);
438
        $state = test_helper::create_state([
439
            'activity' => item_activity::create_from_id($forum->cmid),
440
        ]);
441
        $result = $method->invoke($handler, $state);
442
        $this->assertFalse($result);
443
 
444
        // Tracking should be enabled for the H5P activity.
445
        $state = test_helper::create_state([
446
            'activity' => item_activity::create_from_id($activitycontext->id),
447
            'component' => $component,
448
        ]);
449
        $result = $method->invoke($handler, $state);
450
        $this->assertTrue($result);
451
 
452
        // So, when tracking is disabled, the state won't be considered valid.
453
        $activity2 = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course, 'enabletracking' => 0]);
454
        $activitycontext2 = \context_module::instance($activity2->cmid);
455
        $state = test_helper::create_state([
456
            'activity' => item_activity::create_from_id($activitycontext2->id),
457
            'component' => $component,
458
        ]);
459
        $result = $method->invoke($handler, $state);
460
        $this->assertFalse($result);
461
 
462
        // The user should have permission to submit.
463
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
464
        assign_capability('mod/h5pactivity:submit', CAP_PROHIBIT, $studentrole->id, $coursecontext->id);
465
        // Empty all the caches that may be affected by this change.
466
        accesslib_clear_all_caches_for_unit_testing();
467
        \course_modinfo::clear_instance_cache();
468
        $state = test_helper::create_state([
469
            'activity' => item_activity::create_from_id($activitycontext->id),
470
            'component' => $component,
471
        ]);
472
        $result = $method->invoke($handler, $state);
473
        $this->assertFalse($result);
474
    }
475
}