Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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
 * Unit tests for the webservice component.
19
 *
20
 * @package    core_webservice
21
 * @category   test
22
 * @copyright  2016 Jun Pataleta <jun@moodle.com>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
namespace core_webservice;
26
 
27
use core_external\external_api;
28
use core_external\external_multiple_structure;
29
use core_external\external_single_structure;
30
use core_external\external_value;
31
use webservice;
32
 
33
defined('MOODLE_INTERNAL') || die();
34
 
35
global $CFG;
36
require_once($CFG->dirroot . '/webservice/lib.php');
37
 
38
/**
39
 * Unit tests for the webservice component.
40
 *
41
 * @package    core_webservice
42
 * @category   test
43
 * @copyright  2016 Jun Pataleta <jun@moodle.com>
44
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45
 */
46
class lib_test extends \advanced_testcase {
47
 
48
    /**
49
     * Setup.
50
     */
51
    public function setUp(): void {
52
        // Calling parent is good, always.
53
        parent::setUp();
54
 
55
        // We always need enabled WS for this testcase.
56
        set_config('enablewebservices', '1');
57
    }
58
 
59
    /**
60
     * Test init_service_class().
61
     */
62
    public function test_init_service_class() {
63
        global $DB, $USER;
64
 
65
        $this->resetAfterTest(true);
66
 
67
        // Set current user.
68
        $this->setAdminUser();
69
 
70
        // Add a web service.
71
        $webservice = new \stdClass();
72
        $webservice->name = 'Test web service';
73
        $webservice->enabled = true;
74
        $webservice->restrictedusers = false;
75
        $webservice->component = 'moodle';
76
        $webservice->timecreated = time();
77
        $webservice->downloadfiles = true;
78
        $webservice->uploadfiles = true;
79
        $externalserviceid = $DB->insert_record('external_services', $webservice);
80
 
81
        // Add token.
82
        $externaltoken = new \stdClass();
83
        $externaltoken->token = 'testtoken';
84
        $externaltoken->tokentype = 0;
85
        $externaltoken->userid = $USER->id;
86
        $externaltoken->externalserviceid = $externalserviceid;
87
        $externaltoken->contextid = 1;
88
        $externaltoken->creatorid = $USER->id;
89
        $externaltoken->timecreated = time();
90
        $externaltoken->name = \core_external\util::generate_token_name();
91
        $DB->insert_record('external_tokens', $externaltoken);
92
 
93
        // Add a function to the service.
94
        $wsmethod = new \stdClass();
95
        $wsmethod->externalserviceid = $externalserviceid;
96
        $wsmethod->functionname = 'core_course_get_contents';
97
        $DB->insert_record('external_services_functions', $wsmethod);
98
 
99
        // Initialise the dummy web service.
100
        $dummy = new webservice_dummy(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN);
101
        // Set the token.
102
        $dummy->set_token($externaltoken->token);
103
        // Run the web service.
104
        $dummy->run();
105
        // Get service methods and structs.
106
        $servicemethods = $dummy->get_service_methods();
107
        $servicestructs = $dummy->get_service_structs();
108
        $this->assertNotEmpty($servicemethods);
109
        // The function core_course_get_contents should be only the only web service function in the moment.
110
        $this->assertEquals(1, count($servicemethods));
111
        // The function core_course_get_contents doesn't have a struct class, so the list of service structs should be empty.
112
        $this->assertEmpty($servicestructs);
113
 
114
        // Add other functions to the service.
115
        // The function core_comment_get_comments has one struct class in its output.
116
        $wsmethod->functionname = 'core_comment_get_comments';
117
        $DB->insert_record('external_services_functions', $wsmethod);
118
        // The function core_grades_update_grades has one struct class in its input.
119
        $wsmethod->functionname = 'core_grades_update_grades';
120
        $DB->insert_record('external_services_functions', $wsmethod);
121
 
122
        // Run the web service again.
123
        $dummy->run();
124
        // Get service methods and structs.
125
        $servicemethods = $dummy->get_service_methods();
126
        $servicestructs = $dummy->get_service_structs();
127
        $this->assertEquals(3, count($servicemethods));
128
        $this->assertEquals(2, count($servicestructs));
129
 
130
        // Check the contents of service methods.
131
        foreach ($servicemethods as $method) {
132
            // Get the external function info.
133
            $function = external_api::external_function_info($method->name);
134
 
135
            // Check input params.
136
            foreach ($function->parameters_desc->keys as $name => $keydesc) {
137
                $this->check_params($method->inputparams[$name]['type'], $keydesc, $servicestructs);
138
            }
139
 
140
            // Check output params.
141
            $this->check_params($method->outputparams['return']['type'], $function->returns_desc, $servicestructs);
142
 
143
            // Check description.
144
            $this->assertEquals($function->description, $method->description);
145
        }
146
    }
147
 
148
    /**
149
     * Tests update_token_lastaccess() function.
150
     */
151
    public function test_update_token_lastaccess() {
152
        global $DB, $USER;
153
 
154
        $this->resetAfterTest(true);
155
 
156
        // Set current user.
157
        $this->setAdminUser();
158
 
159
        // Add a web service.
160
        $webservice = new \stdClass();
161
        $webservice->name = 'Test web service';
162
        $webservice->enabled = true;
163
        $webservice->restrictedusers = false;
164
        $webservice->component = 'moodle';
165
        $webservice->timecreated = time();
166
        $webservice->downloadfiles = true;
167
        $webservice->uploadfiles = true;
168
        $DB->insert_record('external_services', $webservice);
169
 
170
        // Add token.
171
        $tokenstr = \core_external\util::generate_token(
172
            EXTERNAL_TOKEN_EMBEDDED,
173
            \core_external\util::get_service_by_name($webservice->name),
174
            $USER->id,
175
            \core\context\system::instance()
176
        );
177
        $token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
178
 
179
        // Trigger last access once (at current time).
180
        webservice::update_token_lastaccess($token);
181
 
182
        // Check last access.
183
        $token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
184
        $this->assertLessThan(5, abs(time() - $token->lastaccess));
185
 
186
        // Try setting it to +1 second. This should not update yet.
187
        $before = (int)$token->lastaccess;
188
        webservice::update_token_lastaccess($token, $before + 1);
189
        $token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
190
        $this->assertEquals($before, $token->lastaccess);
191
 
192
        // To -1000 seconds. This should not update.
193
        webservice::update_token_lastaccess($token, $before - 1000);
194
        $token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
195
        $this->assertEquals($before, $token->lastaccess);
196
 
197
        // To +59 seconds. This should also not quite update.
198
        webservice::update_token_lastaccess($token, $before + 59);
199
        $token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
200
        $this->assertEquals($before, $token->lastaccess);
201
 
202
        // Finally to +60 seconds, where it should update.
203
        webservice::update_token_lastaccess($token, $before + 60);
204
        $token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
205
        $this->assertEquals($before + 60, $token->lastaccess);
206
    }
207
 
208
    /**
209
     * Tests for the {@see webservice::get_missing_capabilities_by_users()} implementation.
210
     */
211
    public function test_get_missing_capabilities_by_users() {
212
        global $DB;
213
 
214
        $this->resetAfterTest(true);
215
        $wsman = new webservice();
216
 
217
        $user1 = $this->getDataGenerator()->create_user();
218
        $user2 = $this->getDataGenerator()->create_user();
219
        $user3 = $this->getDataGenerator()->create_user();
220
 
221
        // Add a test web service.
222
        $serviceid = $wsman->add_external_service((object)[
223
            'name' => 'Test web service',
224
            'enabled' => 1,
225
            'requiredcapability' => '',
226
            'restrictedusers' => false,
227
            'component' => 'moodle',
228
            'downloadfiles' => false,
229
            'uploadfiles' => false,
230
        ]);
231
 
232
        // Add a function to the service that does not declare any capability as required.
233
        $wsman->add_external_function_to_service('core_webservice_get_site_info', $serviceid);
234
 
235
        // Users can be provided as an array of objects, arrays or integers (ids).
236
        $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, array($user2), $user3->id], $serviceid));
237
 
238
        // Add a function to the service that declares some capability as required, but that capability is common for
239
        // any user. Here we use 'core_message_delete_conversation' which declares 'moodle/site:deleteownmessage' which
240
        // in turn is granted to the authenticated user archetype by default.
241
        $wsman->add_external_function_to_service('core_message_delete_conversation', $serviceid);
242
 
243
        // So all three users should have this capability implicitly.
244
        $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid));
245
 
246
        // Add a function to the service that declares some non-common capability. Here we use
247
        // 'core_group_add_group_members' that wants 'moodle/course:managegroups'.
248
        $wsman->add_external_function_to_service('core_group_add_group_members', $serviceid);
249
 
250
        // Make it so that the $user1 has the capability in some course.
251
        $course1 = $this->getDataGenerator()->create_course();
252
        $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'editingteacher');
253
 
254
        // Check that no missing capability is reported for the $user1. We don't care at what actual context the
255
        // external function call will evaluate the permission. We just check that there is a chance that the user has
256
        // the capability somewhere.
257
        $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1], $serviceid));
258
 
259
        // But there is no place at the site where the capability would be granted to the other two users, so it is
260
        // reported as missing.
261
        $missing = $wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid);
262
        $this->assertArrayNotHasKey($user1->id, $missing);
263
        $this->assertContains('moodle/course:managegroups', $missing[$user2->id]);
264
        $this->assertContains('moodle/course:managegroups', $missing[$user3->id]);
265
    }
266
 
267
    /**
268
     * Data provider for {@see test_get_active_tokens}
269
     *
270
     * @return array
271
     */
272
    public function get_active_tokens_provider(): array {
273
        return [
274
            'No expiration' => [0, true],
275
            'Active' => [time() + DAYSECS, true],
276
            'Expired' => [time() - DAYSECS, false],
277
        ];
278
    }
279
 
280
    /**
281
     * Test getting active tokens for a user
282
     *
283
     * @param int $validuntil
284
     * @param bool $expectedactive
285
     *
286
     * @dataProvider get_active_tokens_provider
287
     */
288
    public function test_get_active_tokens(int $validuntil, bool $expectedactive): void {
289
        $this->resetAfterTest();
290
 
291
        $user = $this->getDataGenerator()->create_user();
292
 
293
        /** @var \core_webservice_generator $generator */
294
        $generator = $this->getDataGenerator()->get_plugin_generator('core_webservice');
295
 
296
        $service = $generator->create_service(['name' => 'My test service', 'shortname' => 'mytestservice']);
297
        $generator->create_token(['userid' => $user->id, 'service' => $service->shortname, 'validuntil' => $validuntil]);
298
 
299
        $tokens = webservice::get_active_tokens($user->id);
300
        if ($expectedactive) {
301
            $this->assertCount(1, $tokens);
302
            $this->assertEquals($service->id, reset($tokens)->externalserviceid);
303
        } else {
304
            $this->assertEmpty($tokens);
305
        }
306
    }
307
 
308
    /**
309
     * Utility method that tests the parameter type of a method info's input/output parameter.
310
     *
311
     * @param string $type The parameter type that is being evaluated.
312
     * @param mixed $methoddesc The method description of the WS function.
313
     * @param array $servicestructs The list of generated service struct classes.
314
     */
315
    private function check_params($type, $methoddesc, $servicestructs) {
316
        if ($methoddesc instanceof external_value) {
317
            // Test for simple types.
318
            if (in_array($methoddesc->type, [PARAM_INT, PARAM_FLOAT, PARAM_BOOL])) {
319
                $this->assertEquals($methoddesc->type, $type);
320
            } else {
321
                $this->assertEquals('string', $type);
322
            }
323
        } else if ($methoddesc instanceof external_single_structure) {
324
            // Test that the class name of the struct class is in the array of service structs.
325
            $structinfo = $this->get_struct_info($servicestructs, $type);
326
            $this->assertNotNull($structinfo);
327
            // Test that the properties of the struct info exist in the method description.
328
            foreach ($structinfo->properties as $propname => $proptype) {
329
                $this->assertTrue($this->in_keydesc($methoddesc, $propname));
330
            }
331
        } else if ($methoddesc instanceof external_multiple_structure) {
332
            // Test for array types.
333
            $this->assertEquals('array', $type);
334
        }
335
    }
336
 
337
    /**
338
     * Gets the struct information from the list of struct classes based on the given struct class name.
339
     *
340
     * @param array $structarray The list of generated struct classes.
341
     * @param string $structclass The name of the struct class.
342
     * @return object|null The struct class info, or null if it's not found.
343
     */
344
    private function get_struct_info($structarray, $structclass) {
345
        foreach ($structarray as $struct) {
346
            if ($struct->classname === $structclass) {
347
                return $struct;
348
            }
349
        }
350
        return null;
351
    }
352
 
353
    /**
354
     * Searches the keys of the given external_single_structure object if it contains a certain property name.
355
     *
356
     * @param external_single_structure $keydesc
357
     * @param string $propertyname The property name to be searched for.
358
     * @return bool True if the property name is found in $keydesc. False, otherwise.
359
     */
360
    private function in_keydesc(external_single_structure $keydesc, $propertyname) {
361
        foreach ($keydesc->keys as $key => $desc) {
362
            if ($key === $propertyname) {
363
                return true;
364
            }
365
        }
366
        return false;
367
    }
368
}
369
 
370
/**
371
 * Class webservice_dummy.
372
 *
373
 * Dummy webservice class for testing the \webservice_base_server class and enable us to expose variables we want to test.
374
 *
375
 * @package    core_webservice
376
 * @category   test
377
 * @copyright  2016 Jun Pataleta <jun@moodle.com>
378
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
379
 */
380
class webservice_dummy extends \webservice_base_server {
381
 
382
    /**
383
     * webservice_dummy constructor.
384
     *
385
     * @param int $authmethod The authentication method.
386
     */
387
    public function __construct($authmethod) {
388
        parent::__construct($authmethod);
389
 
390
        // Arbitrarily naming this as REST in order not to have to register another WS protocol and set capabilities.
391
        $this->wsname = 'rest';
392
    }
393
 
394
    /**
395
     * Token setter method.
396
     *
397
     * @param string $token The web service token.
398
     */
399
    public function set_token($token) {
400
        $this->token = $token;
401
    }
402
 
403
    /**
404
     * This method parses the request input, it needs to get:
405
     *  1/ user authentication - username+password or token
406
     *  2/ function name
407
     *  3/ function parameters
408
     */
409
    protected function parse_request() {
410
        // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
411
    }
412
 
413
    /**
414
     * Send the result of function call to the WS client.
415
     */
416
    protected function send_response() {
417
        // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
418
    }
419
 
420
    /**
421
     * Send the error information to the WS client.
422
     *
423
     * @param \Exception $ex
424
     */
425
    protected function send_error($ex = null) {
426
        // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
427
    }
428
 
429
    /**
430
     * run() method implementation.
431
     */
432
    public function run() {
433
        $this->authenticate_user();
434
        $this->init_service_class();
435
    }
436
 
437
    /**
438
     * Getter method of servicemethods array.
439
     *
440
     * @return array
441
     */
442
    public function get_service_methods() {
443
        return $this->servicemethods;
444
    }
445
 
446
    /**
447
     * Getter method of servicestructs array.
448
     *
449
     * @return array
450
     */
451
    public function get_service_structs() {
452
        return $this->servicestructs;
453
    }
454
}