. namespace core_external; /** * Unit tests for core_external\util. * * @package core_external * @category test * @copyright 2022 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @covers \core_external\util */ final class util_test extends \advanced_testcase { /** @var \moodle_database The database connection */ protected $db; /** * Store the global DB for restore between tests. */ public function setUp(): void { global $DB; parent::setUp(); $this->db = $DB; external_settings::reset(); } /** * A helper to include the legacy external functions. */ protected function include_legacy_functions(): void { global $CFG; $this->assertTrue( $this->isInIsolation(), 'Inclusion of the legacy test functions requires the test to be run in isolation.', ); // Note: This is retained for testing of the old functions. require_once("{$CFG->libdir}/externallib.php"); } /** * Reset the global DB between tests. */ public function tearDown(): void { global $DB; if ($this->db !== null) { $DB = $this->db; } external_settings::reset(); parent::tearDown(); } /** * Validate courses, but still return courses even if they fail validation. * * @covers \core_external\util::validate_courses */ public function test_validate_courses_keepfails(): void { $this->resetAfterTest(true); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $c3 = $this->getDataGenerator()->create_course(); $u1 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($u1->id, $c1->id); $courseids = [$c1->id, $c2->id, $c3->id]; $this->setUser($u1); [$courses, $warnings] = util::validate_courses($courseids, [], false, true); $this->assertCount(2, $warnings); $this->assertEquals($c2->id, $warnings[0]['itemid']); $this->assertEquals($c3->id, $warnings[1]['itemid']); $this->assertCount(3, $courses); $this->assertTrue($courses[$c1->id]->contextvalidated); $this->assertFalse($courses[$c2->id]->contextvalidated); $this->assertFalse($courses[$c3->id]->contextvalidated); } /** * Validate courses can re-use an array of prefetched courses. * * @covers \core_external\util::validate_courses */ public function test_validate_courses_prefetch(): void { $this->resetAfterTest(true); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $c3 = $this->getDataGenerator()->create_course(); $c4 = $this->getDataGenerator()->create_course(); $u1 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($u1->id, $c1->id); $this->getDataGenerator()->enrol_user($u1->id, $c2->id); $courseids = [$c1->id, $c2->id, $c3->id]; $courses = [$c2->id => $c2, $c3->id => $c3, $c4->id => $c4]; $this->setUser($u1); [$courses, $warnings] = util::validate_courses($courseids, $courses); $this->assertCount(2, $courses); $this->assertCount(1, $warnings); $this->assertArrayHasKey($c1->id, $courses); $this->assertSame($c2, $courses[$c2->id]); $this->assertArrayNotHasKey($c3->id, $courses); // The extra course passed is not returned. $this->assertArrayNotHasKey($c4->id, $courses); } /** * Test the Validate courses standard functionality. * * @covers \core_external\util::validate_courses */ public function test_validate_courses(): void { $this->resetAfterTest(true); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course(); $c3 = $this->getDataGenerator()->create_course(); $u1 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($u1->id, $c1->id); $courseids = [$c1->id, $c2->id, $c3->id]; $this->setAdminUser(); [$courses, $warnings] = util::validate_courses($courseids); $this->assertEmpty($warnings); $this->assertCount(3, $courses); $this->assertArrayHasKey($c1->id, $courses); $this->assertArrayHasKey($c2->id, $courses); $this->assertArrayHasKey($c3->id, $courses); $this->assertEquals($c1->id, $courses[$c1->id]->id); $this->assertEquals($c2->id, $courses[$c2->id]->id); $this->assertEquals($c3->id, $courses[$c3->id]->id); $this->setUser($u1); [$courses, $warnings] = util::validate_courses($courseids); $this->assertCount(2, $warnings); $this->assertEquals($c2->id, $warnings[0]['itemid']); $this->assertEquals($c3->id, $warnings[1]['itemid']); $this->assertCount(1, $courses); $this->assertArrayHasKey($c1->id, $courses); $this->assertArrayNotHasKey($c2->id, $courses); $this->assertArrayNotHasKey($c3->id, $courses); $this->assertEquals($c1->id, $courses[$c1->id]->id); } /** * Text util::get_area_files * * @covers \core_external\util::get_area_files */ public function test_get_area_files(): void { global $CFG, $DB; $this->db = $DB; $DB = $this->getMockBuilder('moodle_database')->getMock(); $content = base64_encode("Let us create a nice simple file."); $timemodified = 102030405; $itemid = 42; $filesize = strlen($content); $DB->method('get_records_sql')->willReturn([ (object) [ 'filename' => 'example.txt', 'filepath' => '/', 'mimetype' => 'text/plain', 'filesize' => $filesize, 'timemodified' => $timemodified, 'itemid' => $itemid, 'pathnamehash' => sha1('/example.txt'), ], ]); $component = 'mod_foo'; $filearea = 'area'; $context = 12345; $expectedfiles = [[ 'filename' => 'example.txt', 'filepath' => '/', 'fileurl' => "{$CFG->wwwroot}/webservice/pluginfile.php/{$context}/{$component}/{$filearea}/{$itemid}/example.txt", 'timemodified' => $timemodified, 'filesize' => $filesize, 'mimetype' => 'text/plain', 'isexternalfile' => false, 'icon' => 'f/text', ], ]; // Get all the files for the area. $files = util::get_area_files($context, $component, $filearea, false); $this->assertEquals($expectedfiles, $files); $DB->method('get_in_or_equal')->willReturn([ '= :mock1', ['mock1' => $itemid], ]); // Get just the file indicated by $itemid. $files = util::get_area_files($context, $component, $filearea, $itemid); $this->assertEquals($expectedfiles, $files); } /** * Test default time for user created tokens. * * @covers \core_external\util::generate_token_for_current_user */ public function test_user_created_tokens_duration(): void { global $CFG, $DB; $this->resetAfterTest(true); $CFG->enablewebservices = 1; $CFG->enablemobilewebservice = 1; $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]); $this->setUser($user1); $timenow = time(); $token = util::generate_token_for_current_user($service); $this->assertGreaterThanOrEqual($timenow + $CFG->tokenduration, $token->validuntil); // Change token default time. $this->setUser($user2); set_config('tokenduration', DAYSECS); $token = util::generate_token_for_current_user($service); $timenow = time(); $this->assertLessThanOrEqual($timenow + DAYSECS, $token->validuntil); } /** * Test the format_text function. * * @covers \core_external\util::format_text * @runInSeparateProcess */ public function test_format_text(): void { $this->include_legacy_functions(); $settings = external_settings::get_instance(); $settings->set_raw(true); $settings->set_filter(false); $context = \context_system::instance(); $test = '$$ \pi $$'; $testformat = FORMAT_MARKDOWN; $correct = [$test, $testformat]; $this->assertSame($correct, util::format_text($test, $testformat, $context, 'core', '', 0)); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct); $settings->set_raw(false); $settings->set_filter(true); $test = '$$ \pi $$'; $testformat = FORMAT_MARKDOWN; $correct = ['

$$ \pi $$

', FORMAT_HTML, ]; $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0), $correct); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct); // Filters can be opted out from by the developer. $test = '$$ \pi $$'; $testformat = FORMAT_MARKDOWN; $correct = ['

$$ \pi $$

', FORMAT_HTML, ]; $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, ['filter' => false]), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct); $test = '

Text

'; $testformat = FORMAT_HTML; $correct = [$test, FORMAT_HTML]; $options = ['allowid' => true]; $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); $test = '

Text

'; $testformat = FORMAT_HTML; $correct = ['

Text

', FORMAT_HTML]; $options = new \stdClass(); $options->allowid = false; $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); $test = '

Text

' . "\n" . 'Newline'; $testformat = FORMAT_MOODLE; $correct = ['

Text

Newline', FORMAT_HTML]; $options = new \stdClass(); $options->newlines = false; $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); $test = '

Text

'; $testformat = FORMAT_MOODLE; $correct = ['
' . $test . '
', FORMAT_HTML]; $options = new \stdClass(); $options->para = true; $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); $test = '

Text

'; $testformat = FORMAT_MOODLE; $correct = [$test, FORMAT_HTML]; $options = new \stdClass(); $options->context = $context; $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); // Function external_format_text should work with context id or context instance. $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); } /** * Teset the format_string function. * * @covers \core_external\util::format_string * @runInSeparateProcess */ public function test_external_format_string(): void { $this->resetAfterTest(); $this->include_legacy_functions(); $settings = external_settings::get_instance(); // Enable multilang filter to on content and heading. filter_set_global_state('multilang', TEXTFILTER_ON); filter_set_applies_to_strings('multilang', 1); $filtermanager = \filter_manager::instance(); $filtermanager->reset_caches(); $settings->set_raw(true); $settings->set_filter(true); $context = \context_system::instance(); $test = 'ENFR '; $test .= '

there

!'; $correct = $test; $this->assertSame($correct, util::format_string($test, $context)); // Function external_format_string should work with context id or context instance. $this->assertSame($correct, external_format_string($test, $context)); $this->assertSame($correct, external_format_string($test, $context->id)); $settings->set_raw(false); $settings->set_filter(false); $test = 'ENFR '; $test .= '

there

?'; $correct = 'ENFR hi there?'; $this->assertSame($correct, util::format_string($test, $context)); // Function external_format_string should work with context id or context instance. $this->assertSame($correct, external_format_string($test, $context)); $this->assertSame($correct, external_format_string($test, $context->id)); $settings->set_filter(true); $test = 'ENFR '; $test .= '

there

@'; $correct = 'EN hi there@'; $this->assertSame($correct, util::format_string($test, $context)); // Function external_format_string should work with context id or context instance. $this->assertSame($correct, external_format_string($test, $context)); $this->assertSame($correct, external_format_string($test, $context->id)); // Filters can be opted out. $test = 'ENFR '; $test .= '

there

%'; $correct = 'ENFR hi there%'; $this->assertSame($correct, util::format_string($test, $context, false, ['filter' => false])); // Function external_format_string should work with context id or context instance. $this->assertSame($correct, external_format_string($test, $context->id, false, ['filter' => false])); $this->assertSame($correct, external_format_string($test, $context, false, ['filter' => false])); $this->assertSame("& < > \" '", format_string("& < > \" '", true, ['escape' => false])); } }