MDL-69810 tool_mobile: Return support contact information via WS
[moodle.git] / admin / tool / mobile / tests / externallib_test.php
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/>.
17 /**
18  * Moodle Mobile admin tool external functions tests.
19  *
20  * @package    tool_mobile
21  * @category   external
22  * @copyright  2016 Juan Leyva
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  * @since      Moodle 3.1
25  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
31 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32 require_once($CFG->dirroot . '/admin/tool/mobile/tests/fixtures/output/mobile.php');
33 require_once($CFG->dirroot . '/webservice/lib.php');
35 use tool_mobile\external;
36 use tool_mobile\api;
38 /**
39  * Moodle Mobile admin tool external functions tests.
40  *
41  * @package     tool_mobile
42  * @copyright   2016 Juan Leyva
43  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44  * @since       Moodle 3.1
45  */
46 class tool_mobile_external_testcase extends externallib_advanced_testcase {
48     /**
49      * Test get_plugins_supporting_mobile.
50      * This is a very basic test because currently there aren't plugins supporting Mobile in core.
51      */
52     public function test_get_plugins_supporting_mobile() {
53         $result = external::get_plugins_supporting_mobile();
54         $result = external_api::clean_returnvalue(external::get_plugins_supporting_mobile_returns(), $result);
55         $this->assertCount(0, $result['warnings']);
56         $this->assertArrayHasKey('plugins', $result);
57         $this->assertTrue(is_array($result['plugins']));
58     }
60     public function test_get_public_config() {
61         global $CFG, $SITE, $OUTPUT;
63         $this->resetAfterTest(true);
64         $result = external::get_public_config();
65         $result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
67         // Test default values.
68         $context = context_system::instance();
69         list($authinstructions, $notusedformat) = external_format_text($CFG->auth_instructions, FORMAT_MOODLE, $context->id);
70         list($maintenancemessage, $notusedformat) = external_format_text($CFG->maintenance_message, FORMAT_MOODLE, $context->id);
72         $expected = array(
73             'wwwroot' => $CFG->wwwroot,
74             'httpswwwroot' => $CFG->wwwroot,
75             'sitename' => external_format_string($SITE->fullname, $context->id, true),
76             'guestlogin' => $CFG->guestloginbutton,
77             'rememberusername' => $CFG->rememberusername,
78             'authloginviaemail' => $CFG->authloginviaemail,
79             'registerauth' => $CFG->registerauth,
80             'forgottenpasswordurl' => $CFG->forgottenpasswordurl,
81             'authinstructions' => $authinstructions,
82             'authnoneenabled' => (int) is_enabled_auth('none'),
83             'enablewebservices' => $CFG->enablewebservices,
84             'enablemobilewebservice' => $CFG->enablemobilewebservice,
85             'maintenanceenabled' => $CFG->maintenance_enabled,
86             'maintenancemessage' => $maintenancemessage,
87             'typeoflogin' => api::LOGIN_VIA_APP,
88             'mobilecssurl' => '',
89             'tool_mobile_disabledfeatures' => '',
90             'launchurl' => "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php",
91             'country' => $CFG->country,
92             'agedigitalconsentverification' => \core_auth\digital_consent::is_age_digital_consent_verification_enabled(),
93             'autolang' => $CFG->autolang,
94             'lang' => $CFG->lang,
95             'langmenu' => $CFG->langmenu,
96             'langlist' => $CFG->langlist,
97             'locale' => $CFG->locale,
98             'tool_mobile_minimumversion' => '',
99             'tool_mobile_iosappid' => get_config('tool_mobile', 'iosappid'),
100             'tool_mobile_androidappid' => get_config('tool_mobile', 'androidappid'),
101             'tool_mobile_setuplink' => get_config('tool_mobile', 'setuplink'),
102             'warnings' => array()
103         );
104         $this->assertEquals($expected, $result);
106         $this->setAdminUser();
107         // Change some values.
108         set_config('registerauth', 'email');
109         $authinstructions = 'Something with <b>html tags</b>';
110         set_config('auth_instructions', $authinstructions);
111         set_config('typeoflogin', api::LOGIN_VIA_BROWSER, 'tool_mobile');
112         set_config('logo', 'mock.png', 'core_admin');
113         set_config('logocompact', 'mock.png', 'core_admin');
114         set_config('forgottenpasswordurl', 'mailto:fake@email.zy'); // Test old hack.
115         set_config('agedigitalconsentverification', 1);
116         set_config('autolang', 1);
117         set_config('lang', 'a_b');  // Set invalid lang.
118         set_config('disabledfeatures', 'myoverview', 'tool_mobile');
119         set_config('minimumversion', '3.8.0', 'tool_mobile');
121         // Enable couple of issuers.
122         $issuer = \core\oauth2\api::create_standard_issuer('google');
123         $irecord = $issuer->to_record();
124         $irecord->clientid = 'mock';
125         $irecord->clientsecret = 'mock';
126         core\oauth2\api::update_issuer($irecord);
128         set_config('hostname', 'localhost', 'auth_cas');
129         set_config('auth_logo', 'http://invalidurl.com//invalid/', 'auth_cas');
130         set_config('auth_name', 'CAS', 'auth_cas');
131         set_config('auth', 'oauth2,cas');
133         list($authinstructions, $notusedformat) = external_format_text($authinstructions, FORMAT_MOODLE, $context->id);
134         $expected['registerauth'] = 'email';
135         $expected['authinstructions'] = $authinstructions;
136         $expected['typeoflogin'] = api::LOGIN_VIA_BROWSER;
137         $expected['forgottenpasswordurl'] = ''; // Expect empty when it's not an URL.
138         $expected['agedigitalconsentverification'] = true;
139         $expected['supportname'] = $CFG->supportname;
140         $expected['supportemail'] = $CFG->supportemail;
141         $expected['autolang'] = '1';
142         $expected['lang'] = ''; // Expect empty because it was set to an invalid lang.
143         $expected['tool_mobile_disabledfeatures'] = 'myoverview';
144         $expected['tool_mobile_minimumversion'] = '3.8.0';
146         if ($logourl = $OUTPUT->get_logo_url()) {
147             $expected['logourl'] = $logourl->out(false);
148         }
149         if ($compactlogourl = $OUTPUT->get_compact_logo_url()) {
150             $expected['compactlogourl'] = $compactlogourl->out(false);
151         }
153         $result = external::get_public_config();
154         $result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
155         // First check providers.
156         $identityproviders = $result['identityproviders'];
157         unset($result['identityproviders']);
159         $this->assertEquals('Google', $identityproviders[0]['name']);
160         $this->assertEquals($irecord->image, $identityproviders[0]['iconurl']);
161         $this->assertContains($CFG->wwwroot, $identityproviders[0]['url']);
163         $this->assertEquals('CAS', $identityproviders[1]['name']);
164         $this->assertEmpty($identityproviders[1]['iconurl']);
165         $this->assertContains($CFG->wwwroot, $identityproviders[1]['url']);
167         $this->assertEquals($expected, $result);
169         // Change providers img.
170         $newurl = 'validimage.png';
171         set_config('auth_logo', $newurl, 'auth_cas');
172         $result = external::get_public_config();
173         $result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
174         $this->assertContains($newurl, $result['identityproviders'][1]['iconurl']);
175     }
177     /**
178      * Test get_config
179      */
180     public function test_get_config() {
181         global $CFG, $SITE;
182         require_once($CFG->dirroot . '/course/format/lib.php');
184         $this->resetAfterTest(true);
186         $mysitepolicy = 'http://mysite.is/policy/';
187         set_config('sitepolicy', $mysitepolicy);
189         $result = external::get_config();
190         $result = external_api::clean_returnvalue(external::get_config_returns(), $result);
192         // SITE summary is null in phpunit which gets transformed to an empty string by format_text.
193         list($sitesummary, $unused) = external_format_text($SITE->summary, $SITE->summaryformat, context_system::instance()->id);
195         // Test default values.
196         $context = context_system::instance();
197         $expected = array(
198             array('name' => 'fullname', 'value' => $SITE->fullname),
199             array('name' => 'shortname', 'value' => $SITE->shortname),
200             array('name' => 'summary', 'value' => $sitesummary),
201             array('name' => 'summaryformat', 'value' => FORMAT_HTML),
202             array('name' => 'frontpage', 'value' => $CFG->frontpage),
203             array('name' => 'frontpageloggedin', 'value' => $CFG->frontpageloggedin),
204             array('name' => 'maxcategorydepth', 'value' => $CFG->maxcategorydepth),
205             array('name' => 'frontpagecourselimit', 'value' => $CFG->frontpagecourselimit),
206             array('name' => 'numsections', 'value' => course_get_format($SITE)->get_last_section_number()),
207             array('name' => 'newsitems', 'value' => $SITE->newsitems),
208             array('name' => 'commentsperpage', 'value' => $CFG->commentsperpage),
209             array('name' => 'sitepolicy', 'value' => $mysitepolicy),
210             array('name' => 'sitepolicyhandler', 'value' => ''),
211             array('name' => 'disableuserimages', 'value' => $CFG->disableuserimages),
212             array('name' => 'mygradesurl', 'value' => user_mygrades_url()->out(false)),
213             array('name' => 'tool_mobile_forcelogout', 'value' => 0),
214             array('name' => 'tool_mobile_customlangstrings', 'value' => ''),
215             array('name' => 'tool_mobile_disabledfeatures', 'value' => ''),
216             array('name' => 'tool_mobile_filetypeexclusionlist', 'value' => ''),
217             array('name' => 'tool_mobile_custommenuitems', 'value' => ''),
218             array('name' => 'tool_mobile_apppolicy', 'value' => ''),
219             array('name' => 'calendartype', 'value' => $CFG->calendartype),
220             array('name' => 'calendar_site_timeformat', 'value' => $CFG->calendar_site_timeformat),
221             array('name' => 'calendar_startwday', 'value' => $CFG->calendar_startwday),
222             array('name' => 'calendar_adminseesall', 'value' => $CFG->calendar_adminseesall),
223             array('name' => 'calendar_lookahead', 'value' => $CFG->calendar_lookahead),
224             array('name' => 'calendar_maxevents', 'value' => $CFG->calendar_maxevents),
225         );
226         $colornumbers = range(1, 10);
227         foreach ($colornumbers as $number) {
228             $expected[] = [
229                 'name' => 'core_admin_coursecolor' . $number,
230                 'value' => get_config('core_admin', 'coursecolor' . $number)
231             ];
232         }
233         $expected[] = ['name' => 'supportname', 'value' => $CFG->supportname];
234         $expected[] = ['name' => 'supportemail', 'value' => $CFG->supportemail];
235         $expected[] = ['name' => 'supportpage', 'value' => $CFG->supportpage];
237         $this->assertCount(0, $result['warnings']);
238         $this->assertEquals($expected, $result['settings']);
240         // Change a value and retrieve filtering by section.
241         set_config('commentsperpage', 1);
242         $expected[10]['value'] = 1;
243         // Remove not expected elements.
244         array_splice($expected, 11);
246         $result = external::get_config('frontpagesettings');
247         $result = external_api::clean_returnvalue(external::get_config_returns(), $result);
248         $this->assertCount(0, $result['warnings']);
249         $this->assertEquals($expected, $result['settings']);
250     }
252     /*
253      * Test get_autologin_key.
254      */
255     public function test_get_autologin_key() {
256         global $DB, $CFG, $USER;
258         $this->resetAfterTest(true);
260         $user = $this->getDataGenerator()->create_user();
261         $this->setUser($user);
262         $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
264         $token = external_generate_token_for_current_user($service);
266         // Check we got the private token.
267         $this->assertTrue(isset($token->privatetoken));
269         // Enable requeriments.
270         $_GET['wstoken'] = $token->token;   // Mock parameters.
272         // Fake the app.
273         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
274                 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
276         // Even if we force the password change for the current user we should be able to retrieve the key.
277         set_user_preference('auth_forcepasswordchange', 1, $user->id);
279         $this->setCurrentTimeStart();
280         $result = external::get_autologin_key($token->privatetoken);
281         $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
282         // Validate the key.
283         $this->assertEquals(32, core_text::strlen($result['key']));
284         $key = $DB->get_record('user_private_key', array('value' => $result['key']));
285         $this->assertEquals($USER->id, $key->userid);
286         $this->assertTimeCurrent($key->validuntil - api::LOGIN_KEY_TTL);
288         // Now, try with an invalid private token.
289         set_user_preference('tool_mobile_autologin_request_last', time() - HOURSECS, $USER);
291         $this->expectException('moodle_exception');
292         $this->expectExceptionMessage(get_string('invalidprivatetoken', 'tool_mobile'));
293         $result = external::get_autologin_key(random_string('64'));
294     }
296     /**
297      * Test get_autologin_key missing ws.
298      */
299     public function test_get_autologin_key_missing_ws() {
300         global $CFG;
301         $this->resetAfterTest(true);
303         // Fake the app.
304         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
305             'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
307         // Need to disable webservices to verify that's checked.
308         $CFG->enablewebservices = 0;
309         $CFG->enablemobilewebservice = 0;
311         $this->setAdminUser();
312         $this->expectException('moodle_exception');
313         $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
314         $result = external::get_autologin_key('');
315     }
317     /**
318      * Test get_autologin_key missing https.
319      */
320     public function test_get_autologin_key_missing_https() {
321         global $CFG;
323         // Fake the app.
324         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
325             'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
327         // Need to simulate a non HTTPS site here.
328         $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot);
330         $this->resetAfterTest(true);
331         $this->setAdminUser();
333         $this->expectException('moodle_exception');
334         $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
335         $result = external::get_autologin_key('');
336     }
338     /**
339      * Test get_autologin_key missing admin.
340      */
341     public function test_get_autologin_key_missing_admin() {
342         global $CFG;
344         $this->resetAfterTest(true);
345         $this->setAdminUser();
347         // Fake the app.
348         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
349             'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
351         $this->expectException('moodle_exception');
352         $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
353         $result = external::get_autologin_key('');
354     }
356     /**
357      * Test get_autologin_key locked.
358      */
359     public function test_get_autologin_key_missing_locked() {
360         global $CFG, $DB, $USER;
362         $this->resetAfterTest(true);
363         $user = $this->getDataGenerator()->create_user();
364         $this->setUser($user);
366         $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
368         $token = external_generate_token_for_current_user($service);
369         $_GET['wstoken'] = $token->token;   // Mock parameters.
371         // Fake the app.
372         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
373             'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
375         $result = external::get_autologin_key($token->privatetoken);
376         $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
378         // Mock last time request.
379         $mocktime = time() - 7 * MINSECS;
380         set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER);
381         $result = external::get_autologin_key($token->privatetoken);
382         $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
384         // We just requested one token, we must wait.
385         $this->expectException('moodle_exception');
386         $this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile'));
387         $result = external::get_autologin_key($token->privatetoken);
388     }
390     /**
391      * Test get_autologin_key missing app_request.
392      */
393     public function test_get_autologin_key_missing_app_request() {
394         global $CFG;
396         $this->resetAfterTest(true);
397         $this->setAdminUser();
399         $this->expectException('moodle_exception');
400         $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
401         $result = external::get_autologin_key('');
402     }
404     /**
405      * Test get_content.
406      */
407     public function test_get_content() {
409         $paramval = 16;
410         $result = external::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval)));
411         $result = external_api::clean_returnvalue(external::get_content_returns(), $result);
412         $this->assertCount(1, $result['templates']);
413         $this->assertCount(1, $result['otherdata']);
414         $this->assertCount(2, $result['restrict']['users']);
415         $this->assertCount(2, $result['restrict']['courses']);
416         $this->assertEquals('alert();', $result['javascript']);
417         $this->assertEquals('main', $result['templates'][0]['id']);
418         $this->assertEquals('The HTML code', $result['templates'][0]['html']);
419         $this->assertEquals('otherdata1', $result['otherdata'][0]['name']);
420         $this->assertEquals($paramval, $result['otherdata'][0]['value']);
421         $this->assertEquals(array(1, 2), $result['restrict']['users']);
422         $this->assertEquals(array(3, 4), $result['restrict']['courses']);
423         $this->assertEmpty($result['files']);
424         $this->assertFalse($result['disabled']);
425     }
427     /**
428      * Test get_content disabled.
429      */
430     public function test_get_content_disabled() {
432         $paramval = 16;
433         $result = external::get_content('tool_mobile', 'test_view_disabled',
434             array(array('name' => 'param1', 'value' => $paramval)));
435         $result = external_api::clean_returnvalue(external::get_content_returns(), $result);
436         $this->assertTrue($result['disabled']);
437     }
439     /**
440      * Test get_content non existent function in valid component.
441      */
442     public function test_get_content_non_existent_function() {
444         $this->expectException('coding_exception');
445         $result = external::get_content('tool_mobile', 'test_blahblah');
446     }
448     /**
449      * Test get_content incorrect component.
450      */
451     public function test_get_content_invalid_component() {
453         $this->expectException('moodle_exception');
454         $result = external::get_content('tool_mobile\hack', 'test_view');
455     }
457     /**
458      * Test get_content non existent component.
459      */
460     public function test_get_content_non_existent_component() {
462         $this->expectException('moodle_exception');
463         $result = external::get_content('tool_blahblahblah', 'test_view');
464     }
466     public function test_call_external_functions() {
467         global $SESSION;
469         $this->resetAfterTest(true);
471         $category = self::getDataGenerator()->create_category(array('name' => 'Category 1'));
472         $course = self::getDataGenerator()->create_course([
473             'category' => $category->id,
474             'shortname' => 'c1',
475             'summary' => '<span lang="en" class="multilang">Course summary</span>'
476                 . '<span lang="eo" class="multilang">Kurso resumo</span>'
477                 . '@@PLUGINFILE@@/filename.txt'
478                 . '<!-- Comment stripped when formatting text -->',
479             'summaryformat' => FORMAT_MOODLE
480         ]);
481         $user1 = self::getDataGenerator()->create_user(['username' => 'user1', 'lastaccess' => time()]);
482         $user2 = self::getDataGenerator()->create_user(['username' => 'user2', 'lastaccess' => time()]);
484         self::setUser($user1);
486         // Setup WS token.
487         $webservicemanager = new \webservice;
488         $service = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
489         $token = external_generate_token_for_current_user($service);
490         $_POST['wstoken'] = $token->token;
492         // Workaround for external_api::call_external_function requiring sesskey.
493         $_POST['sesskey'] = sesskey();
495         // Call some functions.
497         $requests = [
498             [
499                 'function' => 'core_course_get_courses_by_field',
500                 'arguments' => json_encode(['field' => 'id', 'value' => $course->id])
501             ],
502             [
503                 'function' => 'core_user_get_users_by_field',
504                 'arguments' => json_encode(['field' => 'id', 'values' => [$user1->id]])
505             ],
506             [
507                 'function' => 'core_user_get_user_preferences',
508                 'arguments' => json_encode(['name' => 'some_setting', 'userid' => $user2->id])
509             ],
510             [
511                 'function' => 'core_course_get_courses_by_field',
512                 'arguments' => json_encode(['field' => 'shortname', 'value' => $course->shortname])
513             ],
514         ];
515         $result = external::call_external_functions($requests);
517         // We need to execute the return values cleaning process to simulate the web service server.
518         $result = external_api::clean_returnvalue(external::call_external_functions_returns(), $result);
520         // Only 3 responses, the 4th request is not executed because the 3rd throws an exception.
521         $this->assertCount(3, $result['responses']);
523         $this->assertFalse($result['responses'][0]['error']);
524         $coursedata = external_api::clean_returnvalue(
525             core_course_external::get_courses_by_field_returns(),
526             core_course_external::get_courses_by_field('id', $course->id));
527          $this->assertEquals(json_encode($coursedata), $result['responses'][0]['data']);
529         $this->assertFalse($result['responses'][1]['error']);
530         $userdata = external_api::clean_returnvalue(
531             core_user_external::get_users_by_field_returns(),
532             core_user_external::get_users_by_field('id', [$user1->id]));
533         $this->assertEquals(json_encode($userdata), $result['responses'][1]['data']);
535         $this->assertTrue($result['responses'][2]['error']);
536         $exception = json_decode($result['responses'][2]['exception'], true);
537         $this->assertEquals('nopermissions', $exception['errorcode']);
539         // Call a function not included in the external service.
541         $_POST['wstoken'] = $token->token;
542         $functions = $webservicemanager->get_not_associated_external_functions($service->id);
543         $requests = [['function' => current($functions)->name]];
544         $result = external::call_external_functions($requests);
546         $this->assertTrue($result['responses'][0]['error']);
547         $exception = json_decode($result['responses'][0]['exception'], true);
548         $this->assertEquals('accessexception', $exception['errorcode']);
549         $this->assertEquals('webservice', $exception['module']);
551         // Call a function with different external settings.
553         filter_set_global_state('multilang', TEXTFILTER_ON);
554         $_POST['wstoken'] = $token->token;
555         $SESSION->lang = 'eo'; // Change default language, so we can test changing it to "en".
556         $requests = [
557             [
558                 'function' => 'core_course_get_courses_by_field',
559                 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
560             ],
561             [
562                 'function' => 'core_course_get_courses_by_field',
563                 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
564                 'settingraw' => '1'
565             ],
566             [
567                 'function' => 'core_course_get_courses_by_field',
568                 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
569                 'settingraw' => '1',
570                 'settingfileurl' => '0'
571             ],
572             [
573                 'function' => 'core_course_get_courses_by_field',
574                 'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
575                 'settingfilter' => '1',
576                 'settinglang' => 'en'
577             ],
578         ];
579         $result = external::call_external_functions($requests);
581         $this->assertCount(4, $result['responses']);
583         $context = \context_course::instance($course->id);
584         $pluginfile = 'webservice/pluginfile.php';
586         $this->assertFalse($result['responses'][0]['error']);
587         $data = json_decode($result['responses'][0]['data']);
588         $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null);
589         $expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => false]);
590         $this->assertEquals($expected, $data->courses[0]->summary);
592         $this->assertFalse($result['responses'][1]['error']);
593         $data = json_decode($result['responses'][1]['data']);
594         $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null);
595         $this->assertEquals($expected, $data->courses[0]->summary);
597         $this->assertFalse($result['responses'][2]['error']);
598         $data = json_decode($result['responses'][2]['data']);
599         $this->assertEquals($course->summary, $data->courses[0]->summary);
601         $this->assertFalse($result['responses'][3]['error']);
602         $data = json_decode($result['responses'][3]['data']);
603         $expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null);
604         $SESSION->lang = 'en'; // We expect filtered text in english.
605         $expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => true]);
606         $this->assertEquals($expected, $data->courses[0]->summary);
607     }
609     /*
610      * Test get_tokens_for_qr_login.
611      */
612     public function test_get_tokens_for_qr_login() {
613         global $DB, $CFG, $USER;
615         $this->resetAfterTest(true);
617         $user = $this->getDataGenerator()->create_user();
618         $this->setUser($user);
620         $qrloginkey = api::get_qrlogin_key();
622         // Generate new tokens, the ones we expect to receive.
623         $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
624         $token = external_generate_token_for_current_user($service);
626         // Fake the app.
627         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
628                 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
630         $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id);
631         $result = external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result);
633         $this->assertEmpty($result['warnings']);
634         $this->assertEquals($token->token, $result['token']);
635         $this->assertEquals($token->privatetoken, $result['privatetoken']);
637         // Now, try with an invalid key.
638         $this->expectException('moodle_exception');
639         $this->expectExceptionMessage(get_string('invalidkey', 'error'));
640         $result = external::get_tokens_for_qr_login(random_string('64'), $user->id);
641     }
643     /**
644      * Test get_tokens_for_qr_login missing QR code enabled.
645      */
646     public function test_get_tokens_for_qr_login_missing_enableqr() {
647         global $CFG, $USER;
648         $this->resetAfterTest(true);
649         $this->setAdminUser();
651         set_config('qrcodetype', tool_mobile\api::QR_CODE_DISABLED, 'tool_mobile');
653         $this->expectExceptionMessage(get_string('qrcodedisabled', 'tool_mobile'));
654         $result = external::get_tokens_for_qr_login('', $USER->id);
655     }
657     /**
658      * Test get_tokens_for_qr_login missing ws.
659      */
660     public function test_get_tokens_for_qr_login_missing_ws() {
661         global $CFG;
662         $this->resetAfterTest(true);
664         $user = $this->getDataGenerator()->create_user();
665         $this->setUser($user);
667         // Fake the app.
668         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
669             'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
671         // Need to disable webservices to verify that's checked.
672         $CFG->enablewebservices = 0;
673         $CFG->enablemobilewebservice = 0;
675         $this->setAdminUser();
676         $this->expectException('moodle_exception');
677         $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
678         $result = external::get_tokens_for_qr_login('', $user->id);
679     }
681     /**
682      * Test get_tokens_for_qr_login missing https.
683      */
684     public function test_get_tokens_for_qr_login_missing_https() {
685         global $CFG, $USER;
687         // Fake the app.
688         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
689             'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
691         // Need to simulate a non HTTPS site here.
692         $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot);
694         $this->resetAfterTest(true);
695         $this->setAdminUser();
697         $this->expectException('moodle_exception');
698         $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
699         $result = external::get_tokens_for_qr_login('', $USER->id);
700     }
702     /**
703      * Test get_tokens_for_qr_login missing admin.
704      */
705     public function test_get_tokens_for_qr_login_missing_admin() {
706         global $CFG, $USER;
708         $this->resetAfterTest(true);
709         $this->setAdminUser();
711         // Fake the app.
712         core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
713             'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
715         $this->expectException('moodle_exception');
716         $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
717         $result = external::get_tokens_for_qr_login('', $USER->id);
718     }
720     /**
721      * Test get_tokens_for_qr_login missing app_request.
722      */
723     public function test_get_tokens_for_qr_login_missing_app_request() {
724         global $CFG, $USER;
726         $this->resetAfterTest(true);
727         $this->setAdminUser();
729         $this->expectException('moodle_exception');
730         $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
731         $result = external::get_tokens_for_qr_login('', $USER->id);
732     }
734     /**
735      * Test validate subscription key.
736      */
737     public function test_validate_subscription_key_valid() {
738         $this->resetAfterTest(true);
740         $sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)];
741         set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
743         $result = external::validate_subscription_key($sitesubscriptionkey['key']);
744         $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
745         $this->assertEmpty($result['warnings']);
746         $this->assertTrue($result['validated']);
747     }
749     /**
750      * Test validate subscription key invalid first and then a valid one.
751      */
752     public function test_validate_subscription_key_invalid_key_first() {
753         $this->resetAfterTest(true);
755         $sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)];
756         set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
758         $result = external::validate_subscription_key('fakekey');
759         $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
760         $this->assertEmpty($result['warnings']);
761         $this->assertFalse($result['validated']);
763         // The valid one has been invalidated because the previous attempt.
764         $result = external::validate_subscription_key($sitesubscriptionkey['key']);
765         $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
766         $this->assertEmpty($result['warnings']);
767         $this->assertFalse($result['validated']);
768     }
770     /**
771      * Test validate subscription key invalid.
772      */
773     public function test_validate_subscription_key_invalid_key() {
774         $this->resetAfterTest(true);
776         $result = external::validate_subscription_key('fakekey');
777         $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
778         $this->assertEmpty($result['warnings']);
779         $this->assertFalse($result['validated']);
780     }
782     /**
783      * Test validate subscription key invalid.
784      */
785     public function test_validate_subscription_key_outdated() {
786         $this->resetAfterTest(true);
788         $sitesubscriptionkey = ['validuntil' => time() - MINSECS, 'key' => complex_random_string(32)];
789         set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
791         $result = external::validate_subscription_key($sitesubscriptionkey['key']);
792         $result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
793         $this->assertEmpty($result['warnings']);
794         $this->assertFalse($result['validated']);
795     }