mirrored from git://git.moodle.org/moodle.git
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MDL-76583 core_external: Move external_api unit tests
- Loading branch information
1 parent
56bcaf4
commit cdd51a6
Showing
2 changed files
with
353 additions
and
283 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,353 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace core_external; | ||
|
||
/** | ||
* Unit tests for core_external\external_api. | ||
* | ||
* @package core_external | ||
* @category test | ||
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License | ||
* @covers \core_external\external_api | ||
*/ | ||
class external_api_test extends \advanced_testcase { | ||
|
||
public static function setUpBeforeClass(): void { | ||
global $CFG; | ||
require_once("{$CFG->libdir}/externallib.php"); | ||
} | ||
|
||
/** | ||
* Test the validate_parameters method. | ||
* | ||
* @covers \core_external\external_api::validate_parameters | ||
*/ | ||
public function test_validate_params() { | ||
$params = array('text'=>'aaa', 'someid'=>'6'); | ||
$description = new \external_function_parameters(array('someid' => new \external_value(PARAM_INT, 'Some int value'), | ||
'text' => new \external_value(PARAM_ALPHA, 'Some text value'))); | ||
$result = external_api::validate_parameters($description, $params); | ||
$this->assertCount(2, $result); | ||
reset($result); | ||
$this->assertSame('someid', key($result)); | ||
$this->assertSame(6, $result['someid']); | ||
$this->assertSame('aaa', $result['text']); | ||
|
||
$params = array('someids'=>array('1', 2, 'a'=>'3'), 'scalar'=>666); | ||
$description = new \external_function_parameters(array('someids' => new \external_multiple_structure(new \external_value(PARAM_INT, 'Some ID')), | ||
'scalar' => new \external_value(PARAM_ALPHANUM, 'Some text value'))); | ||
$result = external_api::validate_parameters($description, $params); | ||
$this->assertCount(2, $result); | ||
reset($result); | ||
$this->assertSame('someids', key($result)); | ||
$this->assertEquals(array(0=>1, 1=>2, 2=>3), $result['someids']); | ||
$this->assertSame('666', $result['scalar']); | ||
|
||
$params = array('text'=>'aaa'); | ||
$description = new \external_function_parameters(array('someid' => new \external_value(PARAM_INT, 'Some int value', false), | ||
'text' => new \external_value(PARAM_ALPHA, 'Some text value'))); | ||
$result = external_api::validate_parameters($description, $params); | ||
$this->assertCount(2, $result); | ||
reset($result); | ||
$this->assertSame('someid', key($result)); | ||
$this->assertNull($result['someid']); | ||
$this->assertSame('aaa', $result['text']); | ||
|
||
$params = array('text'=>'aaa'); | ||
$description = new \external_function_parameters(array('someid' => new \external_value(PARAM_INT, 'Some int value', false, 6), | ||
'text' => new \external_value(PARAM_ALPHA, 'Some text value'))); | ||
$result = external_api::validate_parameters($description, $params); | ||
$this->assertCount(2, $result); | ||
reset($result); | ||
$this->assertSame('someid', key($result)); | ||
$this->assertSame(6, $result['someid']); | ||
$this->assertSame('aaa', $result['text']); | ||
} | ||
|
||
/** | ||
* Test for clean_returnvalue() for testing that returns the PHP type. | ||
* | ||
* @covers \core_external\external_api::clean_returnvalue | ||
*/ | ||
public function test_clean_returnvalue_return_php_type() { | ||
|
||
$returndesc = new \external_single_structure( | ||
array( | ||
'value' => new \external_value(PARAM_RAW, 'Some text', VALUE_OPTIONAL, null, NULL_NOT_ALLOWED) | ||
) | ||
); | ||
|
||
// Check return type on exception because the external values does not allow NULL values. | ||
$testdata = array('value' => null); | ||
try { | ||
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata); | ||
} catch (\moodle_exception $e) { | ||
$this->assertInstanceOf(\invalid_response_exception::class, $e); | ||
$this->assertStringContainsString('of PHP type "NULL"', $e->debuginfo); | ||
} | ||
} | ||
|
||
/** | ||
* Test for clean_returnvalue(). | ||
* | ||
* @covers external_api::clean_returnvalue | ||
*/ | ||
public function test_clean_returnvalue() { | ||
|
||
// Build some return value decription. | ||
$returndesc = new \external_multiple_structure( | ||
new \external_single_structure( | ||
array( | ||
'object' => new \external_single_structure( | ||
array('value1' => new \external_value(PARAM_INT, 'this is a int'))), | ||
'value2' => new \external_value(PARAM_TEXT, 'some text', VALUE_OPTIONAL)) | ||
)); | ||
|
||
// Clean an object (it should be cast into an array). | ||
$object = new \stdClass(); | ||
$object->value1 = 1; | ||
$singlestructure['object'] = $object; | ||
$singlestructure['value2'] = 'Some text'; | ||
$testdata = array($singlestructure); | ||
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata); | ||
$cleanedsinglestructure = array_pop($cleanedvalue); | ||
$this->assertSame($object->value1, $cleanedsinglestructure['object']['value1']); | ||
$this->assertSame($singlestructure['value2'], $cleanedsinglestructure['value2']); | ||
|
||
// Missing VALUE_OPTIONAL. | ||
$object = new \stdClass(); | ||
$object->value1 = 1; | ||
$singlestructure = new \stdClass(); | ||
$singlestructure->object = $object; | ||
$testdata = array($singlestructure); | ||
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata); | ||
$cleanedsinglestructure = array_pop($cleanedvalue); | ||
$this->assertSame($object->value1, $cleanedsinglestructure['object']['value1']); | ||
$this->assertArrayNotHasKey('value2', $cleanedsinglestructure); | ||
|
||
// Unknown attribute (the value should be ignored). | ||
$object = array(); | ||
$object['value1'] = 1; | ||
$singlestructure = array(); | ||
$singlestructure['object'] = $object; | ||
$singlestructure['value2'] = 'Some text'; | ||
$singlestructure['unknownvalue'] = 'Some text to ignore'; | ||
$testdata = array($singlestructure); | ||
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata); | ||
$cleanedsinglestructure = array_pop($cleanedvalue); | ||
$this->assertSame($object['value1'], $cleanedsinglestructure['object']['value1']); | ||
$this->assertSame($singlestructure['value2'], $cleanedsinglestructure['value2']); | ||
$this->assertArrayNotHasKey('unknownvalue', $cleanedsinglestructure); | ||
|
||
// Missing required value (an exception is thrown). | ||
$object = array(); | ||
$singlestructure = array(); | ||
$singlestructure['object'] = $object; | ||
$singlestructure['value2'] = 'Some text'; | ||
$testdata = array($singlestructure); | ||
$this->expectException('invalid_response_exception'); | ||
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata); | ||
} | ||
|
||
/** | ||
* Test \core_external\external_api::get_context_from_params(). | ||
* | ||
* @covers \core_external\external_api::get_context_from_params | ||
*/ | ||
public function test_get_context_from_params() { | ||
$this->resetAfterTest(true); | ||
$course = $this->getDataGenerator()->create_course(); | ||
$realcontext = \context_course::instance($course->id); | ||
|
||
// Use context id. | ||
$fetchedcontext = $this->get_context_from_params(array("contextid" => $realcontext->id)); | ||
$this->assertEquals($realcontext, $fetchedcontext); | ||
|
||
// Use context level and instance id. | ||
$fetchedcontext = $this->get_context_from_params(array("contextlevel" => "course", "instanceid" => $course->id)); | ||
$this->assertEquals($realcontext, $fetchedcontext); | ||
|
||
// Passing empty values. | ||
try { | ||
$fetchedcontext = $this->get_context_from_params(array("contextid" => 0)); | ||
$this->fail('Exception expected from get_context_wrapper()'); | ||
} catch (\moodle_exception $e) { | ||
$this->assertInstanceOf(\invalid_parameter_exception::class, $e); | ||
} | ||
|
||
try { | ||
$fetchedcontext = $this->get_context_from_params(array("instanceid" => 0)); | ||
$this->fail('Exception expected from get_context_wrapper()'); | ||
} catch (\moodle_exception $e) { | ||
$this->assertInstanceOf(\invalid_parameter_exception::class, $e); | ||
} | ||
|
||
try { | ||
$fetchedcontext = $this->get_context_from_params(array("contextid" => null)); | ||
$this->fail('Exception expected from get_context_wrapper()'); | ||
} catch (\moodle_exception $e) { | ||
$this->assertInstanceOf(\invalid_parameter_exception::class, $e); | ||
} | ||
|
||
// Tests for context with instanceid equal to 0 (System context). | ||
$realcontext = \context_system::instance(); | ||
$fetchedcontext = $this->get_context_from_params(array("contextlevel" => "system", "instanceid" => 0)); | ||
$this->assertEquals($realcontext, $fetchedcontext); | ||
|
||
// Passing wrong level. | ||
$this->expectException('invalid_parameter_exception'); | ||
$fetchedcontext = $this->get_context_from_params(array("contextlevel" => "random", "instanceid" => $course->id)); | ||
} | ||
|
||
/** | ||
* Test \core_external\external_api::get_context()_from_params parameter validation. | ||
* | ||
* @covers \core_external\external_api::get_context | ||
*/ | ||
public function test_get_context_params() { | ||
global $USER; | ||
|
||
// Call without correct context details. | ||
$this->expectException('invalid_parameter_exception'); | ||
$this->get_context_from_params(array('roleid' => 3, 'userid' => $USER->id)); | ||
} | ||
|
||
/** | ||
* Test \core_external\external_api::get_context()_from_params parameter validation. | ||
* | ||
* @covers \core_external\external_api::get_context | ||
*/ | ||
public function test_get_context_params2() { | ||
global $USER; | ||
|
||
// Call without correct context details. | ||
$this->expectException('invalid_parameter_exception'); | ||
$this->get_context_from_params(array('roleid' => 3, 'userid' => $USER->id, 'contextlevel' => "course")); | ||
} | ||
|
||
/** | ||
* Test \core_external\external_api::get_context()_from_params parameter validation. | ||
* @covers \core_external\external_api::get_context | ||
*/ | ||
public function test_get_context_params3() { | ||
global $USER; | ||
|
||
// Call without correct context details. | ||
$this->resetAfterTest(true); | ||
$course = self::getDataGenerator()->create_course(); | ||
$this->expectException('invalid_parameter_exception'); | ||
$this->get_context_from_params(array('roleid' => 3, 'userid' => $USER->id, 'instanceid' => $course->id)); | ||
} | ||
|
||
/** | ||
* Data provider for the test_all_external_info test. | ||
* | ||
* @return array | ||
*/ | ||
public function all_external_info_provider() { | ||
global $DB; | ||
|
||
// We are testing here that all the external function descriptions can be generated without | ||
// producing warnings. E.g. misusing optional params will generate a debugging message which | ||
// will fail this test. | ||
$functions = $DB->get_records('external_functions', array(), 'name'); | ||
$return = array(); | ||
foreach ($functions as $f) { | ||
$return[$f->name] = array($f); | ||
} | ||
return $return; | ||
} | ||
|
||
/** | ||
* Test \core_external\external_api::external_function_info. | ||
* | ||
* @dataProvider all_external_info_provider | ||
* @covers \core_external\external_api::external_function_info | ||
*/ | ||
public function test_all_external_info($f) { | ||
$desc = external_api::external_function_info($f); | ||
$this->assertNotEmpty($desc->name); | ||
$this->assertNotEmpty($desc->classname); | ||
$this->assertNotEmpty($desc->methodname); | ||
$this->assertEquals($desc->component, clean_param($desc->component, PARAM_COMPONENT)); | ||
$this->assertInstanceOf(\external_function_parameters::class, $desc->parameters_desc); | ||
if ($desc->returns_desc != null) { | ||
$this->assertInstanceOf(\external_description::class, $desc->returns_desc); | ||
} | ||
} | ||
|
||
/** | ||
* Test the \core_external\external_api::call_external_function() function. | ||
* | ||
* @covers \core_external\external_api::call_external_function | ||
*/ | ||
public function test_call_external_function() { | ||
global $PAGE, $COURSE, $CFG; | ||
|
||
$this->resetAfterTest(true); | ||
|
||
// Call some webservice functions and verify they are correctly handling $PAGE and $COURSE. | ||
// First test a function that calls validate_context outside a course. | ||
$this->setAdminUser(); | ||
$category = $this->getDataGenerator()->create_category(); | ||
$params = array( | ||
'contextid' => \context_coursecat::instance($category->id)->id, | ||
'name' => 'aaagrrryyy', | ||
'idnumber' => '', | ||
'description' => '' | ||
); | ||
$cohort1 = $this->getDataGenerator()->create_cohort($params); | ||
$cohort2 = $this->getDataGenerator()->create_cohort(); | ||
|
||
$beforepage = $PAGE; | ||
$beforecourse = $COURSE; | ||
$params = array('cohortids' => array($cohort1->id, $cohort2->id)); | ||
$result = external_api::call_external_function('core_cohort_get_cohorts', $params); | ||
|
||
$this->assertSame($beforepage, $PAGE); | ||
$this->assertSame($beforecourse, $COURSE); | ||
|
||
// Now test a function that calls validate_context inside a course. | ||
$course = $this->getDataGenerator()->create_course(); | ||
|
||
$beforepage = $PAGE; | ||
$beforecourse = $COURSE; | ||
$params = array('courseid' => $course->id, 'options' => array()); | ||
$result = external_api::call_external_function('core_enrol_get_enrolled_users', $params); | ||
|
||
$this->assertSame($beforepage, $PAGE); | ||
$this->assertSame($beforecourse, $COURSE); | ||
|
||
// Test a function that triggers a PHP exception. | ||
require_once($CFG->dirroot . '/lib/tests/fixtures/test_external_function_throwable.php'); | ||
|
||
// Call our test function. | ||
$result = \test_external_function_throwable::call_external_function('core_throw_exception', array(), false); | ||
|
||
$this->assertTrue($result['error']); | ||
$this->assertArrayHasKey('exception', $result); | ||
$this->assertEquals($result['exception']->message, 'Exception - Modulo by zero'); | ||
} | ||
|
||
protected function get_context_from_params() { | ||
$rc = new \ReflectionClass(external_api::class); | ||
$method = $rc->getMethod('get_context_from_params'); | ||
$method->setAccessible(true); | ||
return $method->invokeArgs(null, func_get_args()); | ||
} | ||
} |
Oops, something went wrong.