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