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