2 // This file is part of Moodle - http://moodle.org/
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.
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.
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 namespace tool_mobile
;
19 use externallib_advanced_testcase
;
21 defined('MOODLE_INTERNAL') ||
die();
25 require_once($CFG->dirroot
. '/webservice/tests/helpers.php');
26 require_once($CFG->dirroot
. '/admin/tool/mobile/tests/fixtures/output/mobile.php');
27 require_once($CFG->dirroot
. '/webservice/lib.php');
30 * Moodle Mobile admin tool external functions tests.
32 * @package tool_mobile
33 * @copyright 2016 Juan Leyva
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class externallib_test
extends externallib_advanced_testcase
{
40 * Test get_plugins_supporting_mobile.
41 * This is a very basic test because currently there aren't plugins supporting Mobile in core.
43 public function test_get_plugins_supporting_mobile() {
44 $result = external
::get_plugins_supporting_mobile();
45 $result = \external_api
::clean_returnvalue(external
::get_plugins_supporting_mobile_returns(), $result);
46 $this->assertCount(0, $result['warnings']);
47 $this->assertArrayHasKey('plugins', $result);
48 $this->assertTrue(is_array($result['plugins']));
51 public function test_get_public_config() {
52 global $CFG, $SITE, $OUTPUT;
54 $this->resetAfterTest(true);
55 $result = external
::get_public_config();
56 $result = \external_api
::clean_returnvalue(external
::get_public_config_returns(), $result);
58 // Test default values.
59 $context = \context_system
::instance();
60 list($authinstructions, $notusedformat) = external_format_text($CFG->auth_instructions
, FORMAT_MOODLE
, $context->id
);
61 list($maintenancemessage, $notusedformat) = external_format_text($CFG->maintenance_message
, FORMAT_MOODLE
, $context->id
);
64 'wwwroot' => $CFG->wwwroot
,
65 'httpswwwroot' => $CFG->wwwroot
,
66 'sitename' => external_format_string($SITE->fullname
, $context->id
, true),
67 'guestlogin' => $CFG->guestloginbutton
,
68 'rememberusername' => $CFG->rememberusername
,
69 'authloginviaemail' => $CFG->authloginviaemail
,
70 'registerauth' => $CFG->registerauth
,
71 'forgottenpasswordurl' => $CFG->forgottenpasswordurl
,
72 'authinstructions' => $authinstructions,
73 'authnoneenabled' => (int) is_enabled_auth('none'),
74 'enablewebservices' => $CFG->enablewebservices
,
75 'enablemobilewebservice' => $CFG->enablemobilewebservice
,
76 'maintenanceenabled' => $CFG->maintenance_enabled
,
77 'maintenancemessage' => $maintenancemessage,
78 'typeoflogin' => api
::LOGIN_VIA_APP
,
80 'tool_mobile_disabledfeatures' => '',
81 'launchurl' => "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php",
82 'country' => $CFG->country
,
83 'agedigitalconsentverification' => \core_auth\digital_consent
::is_age_digital_consent_verification_enabled(),
84 'autolang' => $CFG->autolang
,
86 'langmenu' => $CFG->langmenu
,
87 'langlist' => $CFG->langlist
,
88 'locale' => $CFG->locale
,
89 'tool_mobile_minimumversion' => '',
90 'tool_mobile_iosappid' => get_config('tool_mobile', 'iosappid'),
91 'tool_mobile_androidappid' => get_config('tool_mobile', 'androidappid'),
92 'tool_mobile_setuplink' => get_config('tool_mobile', 'setuplink'),
93 'tool_mobile_qrcodetype' => get_config('tool_mobile', 'qrcodetype'),
94 'supportpage' => $CFG->supportpage
,
97 $this->assertEquals($expected, $result);
99 $this->setAdminUser();
100 // Change some values.
101 set_config('registerauth', 'email');
102 $authinstructions = 'Something with <b>html tags</b>';
103 set_config('auth_instructions', $authinstructions);
104 set_config('typeoflogin', api
::LOGIN_VIA_BROWSER
, 'tool_mobile');
105 set_config('logo', 'mock.png', 'core_admin');
106 set_config('logocompact', 'mock.png', 'core_admin');
107 set_config('forgottenpasswordurl', 'mailto:fake@email.zy'); // Test old hack.
108 set_config('agedigitalconsentverification', 1);
109 set_config('autolang', 1);
110 set_config('lang', 'a_b'); // Set invalid lang.
111 set_config('disabledfeatures', 'myoverview', 'tool_mobile');
112 set_config('minimumversion', '3.8.0', 'tool_mobile');
113 set_config('supportemail', 'test@test.com');
115 // Enable couple of issuers.
116 $issuer = \core\oauth2\api
::create_standard_issuer('google');
117 $irecord = $issuer->to_record();
118 $irecord->clientid
= 'mock';
119 $irecord->clientsecret
= 'mock';
120 \core\oauth2\api
::update_issuer($irecord);
122 set_config('hostname', 'localhost', 'auth_cas');
123 set_config('auth_logo', 'http://invalidurl.com//invalid/', 'auth_cas');
124 set_config('auth_name', 'CAS', 'auth_cas');
125 set_config('auth', 'oauth2,cas');
127 list($authinstructions, $notusedformat) = external_format_text($authinstructions, FORMAT_MOODLE
, $context->id
);
128 $expected['registerauth'] = 'email';
129 $expected['authinstructions'] = $authinstructions;
130 $expected['typeoflogin'] = api
::LOGIN_VIA_BROWSER
;
131 $expected['forgottenpasswordurl'] = ''; // Expect empty when it's not an URL.
132 $expected['agedigitalconsentverification'] = true;
133 $expected['supportname'] = $CFG->supportname
;
134 $expected['supportemail'] = $CFG->supportemail
;
135 $expected['autolang'] = '1';
136 $expected['lang'] = ''; // Expect empty because it was set to an invalid lang.
137 $expected['tool_mobile_disabledfeatures'] = 'myoverview';
138 $expected['tool_mobile_minimumversion'] = '3.8.0';
140 if ($logourl = $OUTPUT->get_logo_url()) {
141 $expected['logourl'] = $logourl->out(false);
143 if ($compactlogourl = $OUTPUT->get_compact_logo_url()) {
144 $expected['compactlogourl'] = $compactlogourl->out(false);
147 $result = external
::get_public_config();
148 $result = \external_api
::clean_returnvalue(external
::get_public_config_returns(), $result);
149 // First check providers.
150 $identityproviders = $result['identityproviders'];
151 unset($result['identityproviders']);
153 $this->assertEquals('Google', $identityproviders[0]['name']);
154 $this->assertEquals($irecord->image
, $identityproviders[0]['iconurl']);
155 $this->assertStringContainsString($CFG->wwwroot
, $identityproviders[0]['url']);
157 $this->assertEquals('CAS', $identityproviders[1]['name']);
158 $this->assertEmpty($identityproviders[1]['iconurl']);
159 $this->assertStringContainsString($CFG->wwwroot
, $identityproviders[1]['url']);
161 $this->assertEquals($expected, $result);
163 // Change providers img.
164 $newurl = 'validimage.png';
165 set_config('auth_logo', $newurl, 'auth_cas');
166 $result = external
::get_public_config();
167 $result = \external_api
::clean_returnvalue(external
::get_public_config_returns(), $result);
168 $this->assertStringContainsString($newurl, $result['identityproviders'][1]['iconurl']);
174 public function test_get_config() {
176 require_once($CFG->dirroot
. '/course/format/lib.php');
178 $this->resetAfterTest(true);
180 $mysitepolicy = 'http://mysite.is/policy/';
181 set_config('sitepolicy', $mysitepolicy);
182 set_config('supportemail', 'test@test.com');
184 $result = external
::get_config();
185 $result = \external_api
::clean_returnvalue(external
::get_config_returns(), $result);
187 // SITE summary is null in phpunit which gets transformed to an empty string by format_text.
188 list($sitesummary, $unused) = external_format_text($SITE->summary
, $SITE->summaryformat
, \context_system
::instance()->id
);
190 // Test default values.
191 $context = \context_system
::instance();
193 array('name' => 'fullname', 'value' => $SITE->fullname
),
194 array('name' => 'shortname', 'value' => $SITE->shortname
),
195 array('name' => 'summary', 'value' => $sitesummary),
196 array('name' => 'summaryformat', 'value' => FORMAT_HTML
),
197 array('name' => 'frontpage', 'value' => $CFG->frontpage
),
198 array('name' => 'frontpageloggedin', 'value' => $CFG->frontpageloggedin
),
199 array('name' => 'maxcategorydepth', 'value' => $CFG->maxcategorydepth
),
200 array('name' => 'frontpagecourselimit', 'value' => $CFG->frontpagecourselimit
),
201 array('name' => 'numsections', 'value' => course_get_format($SITE)->get_last_section_number()),
202 array('name' => 'newsitems', 'value' => $SITE->newsitems
),
203 array('name' => 'commentsperpage', 'value' => $CFG->commentsperpage
),
204 array('name' => 'sitepolicy', 'value' => $mysitepolicy),
205 array('name' => 'sitepolicyhandler', 'value' => ''),
206 array('name' => 'disableuserimages', 'value' => $CFG->disableuserimages
),
207 array('name' => 'mygradesurl', 'value' => user_mygrades_url()->out(false)),
208 array('name' => 'tool_mobile_forcelogout', 'value' => 0),
209 array('name' => 'tool_mobile_customlangstrings', 'value' => ''),
210 array('name' => 'tool_mobile_disabledfeatures', 'value' => ''),
211 array('name' => 'tool_mobile_filetypeexclusionlist', 'value' => ''),
212 array('name' => 'tool_mobile_custommenuitems', 'value' => ''),
213 array('name' => 'tool_mobile_apppolicy', 'value' => ''),
214 array('name' => 'tool_mobile_autologinmintimebetweenreq', 'value' => 6 * MINSECS
),
215 array('name' => 'calendartype', 'value' => $CFG->calendartype
),
216 array('name' => 'calendar_site_timeformat', 'value' => $CFG->calendar_site_timeformat
),
217 array('name' => 'calendar_startwday', 'value' => $CFG->calendar_startwday
),
218 array('name' => 'calendar_adminseesall', 'value' => $CFG->calendar_adminseesall
),
219 array('name' => 'calendar_lookahead', 'value' => $CFG->calendar_lookahead
),
220 array('name' => 'calendar_maxevents', 'value' => $CFG->calendar_maxevents
),
222 $colornumbers = range(1, 10);
223 foreach ($colornumbers as $number) {
225 'name' => 'core_admin_coursecolor' . $number,
226 'value' => get_config('core_admin', 'coursecolor' . $number)
229 $expected[] = ['name' => 'supportname', 'value' => $CFG->supportname
];
230 $expected[] = ['name' => 'supportemail', 'value' => $CFG->supportemail
];
231 $expected[] = ['name' => 'supportpage', 'value' => $CFG->supportpage
];
233 $expected[] = ['name' => 'coursegraceperiodafter', 'value' => $CFG->coursegraceperiodafter
];
234 $expected[] = ['name' => 'coursegraceperiodbefore', 'value' => $CFG->coursegraceperiodbefore
];
236 $expected[] = ['name' => 'enabledashboard', 'value' => $CFG->enabledashboard
];
238 $this->assertCount(0, $result['warnings']);
239 $this->assertEquals($expected, $result['settings']);
241 // Change a value and retrieve filtering by section.
242 set_config('commentsperpage', 1);
243 $expected[10]['value'] = 1;
244 // Remove not expected elements.
245 array_splice($expected, 11);
247 $result = external
::get_config('frontpagesettings');
248 $result = \external_api
::clean_returnvalue(external
::get_config_returns(), $result);
249 $this->assertCount(0, $result['warnings']);
250 $this->assertEquals($expected, $result['settings']);
254 * Test get_autologin_key.
256 public function test_get_autologin_key() {
257 global $DB, $CFG, $USER;
259 $this->resetAfterTest(true);
261 $user = $this->getDataGenerator()->create_user();
262 $this->setUser($user);
263 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE
));
265 $token = external_generate_token_for_current_user($service);
267 // Check we got the private token.
268 $this->assertTrue(isset($token->privatetoken
));
270 // Enable requeriments.
271 $_GET['wstoken'] = $token->token
; // Mock parameters.
274 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
275 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
277 // Even if we force the password change for the current user we should be able to retrieve the key.
278 set_user_preference('auth_forcepasswordchange', 1, $user->id
);
280 $this->setCurrentTimeStart();
281 $result = external
::get_autologin_key($token->privatetoken
);
282 $result = \external_api
::clean_returnvalue(external
::get_autologin_key_returns(), $result);
284 $this->assertEquals(32, \core_text
::strlen($result['key']));
285 $key = $DB->get_record('user_private_key', array('value' => $result['key']));
286 $this->assertEquals($USER->id
, $key->userid
);
287 $this->assertTimeCurrent($key->validuntil
- api
::LOGIN_KEY_TTL
);
289 // Now, try with an invalid private token.
290 set_user_preference('tool_mobile_autologin_request_last', time() - HOURSECS
, $USER);
292 $this->expectException('moodle_exception');
293 $this->expectExceptionMessage(get_string('invalidprivatetoken', 'tool_mobile'));
294 $result = external
::get_autologin_key(random_string('64'));
298 * Test get_autologin_key missing ws.
300 public function test_get_autologin_key_missing_ws() {
302 $this->resetAfterTest(true);
305 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
306 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
308 // Need to disable webservices to verify that's checked.
309 $CFG->enablewebservices
= 0;
310 $CFG->enablemobilewebservice
= 0;
312 $this->setAdminUser();
313 $this->expectException('moodle_exception');
314 $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
315 $result = external
::get_autologin_key('');
319 * Test get_autologin_key missing https.
321 public function test_get_autologin_key_missing_https() {
325 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
326 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
328 // Need to simulate a non HTTPS site here.
329 $CFG->wwwroot
= str_replace('https:', 'http:', $CFG->wwwroot
);
331 $this->resetAfterTest(true);
332 $this->setAdminUser();
334 $this->expectException('moodle_exception');
335 $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
336 $result = external
::get_autologin_key('');
340 * Test get_autologin_key missing admin.
342 public function test_get_autologin_key_missing_admin() {
345 $this->resetAfterTest(true);
346 $this->setAdminUser();
349 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
350 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
352 $this->expectException('moodle_exception');
353 $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
354 $result = external
::get_autologin_key('');
358 * Test get_autologin_key locked.
360 public function test_get_autologin_key_missing_locked() {
361 global $CFG, $DB, $USER;
363 $this->resetAfterTest(true);
364 $user = $this->getDataGenerator()->create_user();
365 $this->setUser($user);
367 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE
));
369 $token = external_generate_token_for_current_user($service);
370 $_GET['wstoken'] = $token->token
; // Mock parameters.
373 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
374 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
376 $result = external
::get_autologin_key($token->privatetoken
);
377 $result = \external_api
::clean_returnvalue(external
::get_autologin_key_returns(), $result);
379 // Mock last time request.
380 $mocktime = time() - 7 * MINSECS
;
381 set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER);
382 $result = external
::get_autologin_key($token->privatetoken
);
383 $result = \external_api
::clean_returnvalue(external
::get_autologin_key_returns(), $result);
385 // Change min time between requests to 30 seconds.
386 set_config('autologinmintimebetweenreq', 30, 'tool_mobile');
388 // Mock a previous request, 60 seconds ago.
389 $mocktime = time() - MINSECS
;
390 set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER);
391 $result = external
::get_autologin_key($token->privatetoken
); // All good, we were expecint 30 seconds or more.
392 $result = \external_api
::clean_returnvalue(external
::get_autologin_key_returns(), $result);
394 // We just requested one token, we must wait.
395 $this->expectException('moodle_exception');
396 $this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile'));
397 $result = external
::get_autologin_key($token->privatetoken
);
401 * Test get_autologin_key missing app_request.
403 public function test_get_autologin_key_missing_app_request() {
406 $this->resetAfterTest(true);
407 $this->setAdminUser();
409 $this->expectException('moodle_exception');
410 $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
411 $result = external
::get_autologin_key('');
417 public function test_get_content() {
420 $result = external
::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval)));
421 $result = \external_api
::clean_returnvalue(external
::get_content_returns(), $result);
422 $this->assertCount(1, $result['templates']);
423 $this->assertCount(1, $result['otherdata']);
424 $this->assertCount(2, $result['restrict']['users']);
425 $this->assertCount(2, $result['restrict']['courses']);
426 $this->assertEquals('alert();', $result['javascript']);
427 $this->assertEquals('main', $result['templates'][0]['id']);
428 $this->assertEquals('The HTML code', $result['templates'][0]['html']);
429 $this->assertEquals('otherdata1', $result['otherdata'][0]['name']);
430 $this->assertEquals($paramval, $result['otherdata'][0]['value']);
431 $this->assertEquals(array(1, 2), $result['restrict']['users']);
432 $this->assertEquals(array(3, 4), $result['restrict']['courses']);
433 $this->assertEmpty($result['files']);
434 $this->assertFalse($result['disabled']);
438 * Test get_content disabled.
440 public function test_get_content_disabled() {
443 $result = external
::get_content('tool_mobile', 'test_view_disabled',
444 array(array('name' => 'param1', 'value' => $paramval)));
445 $result = \external_api
::clean_returnvalue(external
::get_content_returns(), $result);
446 $this->assertTrue($result['disabled']);
450 * Test get_content non existent function in valid component.
452 public function test_get_content_non_existent_function() {
454 $this->expectException('coding_exception');
455 $result = external
::get_content('tool_mobile', 'test_blahblah');
459 * Test get_content incorrect component.
461 public function test_get_content_invalid_component() {
463 $this->expectException('moodle_exception');
464 $result = external
::get_content('tool_mobile\hack', 'test_view');
468 * Test get_content non existent component.
470 public function test_get_content_non_existent_component() {
472 $this->expectException('moodle_exception');
473 $result = external
::get_content('tool_blahblahblah', 'test_view');
476 public function test_call_external_functions() {
479 $this->resetAfterTest(true);
481 $category = self
::getDataGenerator()->create_category(array('name' => 'Category 1'));
482 $course = self
::getDataGenerator()->create_course([
483 'category' => $category->id
,
485 'summary' => '<span lang="en" class="multilang">Course summary</span>'
486 . '<span lang="eo" class="multilang">Kurso resumo</span>'
487 . '@@PLUGINFILE@@/filename.txt'
488 . '<!-- Comment stripped when formatting text -->',
489 'summaryformat' => FORMAT_MOODLE
491 $user1 = self
::getDataGenerator()->create_user(['username' => 'user1', 'lastaccess' => time()]);
492 $user2 = self
::getDataGenerator()->create_user(['username' => 'user2', 'lastaccess' => time()]);
494 self
::setUser($user1);
497 $webservicemanager = new \webservice
;
498 $service = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE
);
499 $token = external_generate_token_for_current_user($service);
500 $_POST['wstoken'] = $token->token
;
502 // Workaround for \external_api::call_external_function requiring sesskey.
503 $_POST['sesskey'] = sesskey();
505 // Call some functions.
509 'function' => 'core_course_get_courses_by_field',
510 'arguments' => json_encode(['field' => 'id', 'value' => $course->id
])
513 'function' => 'core_user_get_users_by_field',
514 'arguments' => json_encode(['field' => 'id', 'values' => [$user1->id
]])
517 'function' => 'core_user_get_user_preferences',
518 'arguments' => json_encode(['name' => 'some_setting', 'userid' => $user2->id
])
521 'function' => 'core_course_get_courses_by_field',
522 'arguments' => json_encode(['field' => 'shortname', 'value' => $course->shortname
])
525 $result = external
::call_external_functions($requests);
527 // We need to execute the return values cleaning process to simulate the web service server.
528 $result = \external_api
::clean_returnvalue(external
::call_external_functions_returns(), $result);
530 // Only 3 responses, the 4th request is not executed because the 3rd throws an exception.
531 $this->assertCount(3, $result['responses']);
533 $this->assertFalse($result['responses'][0]['error']);
534 $coursedata = \external_api
::clean_returnvalue(
535 \core_course_external
::get_courses_by_field_returns(),
536 \core_course_external
::get_courses_by_field('id', $course->id
));
537 $this->assertEquals(json_encode($coursedata), $result['responses'][0]['data']);
539 $this->assertFalse($result['responses'][1]['error']);
540 $userdata = \external_api
::clean_returnvalue(
541 \core_user_external
::get_users_by_field_returns(),
542 \core_user_external
::get_users_by_field('id', [$user1->id
]));
543 $this->assertEquals(json_encode($userdata), $result['responses'][1]['data']);
545 $this->assertTrue($result['responses'][2]['error']);
546 $exception = json_decode($result['responses'][2]['exception'], true);
547 $this->assertEquals('nopermissions', $exception['errorcode']);
549 // Call a function not included in the external service.
551 $_POST['wstoken'] = $token->token
;
552 $functions = $webservicemanager->get_not_associated_external_functions($service->id
);
553 $requests = [['function' => current($functions)->name
]];
554 $result = external
::call_external_functions($requests);
556 $this->assertTrue($result['responses'][0]['error']);
557 $exception = json_decode($result['responses'][0]['exception'], true);
558 $this->assertEquals('accessexception', $exception['errorcode']);
559 $this->assertEquals('webservice', $exception['module']);
561 // Call a function with different external settings.
563 filter_set_global_state('multilang', TEXTFILTER_ON
);
564 $_POST['wstoken'] = $token->token
;
565 $SESSION->lang
= 'eo'; // Change default language, so we can test changing it to "en".
568 'function' => 'core_course_get_courses_by_field',
569 'arguments' => json_encode(['field' => 'id', 'value' => $course->id
]),
572 'function' => 'core_course_get_courses_by_field',
573 'arguments' => json_encode(['field' => 'id', 'value' => $course->id
]),
577 'function' => 'core_course_get_courses_by_field',
578 'arguments' => json_encode(['field' => 'id', 'value' => $course->id
]),
580 'settingfileurl' => '0'
583 'function' => 'core_course_get_courses_by_field',
584 'arguments' => json_encode(['field' => 'id', 'value' => $course->id
]),
585 'settingfilter' => '1',
586 'settinglang' => 'en'
589 $result = external
::call_external_functions($requests);
591 $this->assertCount(4, $result['responses']);
593 $context = \context_course
::instance($course->id
);
594 $pluginfile = 'webservice/pluginfile.php';
596 $this->assertFalse($result['responses'][0]['error']);
597 $data = json_decode($result['responses'][0]['data']);
598 $expected = file_rewrite_pluginfile_urls($course->summary
, $pluginfile, $context->id
, 'course', 'summary', null);
599 $expected = format_text($expected, $course->summaryformat
, ['para' => false, 'filter' => false]);
600 $this->assertEquals($expected, $data->courses
[0]->summary
);
602 $this->assertFalse($result['responses'][1]['error']);
603 $data = json_decode($result['responses'][1]['data']);
604 $expected = file_rewrite_pluginfile_urls($course->summary
, $pluginfile, $context->id
, 'course', 'summary', null);
605 $this->assertEquals($expected, $data->courses
[0]->summary
);
607 $this->assertFalse($result['responses'][2]['error']);
608 $data = json_decode($result['responses'][2]['data']);
609 $this->assertEquals($course->summary
, $data->courses
[0]->summary
);
611 $this->assertFalse($result['responses'][3]['error']);
612 $data = json_decode($result['responses'][3]['data']);
613 $expected = file_rewrite_pluginfile_urls($course->summary
, $pluginfile, $context->id
, 'course', 'summary', null);
614 $SESSION->lang
= 'en'; // We expect filtered text in english.
615 $expected = format_text($expected, $course->summaryformat
, ['para' => false, 'filter' => true]);
616 $this->assertEquals($expected, $data->courses
[0]->summary
);
620 * Test get_tokens_for_qr_login.
622 public function test_get_tokens_for_qr_login() {
623 global $DB, $CFG, $USER;
625 $this->resetAfterTest(true);
627 $user = $this->getDataGenerator()->create_user();
628 $this->setUser($user);
630 $mobilesettings = get_config('tool_mobile');
631 $mobilesettings->qrsameipcheck
= 1;
632 $qrloginkey = api
::get_qrlogin_key($mobilesettings);
634 // Generate new tokens, the ones we expect to receive.
635 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE
));
636 $token = external_generate_token_for_current_user($service);
639 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
640 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
642 $result = external
::get_tokens_for_qr_login($qrloginkey, $USER->id
);
643 $result = \external_api
::clean_returnvalue(external
::get_tokens_for_qr_login_returns(), $result);
645 $this->assertEmpty($result['warnings']);
646 $this->assertEquals($token->token
, $result['token']);
647 $this->assertEquals($token->privatetoken
, $result['privatetoken']);
649 // Now, try with an invalid key.
650 $this->expectException('moodle_exception');
651 $this->expectExceptionMessage(get_string('invalidkey', 'error'));
652 $result = external
::get_tokens_for_qr_login(random_string('64'), $user->id
);
656 * Test get_tokens_for_qr_login ignore ip check.
658 public function test_get_tokens_for_qr_login_ignore_ip_check() {
659 global $DB, $CFG, $USER;
661 $this->resetAfterTest(true);
663 $user = $this->getDataGenerator()->create_user();
664 $this->setUser($user);
666 $mobilesettings = get_config('tool_mobile');
667 $mobilesettings->qrsameipcheck
= 0;
668 $qrloginkey = api
::get_qrlogin_key($mobilesettings);
670 $key = $DB->get_record('user_private_key', ['value' => $qrloginkey]);
671 $this->assertNull($key->iprestriction
);
673 // Generate new tokens, the ones we expect to receive.
674 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE
));
675 $token = external_generate_token_for_current_user($service);
678 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
679 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
681 $result = external
::get_tokens_for_qr_login($qrloginkey, $USER->id
);
682 $result = \external_api
::clean_returnvalue(external
::get_tokens_for_qr_login_returns(), $result);
684 $this->assertEmpty($result['warnings']);
685 $this->assertEquals($token->token
, $result['token']);
686 $this->assertEquals($token->privatetoken
, $result['privatetoken']);
688 // Now, try with an invalid key.
689 $this->expectException('moodle_exception');
690 $this->expectExceptionMessage(get_string('invalidkey', 'error'));
691 $result = external
::get_tokens_for_qr_login(random_string('64'), $user->id
);
695 * Test get_tokens_for_qr_login ip check fails.
697 public function test_get_tokens_for_qr_login_ip_check_mismatch() {
698 global $DB, $CFG, $USER;
700 $this->resetAfterTest(true);
702 $user = $this->getDataGenerator()->create_user();
703 $this->setUser($user);
705 $mobilesettings = get_config('tool_mobile');
706 $mobilesettings->qrsameipcheck
= 1;
707 $qrloginkey = api
::get_qrlogin_key($mobilesettings);
709 // Alter expected ip.
710 $DB->set_field('user_private_key', 'iprestriction', '6.6.6.6', ['value' => $qrloginkey]);
712 // Generate new tokens, the ones we expect to receive.
713 $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE
));
714 $token = external_generate_token_for_current_user($service);
717 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
718 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
720 $this->expectException('moodle_exception');
721 $this->expectExceptionMessage(get_string('ipmismatch', 'error'));
722 $result = external
::get_tokens_for_qr_login($qrloginkey, $USER->id
);
726 * Test get_tokens_for_qr_login missing QR code enabled.
728 public function test_get_tokens_for_qr_login_missing_enableqr() {
730 $this->resetAfterTest(true);
731 $this->setAdminUser();
733 set_config('qrcodetype', api
::QR_CODE_DISABLED
, 'tool_mobile');
735 $this->expectExceptionMessage(get_string('qrcodedisabled', 'tool_mobile'));
736 $result = external
::get_tokens_for_qr_login('', $USER->id
);
740 * Test get_tokens_for_qr_login missing ws.
742 public function test_get_tokens_for_qr_login_missing_ws() {
744 $this->resetAfterTest(true);
746 $user = $this->getDataGenerator()->create_user();
747 $this->setUser($user);
750 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
751 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
753 // Need to disable webservices to verify that's checked.
754 $CFG->enablewebservices
= 0;
755 $CFG->enablemobilewebservice
= 0;
757 $this->setAdminUser();
758 $this->expectException('moodle_exception');
759 $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
760 $result = external
::get_tokens_for_qr_login('', $user->id
);
764 * Test get_tokens_for_qr_login missing https.
766 public function test_get_tokens_for_qr_login_missing_https() {
770 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
771 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
773 // Need to simulate a non HTTPS site here.
774 $CFG->wwwroot
= str_replace('https:', 'http:', $CFG->wwwroot
);
776 $this->resetAfterTest(true);
777 $this->setAdminUser();
779 $this->expectException('moodle_exception');
780 $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
781 $result = external
::get_tokens_for_qr_login('', $USER->id
);
785 * Test get_tokens_for_qr_login missing admin.
787 public function test_get_tokens_for_qr_login_missing_admin() {
790 $this->resetAfterTest(true);
791 $this->setAdminUser();
794 \core_useragent
::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
795 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
797 $this->expectException('moodle_exception');
798 $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
799 $result = external
::get_tokens_for_qr_login('', $USER->id
);
803 * Test get_tokens_for_qr_login missing app_request.
805 public function test_get_tokens_for_qr_login_missing_app_request() {
808 $this->resetAfterTest(true);
809 $this->setAdminUser();
811 $this->expectException('moodle_exception');
812 $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
813 $result = external
::get_tokens_for_qr_login('', $USER->id
);
817 * Test validate subscription key.
819 public function test_validate_subscription_key_valid() {
820 $this->resetAfterTest(true);
822 $sitesubscriptionkey = ['validuntil' => time() + MINSECS
, 'key' => complex_random_string(32)];
823 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
825 $result = external
::validate_subscription_key($sitesubscriptionkey['key']);
826 $result = \external_api
::clean_returnvalue(external
::validate_subscription_key_returns(), $result);
827 $this->assertEmpty($result['warnings']);
828 $this->assertTrue($result['validated']);
832 * Test validate subscription key invalid first and then a valid one.
834 public function test_validate_subscription_key_invalid_key_first() {
835 $this->resetAfterTest(true);
837 $sitesubscriptionkey = ['validuntil' => time() + MINSECS
, 'key' => complex_random_string(32)];
838 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
840 $result = external
::validate_subscription_key('fakekey');
841 $result = \external_api
::clean_returnvalue(external
::validate_subscription_key_returns(), $result);
842 $this->assertEmpty($result['warnings']);
843 $this->assertFalse($result['validated']);
845 // The valid one has been invalidated because the previous attempt.
846 $result = external
::validate_subscription_key($sitesubscriptionkey['key']);
847 $result = \external_api
::clean_returnvalue(external
::validate_subscription_key_returns(), $result);
848 $this->assertEmpty($result['warnings']);
849 $this->assertFalse($result['validated']);
853 * Test validate subscription key invalid.
855 public function test_validate_subscription_key_invalid_key() {
856 $this->resetAfterTest(true);
858 $result = external
::validate_subscription_key('fakekey');
859 $result = \external_api
::clean_returnvalue(external
::validate_subscription_key_returns(), $result);
860 $this->assertEmpty($result['warnings']);
861 $this->assertFalse($result['validated']);
865 * Test validate subscription key invalid.
867 public function test_validate_subscription_key_outdated() {
868 $this->resetAfterTest(true);
870 $sitesubscriptionkey = ['validuntil' => time() - MINSECS
, 'key' => complex_random_string(32)];
871 set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
873 $result = external
::validate_subscription_key($sitesubscriptionkey['key']);
874 $result = \external_api
::clean_returnvalue(external
::validate_subscription_key_returns(), $result);
875 $this->assertEmpty($result['warnings']);
876 $this->assertFalse($result['validated']);