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
/**
18
 * This file contains unit test related to xAPI library.
19
 *
20
 * @package    core_xapi
21
 * @copyright  2020 Ferran Recio
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
namespace core_xapi\external;
25
 
26
use core_xapi\xapi_exception;
27
use core_xapi\test_helper;
28
use core_xapi\external\post_statement;
29
use core_xapi\local\statement;
30
use core_xapi\local\statement\item_agent;
31
use core_xapi\local\statement\item_group;
32
use core_xapi\local\statement\item_verb;
33
use core_xapi\local\statement\item_activity;
34
use externallib_advanced_testcase;
35
use stdClass;
36
use core_external\external_api;
37
 
38
defined('MOODLE_INTERNAL') || die();
39
 
40
global $CFG;
41
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
42
 
43
/**
44
 * Unit tests for xAPI statement processing webservice.
45
 *
46
 * @package    core_xapi
47
 * @since      Moodle 3.9
48
 * @copyright  2020 Ferran Recio
49
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50
 */
1441 ariadna 51
final class post_statement_test extends externallib_advanced_testcase {
1 efrain 52
 
53
    /** @var test_helper for generating valid xapi statements. */
54
    private $testhelper;
55
 
56
    /**
57
     * Setup to ensure that fixtures are loaded.
58
     */
59
    public static function setupBeforeClass(): void {
60
        global $CFG;
61
        require_once($CFG->dirroot.'/lib/xapi/tests/helper.php');
62
    }
63
 
64
    /**
65
     * Setup test.
66
     */
67
    public function setUp(): void {
68
        global $CFG;
1441 ariadna 69
        parent::setUp();
1 efrain 70
        // We disable group actors on the test xapi_handler.
71
        $CFG->xapitestforcegroupactors = false;
72
    }
73
 
74
    /**
75
     * Return a xAPI external webservice class to operate.
76
     *
77
     * The test needs to fake a component in order to test without
78
     * using a real one. This way if in the future any component
79
     * implement it's xAPI handler this test will continue working.
80
     *
81
     * @return post_statement the external class
82
     */
83
    private function get_extenal_class(): post_statement {
84
        $ws = new class extends post_statement {
85
            protected static function validate_component(string $component): void {
86
                if ($component != 'fake_component') {
87
                    parent::validate_component($component);
88
                }
89
            }
90
        };
91
        return $ws;
92
    }
93
 
94
    /**
95
     * This function do all checks from a standard post_statements request.
96
     *
97
     * The reason for this function is because statements crafting (special in error
98
     * scenarios) is complicated to do via data providers because every test need a specific
99
     * testing conditions. For this reason alls tests creates a scenario and then uses this
100
     * function to check the results.
101
     *
102
     * @param string $component component name
103
     * @param mixed $data data to encode and send to post_statement
104
     * @param array $expected expected results (i empty an exception is expected)
105
     */
106
    private function post_statements_data(string $component, $data, array $expected) {
107
        global $USER;
108
 
109
        $testhelper = new test_helper();
110
        $testhelper->init_log();
111
 
112
        // If no result is expected we will just incur in exception.
113
        if (empty($expected)) {
114
            $this->expectException(xapi_exception::class);
115
        } else {
116
            $this->preventResetByRollback(); // Logging waits till the transaction gets committed.
117
        }
118
 
119
        $json = json_encode($data);
120
 
121
        $external = $this->get_extenal_class();
122
        $result = $external::execute($component, $json);
123
        $result = external_api::clean_returnvalue($external::execute_returns(), $result);
124
 
125
        // Check results.
126
        $this->assertCount(count($expected), $result);
127
        foreach ($expected as $key => $expect) {
128
            $this->assertEquals($expect, $result[$key]);
129
        }
130
 
131
        // Check log entries.
132
        $log = $testhelper->get_last_log_entry();
133
        $this->assertNotEmpty($log);
134
 
135
        // Validate statement information on log.
136
        $value = $log->get_name();
137
        $this->assertEquals($value, 'xAPI test statement');
138
        $value = $log->get_description();
139
        // Due to logstore limitation, event must use a real component (core_xapi).
140
        $this->assertEquals($value, "User '{$USER->id}' send a statement to component 'core_xapi'");
141
    }
142
 
143
    /**
144
     * Return a valid statement object with the params passed.
145
     *
146
     * All tests are based on craft different types os statements. This function
147
     * is made to provent redundant code on the test.
148
     *
149
     * @param array $items array of overriden statement items (default [])
150
     * @return statement the resulting statement
151
     */
152
    private function get_valid_statement(array $items = []): statement {
153
        global $USER;
154
 
155
        $actor = $items['actor'] ?? item_agent::create_from_user($USER);
156
        $verb = $items['verb'] ?? item_verb::create_from_id('cook');
157
        $object = $items['object'] ?? item_activity::create_from_id('paella');
158
 
159
        $statement = new statement();
160
        $statement->set_actor($actor);
161
        $statement->set_verb($verb);
162
        $statement->set_object($object);
163
 
164
        return $statement;
165
    }
166
 
167
    /**
168
     * Testing different component names on valid statements.
169
     *
170
     * @dataProvider components_provider
171
     * @param string $component component name
172
     * @param array $expected expected results
173
     */
11 efrain 174
    public function test_component_names(string $component, array $expected): void {
1 efrain 175
 
176
        $this->resetAfterTest();
177
 
178
        // Scenario.
179
        $this->setAdminUser();
180
 
181
        // Perform test.
182
        $data = $this->get_valid_statement();
183
        $this->post_statements_data ($component, $data, $expected);
184
    }
185
 
186
    /**
187
     * Data provider for the test_component_names tests.
188
     *
189
     * @return  array
190
     */
1441 ariadna 191
    public static function components_provider(): array {
1 efrain 192
        return [
193
            'Inexistent component' => [
194
                'inexistent_component', []
195
            ],
196
            'Compatible component' => [
197
                'fake_component', [true]
198
            ],
199
            'Incompatible component' => [
200
                'core_xapi', []
201
            ],
202
        ];
203
    }
204
 
205
    /**
206
     * Testing raw JSON encoding.
207
     *
208
     * This test is used for wrong json format and empty structures.
209
     *
210
     * @dataProvider invalid_json_provider
211
     * @param string $json json string to send
212
     */
11 efrain 213
    public function test_invalid_json(string $json): void {
1 efrain 214
 
215
        $this->resetAfterTest();
216
 
217
        // Scenario.
218
        $this->setAdminUser();
219
 
220
        // Perform test.
221
        $testhelper = new test_helper();
222
        $testhelper->init_log();
223
 
224
        // If no result is expected we will just incur in exception.
225
        $this->expectException(xapi_exception::class);
226
 
227
        $external = $this->get_extenal_class();
228
        $result = $external::execute('fake_component', $json);
229
        $result = external_api::clean_returnvalue($external::execute_returns(), $result);
230
    }
231
 
232
    /**
233
     * Data provider for the test_components tests.
234
     *
235
     * @return  array
236
     */
1441 ariadna 237
    public static function invalid_json_provider(): array {
1 efrain 238
        return [
239
            'Wrong json' => [
240
                'This is not { a json object /'
241
            ],
242
            'Empty string json' => [
243
                ''
244
            ],
245
            'Empty array json' => [
246
                '[]'
247
            ],
248
            'Invalid single statement json' => [
249
                '{"actor":{"objectType":"Agent","mbox":"noemail@moodle.org"},"verb":{"id":"InvalidVerb"}'
250
                .',"object":{"objectType":"Activity","id":"somethingwrong"}}'
251
            ],
252
            'Invalid multiple statement json' => [
253
                '[{"actor":{"objectType":"Agent","mbox":"noemail@moodle.org"},"verb":{"id":"InvalidVerb"}'
254
                .',"object":{"objectType":"Activity","id":"somethingwrong"}}]'
255
            ],
256
        ];
257
    }
258
 
259
    /**
260
     * Testing agent (user) statements.
261
     *
262
     * This function test several scenarios using different combinations
263
     * of statement rejection motives. Some motives produces a full batch
264
     * rejection (exception) and other can leed to indivual rejection on
265
     * each statement. For example,try to post a statement without $USER
266
     * in it produces a full batch rejection, while using an invalid
267
     * verb on one statement just reject that specific statement
268
     * That is the expected behaviour.
269
     *
270
     * @dataProvider statement_provider
271
     * @param bool $multiple if send multiple statements (adds one valid statement)
272
     * @param bool $validactor if the actor used is valid
273
     * @param bool $validverb if the verb used is valid
274
     * @param array $expected expected results
275
     */
11 efrain 276
    public function test_statements_agent(bool $multiple, bool $validactor, bool $validverb, array $expected): void {
1 efrain 277
        global $USER;
278
 
279
        $this->resetAfterTest();
280
 
281
        $this->setAdminUser();
282
 
283
        $other = $this->getDataGenerator()->create_user();
284
 
285
        $info = [];
286
 
287
        // Setup actor.
288
        if ($validactor) {
289
            $info['actor'] = item_agent::create_from_user($USER);
290
        } else {
291
            $info['actor'] = item_agent::create_from_user($other);
292
        }
293
 
294
        // Setup verb.
295
        if (!$validverb) {
296
            $info['verb'] = item_verb::create_from_id('invalid');
297
        }
298
 
299
        $data = $this->get_valid_statement($info);
300
 
301
        if ($multiple) {
302
            $data = [
303
                $this->get_valid_statement(),
304
                $data,
305
            ];
306
        }
307
 
308
        // Perform test.
309
        $this->post_statements_data ('fake_component', $data, $expected);
310
    }
311
 
312
    /**
313
     * Testing group statements.
314
     *
315
     * This function test several scenarios using different combinations
316
     * of statement rejection motives. Some motives produces a full batch
317
     * rejection (exception) and other can leed to indivual rejection on
318
     * each statement. For example,try to post a statement without $USER
319
     * in it produces a full batch rejection, while using an invalid
320
     * verb on one statement just reject that specific statement
321
     * That is the expected behaviour.
322
     *
323
     * @dataProvider statement_provider
324
     * @param bool $multiple if send multiple statements (adds one valid statement)
325
     * @param bool $validactor if the actor used is valid
326
     * @param bool $validverb if the verb used is valid
327
     * @param array $expected expected results
328
     */
11 efrain 329
    public function test_statements_group(bool $multiple, bool $validactor, bool $validverb, array $expected): void {
1 efrain 330
        global $USER, $CFG;
331
 
332
        $this->resetAfterTest();
333
 
334
        $this->setAdminUser();
335
 
336
        $other = $this->getDataGenerator()->create_user();
337
 
338
        $info = [];
339
 
340
        // Enable group mode in the handle.
341
        $CFG->xapitestforcegroupactors = true;
342
 
343
        // Create one course and 1 group.
344
        $course = $this->getDataGenerator()->create_course();
345
        $this->getDataGenerator()->enrol_user($USER->id, $course->id);
346
        $this->getDataGenerator()->enrol_user($other->id, $course->id);
347
 
348
        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
349
        $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $other->id));
350
 
351
        if ($validactor) {
352
            // Add $USER into a group to make group valid for processing.
353
            $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $USER->id));
354
        }
355
        $info['actor'] = item_group::create_from_group($group);
356
 
357
        // Setup verb.
358
        if (!$validverb) {
359
            $info['verb'] = item_verb::create_from_id('invalid');
360
        }
361
 
362
        $data = $this->get_valid_statement($info);
363
 
364
        if ($multiple) {
365
            $data = [
366
                $this->get_valid_statement(),
367
                $data,
368
            ];
369
        }
370
 
371
        // Perform test.
372
        $this->post_statements_data ('fake_component', $data, $expected);
373
    }
374
 
375
    /**
376
     * Data provider for the test_components tests.
377
     *
378
     * @return  array
379
     */
1441 ariadna 380
    public static function statement_provider(): array {
1 efrain 381
        return [
382
            // Single statement with group statements enabled.
383
            'Single, Valid actor, valid verb' => [
384
                false, true, true, [true]
385
            ],
386
            'Single, Invalid actor, valid verb' => [
387
                false, false, true, []
388
            ],
389
            'Single, Valid actor, invalid verb' => [
390
                false, true, false, []
391
            ],
392
            'Single, Inalid actor, invalid verb' => [
393
                false, false, false, []
394
            ],
395
            // Multi statement with group statements enabled.
396
            'Multiple, Valid actor, valid verb' => [
397
                true, true, true, [true, true]
398
            ],
399
            'Multiple, Invalid actor, valid verb' => [
400
                true, false, true, []
401
            ],
402
            'Multiple, Valid actor, invalid verb' => [
403
                true, true, false, [true, false]
404
            ],
405
            'Multiple, Inalid actor, invalid verb' => [
406
                true, false, false, []
407
            ],
408
        ];
409
    }
410
 
411
    /**
412
     * Test posting group statements to a handler without group actor support.
413
     *
414
     * Try to use group statement in components that not support this feature
415
     * causes a full statements batch rejection.
416
     *
417
     * @dataProvider group_statement_provider
418
     * @param bool $usegroup1 if the 1st statement must be groupal
419
     * @param bool $usegroup2 if the 2nd statement must be groupal
420
     * @param array $expected expected results
421
     */
11 efrain 422
    public function test_group_disabled(bool $usegroup1, bool $usegroup2, array $expected): void {
1 efrain 423
        global $USER;
424
 
425
        $this->resetAfterTest();
426
 
427
        $this->setAdminUser();
428
 
429
        // Create one course and 1 group.
430
        $course = $this->getDataGenerator()->create_course();
431
        $this->getDataGenerator()->enrol_user($USER->id, $course->id);
432
        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
433
        $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $USER->id));
434
 
435
        $info = ['actor' => item_group::create_from_group($group)];
436
 
437
        $groupstatement = $this->get_valid_statement($info);
438
        $agentstatement = $this->get_valid_statement();
439
 
440
        $data = [];
441
        $data[] = ($usegroup1) ? $groupstatement : $agentstatement;
442
        $data[] = ($usegroup2) ? $groupstatement : $agentstatement;
443
 
444
        // Perform test.
445
        $this->post_statements_data ('fake_component', $data, $expected);
446
    }
447
 
448
    /**
449
     * Data provider for the test_components tests.
450
     *
451
     * @return  array
452
     */
1441 ariadna 453
    public static function group_statement_provider(): array {
1 efrain 454
        return [
455
            // Single statement with group statements enabled.
456
            'Group statement + group statement without group support' => [
457
                true, true, []
458
            ],
459
            'Group statement + agent statement without group support' => [
460
                true, false, []
461
            ],
462
            'Agent statement + group statement without group support' => [
463
                true, false, []
464
            ],
465
            'Agent statement + agent statement without group support' => [
466
                false, false, [true, true]
467
            ],
468
        ];
469
    }
470
 
471
    /**
472
     * Test posting a statements batch not accepted by handler.
473
     *
474
     * If all statements from a batch are rejectes by the plugin the full
475
     * batch is considered rejected and an exception is returned.
476
     */
11 efrain 477
    public function test_full_batch_rejected(): void {
1 efrain 478
        $this->resetAfterTest();
479
 
480
        $this->setAdminUser();
481
 
482
        $info = ['verb' => item_verb::create_from_id('invalid')];
483
 
484
        $statement = $this->get_valid_statement($info);
485
 
486
        $data = [$statement, $statement];
487
 
488
        // Perform test.
489
        $this->post_statements_data ('fake_component', $data, []);
490
    }
491
}