<?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_h5p;

use core_collator;
use Moodle\H5PCore;
use Moodle\H5PDisplayOptionBehaviour;

// phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod

/**
 *
 * Test class covering the H5PFrameworkInterface interface implementation.
 *
 * @package    core_h5p
 * @category   test
 * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @covers     \core_h5p\framework
 * @runTestsInSeparateProcesses
 */
class framework_test extends \advanced_testcase {

    /** @var \core_h5p\framework */
    private $framework;

    /**
     * Set up function for tests.
     */
    public function setUp(): void {
        $factory = new \core_h5p\factory();
        $this->framework = $factory->get_framework();
    }

    /**
     * Test the behaviour of getPlatformInfo().
     */
    public function test_getPlatformInfo() {
        global $CFG;

        $platforminfo = $this->framework->getPlatformInfo();

        $expected = array(
            'name' => 'Moodle',
            'version' => $CFG->version,
            'h5pVersion' => $CFG->version
        );

        $this->assertEquals($expected, $platforminfo);
    }

    /**
     * Test the behaviour of fetchExternalData() when the store path is not defined.
     *
     * This test is intensive and requires downloading content of an external file,
     * therefore it might take longer time to execute.
     * In order to execute this test PHPUNIT_LONGTEST should be set to true in phpunit.xml or directly in config.php.
     */
    public function test_fetchExternalData_no_path_defined() {

        if (!PHPUNIT_LONGTEST) {
            $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
        }

        $this->resetAfterTest();

        $library = 'H5P.Accordion';
        // Provide a valid URL to an external H5P content.
        $url = $this->getExternalTestFileUrl('/'.$library.'.h5p');

        // Test fetching an external H5P content without defining a path to where the file should be stored.
        $data = $this->framework->fetchExternalData($url, null, true);

        // The response should not be empty and return true if the file was successfully downloaded.
        $this->assertNotEmpty($data);
        $this->assertTrue($data);

        $h5pfolderpath = $this->framework->getUploadedH5pFolderPath();
        // The uploaded file should exist on the filesystem.
        $this->assertTrue(file_exists($h5pfolderpath . '.h5p'));
    }

    /**
     * Test the behaviour of fetchExternalData() when the store path is defined.
     *
     * This test is intensive and requires downloading content of an external file,
     * therefore it might take longer time to execute.
     * In order to execute this test PHPUNIT_LONGTEST should be set to true in phpunit.xml or directly in config.php.
     */
    public function test_fetchExternalData_path_defined() {
        global $CFG;

        if (!PHPUNIT_LONGTEST) {
            $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
        }

        $this->resetAfterTest();

        $library = 'H5P.Accordion';
        // Provide a valid URL to an external H5P content.
        $url = $this->getExternalTestFileUrl('/'.$library.'.h5p');

        $h5pfolderpath = $CFG->tempdir . uniqid('/h5p-');

        $data = $this->framework->fetchExternalData($url, null, true, $h5pfolderpath . '.h5p');

        // The response should not be empty and return true if the content has been successfully saved to a file.
        $this->assertNotEmpty($data);
        $this->assertTrue($data);

        // The uploaded file should exist on the filesystem.
        $this->assertTrue(file_exists($h5pfolderpath . '.h5p'));
    }

    /**
     * Test the behaviour of fetchExternalData() when the URL is pointing to an external file that is
     * not an h5p content.
     *
     * This test is intensive and requires downloading content of an external file,
     * therefore it might take longer time to execute.
     * In order to execute this test PHPUNIT_LONGTEST should be set to true in phpunit.xml or directly in config.php.
     */
    public function test_fetchExternalData_url_not_h5p() {

        if (!PHPUNIT_LONGTEST) {
            // This test is intensive and requires downloading the content of an external file.
            $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
        }

        $this->resetAfterTest();

        // Provide an URL to an external file that is not an H5P content file.
        $url = $this->getExternalTestFileUrl('/h5pcontenttypes.json');

        $data = $this->framework->fetchExternalData($url, null, true);

        // The response should not be empty and return true if the content has been successfully saved to a file.
        $this->assertNotEmpty($data);
        $this->assertTrue($data);

        // The uploaded file should exist on the filesystem with it's original extension.
        // NOTE: The file would be later validated by the H5P Validator.
        $h5pfolderpath = $this->framework->getUploadedH5pFolderPath();
        $this->assertTrue(file_exists($h5pfolderpath . '.json'));
    }

    /**
     * Test the behaviour of fetchExternalData() when the URL is invalid.
     */
    public function test_fetchExternalData_url_invalid() {
        // Provide an invalid URL to an external file.
        $url = "someprotocol://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";

        $data = $this->framework->fetchExternalData($url, null, true);

        // The response should be empty.
        $this->assertEmpty($data);
    }

    /**
     * Test the behaviour of setLibraryTutorialUrl().
     */
    public function test_setLibraryTutorialUrl() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create several libraries records.
        $lib1 = $generator->create_library_record('Library1', 'Lib1', 1, 0, 1, '', null, 'http://tutorial1.org',
            'http://example.org');
        $lib2 = $generator->create_library_record('Library2', 'Lib2', 2, 0, 1, '', null, 'http://tutorial2.org');
        $lib3 = $generator->create_library_record('Library3', 'Lib3', 3, 0);

        // Check only lib1 tutorial URL is updated.
        $url = 'https://newtutorial.cat';
        $this->framework->setLibraryTutorialUrl($lib1->machinename, $url);

        $libraries = $DB->get_records('h5p_libraries');
        $this->assertEquals($libraries[$lib1->id]->tutorial, $url);
        $this->assertNotEquals($libraries[$lib2->id]->tutorial, $url);

        // Check lib1 tutorial URL is set to null.
        $this->framework->setLibraryTutorialUrl($lib1->machinename, null);

        $libraries = $DB->get_records('h5p_libraries');
        $this->assertCount(3, $libraries);
        $this->assertNull($libraries[$lib1->id]->tutorial);

        // Check no tutorial URL is set if library name doesn't exist.
        $this->framework->setLibraryTutorialUrl('Unexisting library', $url);

        $libraries = $DB->get_records('h5p_libraries');
        $this->assertCount(3, $libraries);
        $this->assertNull($libraries[$lib1->id]->tutorial);
        $this->assertEquals($libraries[$lib2->id]->tutorial, 'http://tutorial2.org');
        $this->assertNull($libraries[$lib3->id]->tutorial);

        // Check tutorial is set as expected when it was null.
        $this->framework->setLibraryTutorialUrl($lib3->machinename, $url);

        $libraries = $DB->get_records('h5p_libraries');
        $this->assertEquals($libraries[$lib3->id]->tutorial, $url);
        $this->assertNull($libraries[$lib1->id]->tutorial);
        $this->assertEquals($libraries[$lib2->id]->tutorial, 'http://tutorial2.org');
    }

    /**
     * Test the behaviour of setErrorMessage().
     */
    public function test_setErrorMessage() {
        // Set an error message and an error code.
        $message = "Error message";
        $code = '404';

        // Set an error message.
        $this->framework->setErrorMessage($message, $code);

        // Get the error messages.
        $errormessages = $this->framework->getMessages('error');

        $expected = new \stdClass();
        $expected->code = 404;
        $expected->message = 'Error message';

        $this->assertEquals($expected, $errormessages[0]);
    }

    /**
     * Test the behaviour of setInfoMessage().
     */
    public function test_setInfoMessage() {
        $message = "Info message";

        // Set an info message.
        $this->framework->setInfoMessage($message);

        // Get the info messages.
        $infomessages = $this->framework->getMessages('info');

        $expected = 'Info message';

        $this->assertEquals($expected, $infomessages[0]);
    }

    /**
     * Test the behaviour of getMessages() when requesting the info messages.
     */
    public function test_getMessages_info() {
        // Set an info message.
        $this->framework->setInfoMessage("Info message");
        // Set an error message.
        $this->framework->setErrorMessage("Error message 1", 404);

        // Get the info messages.
        $infomessages = $this->framework->getMessages('info');

        $expected = 'Info message';

        // Make sure that only the info message has been returned.
        $this->assertCount(1, $infomessages);
        $this->assertEquals($expected, $infomessages[0]);

        $infomessages = $this->framework->getMessages('info');

        // Make sure the info messages have now been removed.
        $this->assertEmpty($infomessages);
    }

    /**
     * Test the behaviour of getMessages() when requesting the error messages.
     */
    public function test_getMessages_error() {
        // Set an info message.
        $this->framework->setInfoMessage("Info message");
        // Set an error message.
        $this->framework->setErrorMessage("Error message 1", 404);
        // Set another error message.
        $this->framework->setErrorMessage("Error message 2", 403);

        // Get the error messages.
        $errormessages = $this->framework->getMessages('error');

        // Make sure that only the error messages are being returned.
        $this->assertEquals(2, count($errormessages));

        $expected1 = (object) [
            'code' => 404,
            'message' => 'Error message 1'
        ];

        $expected2 = (object) [
            'code' => 403,
            'message' => 'Error message 2'
        ];

        $this->assertEquals($expected1, $errormessages[0]);
        $this->assertEquals($expected2, $errormessages[1]);

        $errormessages = $this->framework->getMessages('error');

        // Make sure the info messages have now been removed.
        $this->assertEmpty($errormessages);
    }

    /**
     * Test the behaviour of t() when translating existing string that does not require any arguments.
     */
    public function test_t_existing_string_no_args() {
        // Existing language string without passed arguments.
        $translation = $this->framework->t('No copyright information available for this content.');

        // Make sure the string translation has been returned.
        $this->assertEquals('No copyright information available for this content.', $translation);
    }

    /**
     * Test the behaviour of t() when translating existing string that does require parameters.
     */
    public function test_t_existing_string_args() {
        // Existing language string with passed arguments.
        $translation = $this->framework->t('Illegal option %option in %library',
            ['%option' => 'example', '%library' => 'Test library']);

        // Make sure the string translation has been returned.
        $this->assertEquals('Illegal option example in Test library', $translation);
    }

    /**
     * Test the behaviour of t() when translating non-existent string.
     */
    public function test_t_non_existent_string() {
        // Non-existing language string.
        $message = 'Random message %option';

        $translation = $this->framework->t($message);

        // Make sure a debugging message is triggered.
        $this->assertDebuggingCalled("String translation cannot be found. Please add a string definition for '" .
            $message . "' in the core_h5p component.");
        // As the string does not exist in the mapping array, make sure the passed message is returned.
        $this->assertEquals($message, $translation);
    }

    /**
     * Test the behaviour of getLibraryFileUrl() when requesting a file URL from an existing library and
     * the folder name is parsable.
     **/
    public function test_getLibraryFileUrl() {
        global $CFG;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create a library record.
        $lib = $generator->create_library_record('Library', 'Lib', 1, 1);

        $expected = "{$CFG->wwwroot}/pluginfile.php/1/core_h5p/libraries/{$lib->id}/Library-1.1/library.json";

        // Get the URL of a file from an existing library. The provided folder name is parsable.
        $actual = $this->framework->getLibraryFileUrl('Library-1.1', 'library.json');

        // Make sure the expected URL is returned.
        $this->assertEquals($expected, $actual);
    }

    /**
     * Test the behaviour of getLibraryFileUrl() when requesting a file URL from a non-existent library and
     * the folder name is parsable.
     **/
    public function test_getLibraryFileUrl_non_existent_library() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create a library record.
        $generator->create_library_record('Library', 'Lib', 1, 1);

        // Get the URL of a file from a non-existent library. The provided folder name is parsable.
        $actual = $this->framework->getLibraryFileUrl('Library2-1.1', 'library.json');

        // Make sure a debugging message is triggered.
        $this->assertDebuggingCalled('The library "Library2-1.1" does not exist.');

        // Make sure that an URL is not returned.
        $this->assertEquals(null, $actual);
    }

    /**
     * Test the behaviour of getLibraryFileUrl() when requesting a file URL from an existing library and
     * the folder name is not parsable.
     **/
    public function test_getLibraryFileUrl_not_parsable_folder_name() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create a library record.
        $generator->create_library_record('Library', 'Lib', 1, 1);

        // Get the URL of a file from an existing library. The provided folder name is not parsable.
        $actual = $this->framework->getLibraryFileUrl('Library1.1', 'library.json');

        // Make sure a debugging message is triggered.
        $this->assertDebuggingCalled(
            'The provided string value "Library1.1" is not a valid name for a library folder.');

        // Make sure that an URL is not returned.
        $this->assertEquals(null, $actual);
    }

    /**
     * Test the behaviour of getLibraryFileUrl() when requesting a file URL from a library that has multiple
     * versions and the folder name is parsable.
     **/
    public function test_getLibraryFileUrl_library_has_multiple_versions() {
        global $CFG;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create library records with a different minor version.
        $lib1 = $generator->create_library_record('Library', 'Lib', 1, 1);
        $lib2 = $generator->create_library_record('Library', 'Lib', 1, 3);

        $expected = "{$CFG->wwwroot}/pluginfile.php/1/core_h5p/libraries/{$lib2->id}/Library-1.3/library.json";

        // Get the URL of a file from an existing library (Library 1.3). The provided folder name is parsable.
        $actual = $this->framework->getLibraryFileUrl('Library-1.3', 'library.json');

        // Make sure the proper URL (from the requested library version) is returned.
        $this->assertEquals($expected, $actual);
    }

    /**
     * Test the behaviour of getLibraryFileUrl() when requesting a file URL from a library that has multiple
     * patch versions and the folder name is parsable.
     **/
    public function test_getLibraryFileUrl_library_has_multiple_patch_versions() {
        global $CFG;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create library records with a different patch version.
        $lib1 = $generator->create_library_record('Library', 'Lib', 1, 1, 2);
        $lib2 = $generator->create_library_record('Library', 'Lib', 1, 1, 4);
        $lib3 = $generator->create_library_record('Library', 'Lib', 1, 1, 3);

        $expected = "{$CFG->wwwroot}/pluginfile.php/1/core_h5p/libraries/{$lib2->id}/Library-1.1/library.json";

        // Get the URL of a file from an existing library. The provided folder name is parsable.
        $actual = $this->framework->getLibraryFileUrl('Library-1.1', 'library.json');

        // Make sure the proper URL (from the latest library patch) is returned.
        $this->assertEquals($expected, $actual);
    }

    /**
     * Test the behaviour of getLibraryFileUrl() when requesting a file URL from a sub-folder
     * of an existing library and the folder name is parsable.
     **/
    public function test_getLibraryFileUrl_library_subfolder() {
        global $CFG;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create a library record.
        $lib = $generator->create_library_record('Library', 'Lib', 1, 1);

        $expected = "{$CFG->wwwroot}/pluginfile.php/1/core_h5p/libraries/{$lib->id}/Library-1.1/css/example.css";

        // Get the URL of a file from a sub-folder from an existing library. The provided folder name is parsable.
        $actual = $this->framework->getLibraryFileUrl('Library-1.1/css', 'example.css');

        // Make sure the proper URL is returned.
        $this->assertEquals($expected, $actual);
    }

    /**
     * Test the behaviour of loadAddons().
     */
    public function test_loadAddons() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a Library addon (1.1).
        $generator->create_library_record('Library', 'Lib', 1, 1, 2,
            '', '/regex1/');
        // Create a Library addon (1.3).
        $generator->create_library_record('Library', 'Lib', 1, 3, 2,
            '', '/regex2/');
        // Create a Library addon (1.2).
        $generator->create_library_record('Library', 'Lib', 1, 2, 2,
            '', '/regex3/');
        // Create a Library1 addon (1.2)
        $generator->create_library_record('Library1', 'Lib1', 1, 2, 2,
            '', '/regex11/');

        // Load the latest version of each addon.
        $addons = $this->framework->loadAddons();

        // The addons array should return 2 results (Library and Library1 addon).
        $this->assertCount(2, $addons);

        // Ensure the addons array is consistently ordered before asserting their contents.
        core_collator::asort_array_of_arrays_by_key($addons, 'machineName');
        [$addonone, $addontwo] = array_values($addons);

        // Make sure the version 1.3 is the latest 'Library' addon version.
        $this->assertEquals('Library', $addonone['machineName']);
        $this->assertEquals(1, $addonone['majorVersion']);
        $this->assertEquals(3, $addonone['minorVersion']);

        // Make sure the version 1.2 is the latest 'Library1' addon version.
        $this->assertEquals('Library1', $addontwo['machineName']);
        $this->assertEquals(1, $addontwo['majorVersion']);
        $this->assertEquals(2, $addontwo['minorVersion']);
    }

    /**
     * Test the behaviour of loadLibraries().
     */
    public function test_loadLibraries() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $generator->generate_h5p_data();

        // Load all libraries.
        $libraries = $this->framework->loadLibraries();

        // Make sure all libraries are returned.
        $this->assertNotEmpty($libraries);
        $this->assertCount(6, $libraries);
        $this->assertEquals('MainLibrary', $libraries['MainLibrary'][0]->machine_name);
        $this->assertEquals('1', $libraries['MainLibrary'][0]->major_version);
        $this->assertEquals('0', $libraries['MainLibrary'][0]->minor_version);
        $this->assertEquals('1', $libraries['MainLibrary'][0]->patch_version);
    }

    /**
     * Test the behaviour of test_getLibraryId() when requesting an existing machine name.
     */
    public function test_getLibraryId_existing_machine_name() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library.
        $lib = $generator->create_library_record('Library', 'Lib', 1, 1, 2);

        // Request the library ID of the library with machine name 'Library'.
        $libraryid = $this->framework->getLibraryId('Library');

        // Make sure the library ID is being returned.
        $this->assertNotFalse($libraryid);
        $this->assertEquals($lib->id, $libraryid);
    }

    /**
     * Test the behaviour of test_getLibraryId() when requesting a non-existent machine name.
     */
    public function test_getLibraryId_non_existent_machine_name() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library.
        $generator->create_library_record('Library', 'Lib', 1, 1, 2);

        // Request the library ID of the library with machinename => 'TestLibrary' (non-existent).
        $libraryid = $this->framework->getLibraryId('TestLibrary');

        // Make sure the library ID not being returned.
        $this->assertFalse($libraryid);
    }

    /**
     * Test the behaviour of test_getLibraryId() when requesting a non-existent major version.
     */
    public function test_getLibraryId_non_existent_major_version() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library.
        $generator->create_library_record('Library', 'Lib', 1, 1, 2);

        // Request the library ID of the library with machine name => 'Library', majorversion => 2 (non-existent).
        $libraryid = $this->framework->getLibraryId('Library', 2);

        // Make sure the library ID not being returned.
        $this->assertFalse($libraryid);
    }

    /**
     * Test the behaviour of test_getLibraryId() when requesting a non-existent minor version.
     */
    public function test_getLibraryId_non_existent_minor_version() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library.
        $generator->create_library_record('Library', 'Lib', 1, 1, 2);

        // Request the library ID of the library with machine name => 'Library',
        // majorversion => 1,  minorversion => 2 (non-existent).
        $libraryid = $this->framework->getLibraryId('Library', 1, 2);

        // Make sure the library ID not being returned.
        $this->assertFalse($libraryid);
    }

    /**
     * Test the behaviour of isPatchedLibrary().
     *
     * @dataProvider isPatchedLibrary_provider
     * @param array $libraryrecords Array containing data for the library creation
     * @param array $testlibrary Array containing the test library data
     * @param bool $expected The expectation whether the library is patched or not
     **/
    public function test_isPatchedLibrary(array $libraryrecords, array $testlibrary, bool $expected): void {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        foreach ($libraryrecords as $library) {
            call_user_func_array([$generator, 'create_library_record'], $library);
        }

        $this->assertEquals($expected, $this->framework->isPatchedLibrary($testlibrary));
    }

    /**
     * Data provider for test_isPatchedLibrary().
     *
     * @return array
     */
    public function isPatchedLibrary_provider(): array {
        return [
            'Unpatched library. No different versioning' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 1,
                    'minorVersion' => 1,
                    'patchVersion' => 2
                ],
                false,
            ],
            'Major version identical; Minor version identical; Patch version newer' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 1,
                    'minorVersion' => 1,
                    'patchVersion' => 3
                ],
                true,
            ],
            'Major version identical; Minor version newer; Patch version newer' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 1,
                    'minorVersion' => 2,
                    'patchVersion' => 3
                ],
                false,
            ],
            'Major version identical; Minor version identical; Patch version older' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 1,
                    'minorVersion' => 1,
                    'patchVersion' => 1
                ],
                false,
            ],
            'Major version identical; Minor version newer; Patch version older' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 1,
                    'minorVersion' => 2,
                    'patchVersion' => 1
                ],
                false,
            ],
            'Major version newer; Minor version identical; Patch version older' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 2,
                    'minorVersion' => 1,
                    'patchVersion' => 1
                ],
                false,
            ],
            'Major version newer; Minor version identical; Patch version newer' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 2,
                    'minorVersion' => 1,
                    'patchVersion' => 3
                ],
                false,
            ],

            'Major version older; Minor version identical; Patch version older' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 0,
                    'minorVersion' => 1,
                    'patchVersion' => 1
                ],
                false,
            ],
            'Major version older; Minor version identical; Patch version newer' => [
                [
                    ['TestLibrary', 'Test', 1, 1, 2],
                ],
                [
                    'machineName' => 'TestLibrary',
                    'majorVersion' => 0,
                    'minorVersion' => 1,
                    'patchVersion' => 3
                ],
                false,
            ],
        ];
    }

    /**
     * Test the behaviour of isInDevMode().
     */
    public function test_isInDevMode() {
        $isdevmode = $this->framework->isInDevMode();

        $this->assertFalse($isdevmode);
    }

    /**
     * Test the behaviour of mayUpdateLibraries().
     */
    public function test_mayUpdateLibraries(): void {
        global $DB;

        $this->resetAfterTest();

        // Create some users.
        $contextsys = \context_system::instance();
        $user = $this->getDataGenerator()->create_user();
        $admin = get_admin();
        $managerrole = $DB->get_record('role', ['shortname' => 'manager'], '*', MUST_EXIST);
        $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
        $manager = $this->getDataGenerator()->create_user();
        role_assign($managerrole->id, $manager->id, $contextsys);

        // Create a course with a label and enrol the user.
        $course = $this->getDataGenerator()->create_course();
        $label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
        list(, $labelcm) = get_course_and_cm_from_instance($label->id, 'label');
        $contextlabel = \context_module::instance($labelcm->id);
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');

        // Create the .h5p file.
        $path = __DIR__ . '/fixtures/h5ptest.zip';

        // Admin and manager should have permission to update libraries.
        $file = helper::create_fake_stored_file_from_path($path, $admin->id, $contextsys);
        $this->framework->set_file($file);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertTrue($mayupdatelib);

        $file = helper::create_fake_stored_file_from_path($path, $manager->id, $contextsys);
        $this->framework->set_file($file);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertTrue($mayupdatelib);

        // By default, normal user hasn't permission to update libraries (in both contexts, system and module label).
        $file = helper::create_fake_stored_file_from_path($path, $user->id, $contextsys);
        $this->framework->set_file($file);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertFalse($mayupdatelib);

        $file = helper::create_fake_stored_file_from_path($path, $user->id, $contextlabel);
        $this->framework->set_file($file);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertFalse($mayupdatelib);

        // If the current user (admin) can update libraries, the method should return true (even if the file userid hasn't the
        // required capabilility in the file context).
        $file = helper::create_fake_stored_file_from_path($path, $admin->id, $contextlabel);
        $this->framework->set_file($file);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertTrue($mayupdatelib);

        // If the update capability is assigned to the user, they should be able to update the libraries (only in the context
        // where the capability has been assigned).
        $file = helper::create_fake_stored_file_from_path($path, $user->id, $contextlabel);
        $this->framework->set_file($file);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertFalse($mayupdatelib);
        assign_capability('moodle/h5p:updatelibraries', CAP_ALLOW, $studentrole->id, $contextlabel);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertTrue($mayupdatelib);
        $file = helper::create_fake_stored_file_from_path($path, $user->id, $contextsys);
        $this->framework->set_file($file);
        $mayupdatelib = $this->framework->mayUpdateLibraries();
        $this->assertFalse($mayupdatelib);
    }

    /**
     * Test the behaviour of get_file() and set_file().
     */
    public function test_get_file(): void {
        $this->resetAfterTest();

        // Create some users.
        $contextsys = \context_system::instance();
        $user = $this->getDataGenerator()->create_user();

        // The H5P file.
        $path = __DIR__ . '/fixtures/h5ptest.zip';

        // An error should be raised when it's called before initialitzing it.
        $this->expectException('coding_exception');
        $this->expectExceptionMessage('Using get_file() before file is set');
        $this->framework->get_file();

        // Check the value when only path and user are set.
        $file = helper::create_fake_stored_file_from_path($path, $user->id);
        $this->framework->set_file($file);
        $file = $this->framework->get_file();
        $this->assertEquals($user->id, $$file->get_userid());
        $this->assertEquals($contextsys->id, $file->get_contextid());

        // Check the value when also the context is set.
        $course = $this->getDataGenerator()->create_course();
        $contextcourse = \context_course::instance($course->id);
        $file = helper::create_fake_stored_file_from_path($path, $user->id, $contextcourse);
        $this->framework->set_file($file);
        $file = $this->framework->get_file();
        $this->assertEquals($user->id, $$file->get_userid());
        $this->assertEquals($contextcourse->id, $file->get_contextid());
    }

    /**
     * Test the behaviour of saveLibraryData() when saving data for a new library.
     */
    public function test_saveLibraryData_new_library() {
        global $DB;

        $this->resetAfterTest();

        $librarydata = array(
            'title' => 'Test',
            'machineName' => 'TestLibrary',
            'majorVersion' => '1',
            'minorVersion' => '0',
            'patchVersion' => '2',
            'runnable' => 1,
            'fullscreen' => 1,
            'preloadedJs' => array(
                array(
                    'path' => 'js/name.min.js'
                )
            ),
            'preloadedCss' => array(
                array(
                    'path' => 'css/name.css'
                )
            ),
            'dropLibraryCss' => array(
                array(
                    'machineName' => 'Name2'
                )
            )
        );

        // Create a new library.
        $this->framework->saveLibraryData($librarydata);

        $library = $DB->get_record('h5p_libraries', ['machinename' => $librarydata['machineName']]);

        // Make sure the library data was properly saved.
        $this->assertNotEmpty($library);
        $this->assertNotEmpty($librarydata['libraryId']);
        $this->assertEquals($librarydata['title'], $library->title);
        $this->assertEquals($librarydata['machineName'], $library->machinename);
        $this->assertEquals($librarydata['majorVersion'], $library->majorversion);
        $this->assertEquals($librarydata['minorVersion'], $library->minorversion);
        $this->assertEquals($librarydata['patchVersion'], $library->patchversion);
        $this->assertEquals($librarydata['preloadedJs'][0]['path'], $library->preloadedjs);
        $this->assertEquals($librarydata['preloadedCss'][0]['path'], $library->preloadedcss);
        $this->assertEquals($librarydata['dropLibraryCss'][0]['machineName'], $library->droplibrarycss);
    }

    /**
     * Test the behaviour of saveLibraryData() when saving (updating) data for an existing library.
     */
    public function test_saveLibraryData_existing_library() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library record.
        $library = $generator->create_library_record('TestLibrary', 'Test', 1, 0, 2);

        $librarydata = array(
            'libraryId' => $library->id,
            'title' => 'Test1',
            'machineName' => 'TestLibrary',
            'majorVersion' => '1',
            'minorVersion' => '2',
            'patchVersion' => '2',
            'runnable' => 1,
            'fullscreen' => 1,
            'preloadedJs' => array(
                array(
                    'path' => 'js/name.min.js'
                )
            ),
            'preloadedCss' => array(
                array(
                    'path' => 'css/name.css'
                )
            ),
            'dropLibraryCss' => array(
                array(
                    'machineName' => 'Name2'
                )
            )
        );

        // Update the library.
        $this->framework->saveLibraryData($librarydata, false);

        $library = $DB->get_record('h5p_libraries', ['machinename' => $librarydata['machineName']]);

        // Make sure the library data was properly updated.
        $this->assertNotEmpty($library);
        $this->assertNotEmpty($librarydata['libraryId']);
        $this->assertEquals($librarydata['title'], $library->title);
        $this->assertEquals($librarydata['machineName'], $library->machinename);
        $this->assertEquals($librarydata['majorVersion'], $library->majorversion);
        $this->assertEquals($librarydata['minorVersion'], $library->minorversion);
        $this->assertEquals($librarydata['patchVersion'], $library->patchversion);
        $this->assertEquals($librarydata['preloadedJs'][0]['path'], $library->preloadedjs);
        $this->assertEquals($librarydata['preloadedCss'][0]['path'], $library->preloadedcss);
        $this->assertEquals($librarydata['dropLibraryCss'][0]['machineName'], $library->droplibrarycss);
    }

    /**
     * Test the behaviour of insertContent().
     */
    public function test_insertContent() {
        global $DB;

        $this->resetAfterTest();

        $content = array(
            'params' => json_encode(['param1' => 'Test']),
            'library' => array(
                'libraryId' => 1
            ),
            'disable' => 8
        );

        // Insert h5p content.
        $contentid = $this->framework->insertContent($content);

        // Get the entered content from the db.
        $dbcontent = $DB->get_record('h5p', ['id' => $contentid]);

        // Make sure the h5p content was properly inserted.
        $this->assertNotEmpty($dbcontent);
        $this->assertEquals($content['params'], $dbcontent->jsoncontent);
        $this->assertEquals($content['library']['libraryId'], $dbcontent->mainlibraryid);
        $this->assertEquals($content['disable'], $dbcontent->displayoptions);
    }

    /**
     * Test the behaviour of insertContent().
     */
    public function test_insertContent_latestlibrary() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create a library record.
        $lib = $generator->create_library_record('TestLibrary', 'Test', 1, 1, 2);

        $content = array(
            'params' => json_encode(['param1' => 'Test']),
            'library' => array(
                'libraryId' => 0,
                'machineName' => 'TestLibrary',
            ),
            'disable' => 8
        );

        // Insert h5p content.
        $contentid = $this->framework->insertContent($content);

        // Get the entered content from the db.
        $dbcontent = $DB->get_record('h5p', ['id' => $contentid]);

        // Make sure the h5p content was properly inserted.
        $this->assertNotEmpty($dbcontent);
        $this->assertEquals($content['params'], $dbcontent->jsoncontent);
        $this->assertEquals($content['disable'], $dbcontent->displayoptions);
        // As the libraryId was empty, the latest library has been used.
        $this->assertEquals($lib->id, $dbcontent->mainlibraryid);
    }

    /**
     * Test the behaviour of updateContent().
     */
    public function test_updateContent() {
        global $DB;

        $this->resetAfterTest();

        /** @var \core_h5p_generator $generator */
        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library record.
        $lib = $generator->create_library_record('TestLibrary', 'Test', 1, 1, 2);

        // Create an h5p content with 'TestLibrary' as it's main library.
        $contentid = $generator->create_h5p_record($lib->id);

        $content = array(
            'id' => $contentid,
            'params' => json_encode(['param2' => 'Test2']),
            'library' => array(
                'libraryId' => $lib->id
            ),
            'disable' => 8
        );

        // Update the h5p content.
        $this->framework->updateContent($content);

        $h5pcontent = $DB->get_record('h5p', ['id' => $contentid]);

        // Make sure the h5p content was properly updated.
        $this->assertNotEmpty($h5pcontent);
        $this->assertNotEmpty($h5pcontent->pathnamehash);
        $this->assertNotEmpty($h5pcontent->contenthash);
        $this->assertEquals($content['params'], $h5pcontent->jsoncontent);
        $this->assertEquals($content['library']['libraryId'], $h5pcontent->mainlibraryid);
        $this->assertEquals($content['disable'], $h5pcontent->displayoptions);
    }

    /**
     * Test the behaviour of saveLibraryDependencies().
     */
    public function test_saveLibraryDependencies() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library 'Library'.
        $library = $generator->create_library_record('Library', 'Title');
        // Create a library 'DependencyLibrary1'.
        $dependency1 = $generator->create_library_record('DependencyLibrary1', 'DependencyTitle1');
        // Create a library 'DependencyLibrary2'.
        $dependency2 = $generator->create_library_record('DependencyLibrary2', 'DependencyTitle2');

        $dependencies = array(
            array(
                'machineName' => $dependency1->machinename,
                'majorVersion' => $dependency1->majorversion,
                'minorVersion' => $dependency1->minorversion
            ),
            array(
                'machineName' => $dependency2->machinename,
                'majorVersion' => $dependency2->majorversion,
                'minorVersion' => $dependency2->minorversion
            ),
        );

        // Set 'DependencyLibrary1' and 'DependencyLibrary2' as library dependencies of 'Library'.
        $this->framework->saveLibraryDependencies($library->id, $dependencies, 'preloaded');

        $libdependencies = $DB->get_records('h5p_library_dependencies', ['libraryid' => $library->id], 'id ASC');

        // Make sure the library dependencies for 'Library' are properly set.
        $this->assertEquals(2, count($libdependencies));
        $this->assertEquals($dependency1->id, reset($libdependencies)->requiredlibraryid);
        $this->assertEquals($dependency2->id, end($libdependencies)->requiredlibraryid);
    }

    /**
     * Test the behaviour of deleteContentData().
     */
    public function test_deleteContentData() {
        global $DB;

        $this->resetAfterTest();

        /** @var \core_h5p_generator $generator */
        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // For the mod_h5pactivity component, the activity needs to be created too.
        $course = $this->getDataGenerator()->create_course();
        $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
        $this->setUser($user);
        $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
        $activitycontext = \context_module::instance($activity->cmid);
        $filerecord = [
            'contextid' => $activitycontext->id,
            'component' => 'mod_h5pactivity',
            'filearea' => 'package',
            'itemid' => 0,
            'filepath' => '/',
            'filename' => 'dummy.h5p',
            'addxapistate' => true,
        ];

        // Generate some h5p related data.
        $data = $generator->generate_h5p_data(false, $filerecord);
        $h5pid = $data->h5pcontent->h5pid;

        // Make sure the particular h5p content exists in the DB.
        $this->assertNotEmpty($DB->get_record('h5p', ['id' => $h5pid]));
        // Make sure the content libraries exists in the DB.
        $this->assertCount(5, $DB->get_records('h5p_contents_libraries', ['h5pid' => $h5pid]));
        // Make sure the particular xAPI state exists in the DB.
        $records = $DB->get_records('xapi_states');
        $record = reset($records);
        $this->assertCount(1, $records);
        $this->assertNotNull($record->statedata);

        // Delete the h5p content and it's related data.
        $this->framework->deleteContentData($h5pid);

        // The particular h5p content should no longer exist in the db.
        $this->assertEmpty($DB->get_record('h5p', ['id' => $h5pid]));
        // The particular content libraries should no longer exist in the db.
        $this->assertEmpty($DB->get_record('h5p_contents_libraries', ['h5pid' => $h5pid]));
        // The xAPI state should be reseted.
        $records = $DB->get_records('xapi_states');
        $record = reset($records);
        $this->assertCount(1, $records);
        $this->assertNull($record->statedata);
    }

    /**
     * Test the behaviour of resetContentUserData().
     */
    public function test_resetContentUserData() {
        global $DB;

        $this->resetAfterTest();

        /** @var \core_h5p_generator $generator */
        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // For the mod_h5pactivity component, the activity needs to be created too.
        $course = $this->getDataGenerator()->create_course();
        $user = $this->getDataGenerator()->create_and_enrol($course, 'student');
        $this->setUser($user);
        $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
        $activitycontext = \context_module::instance($activity->cmid);
        $filerecord = [
            'contextid' => $activitycontext->id,
            'component' => 'mod_h5pactivity',
            'filearea' => 'package',
            'itemid' => 0,
            'filepath' => '/',
            'filename' => 'dummy.h5p',
            'addxapistate' => true,
        ];

        // Generate some h5p related data.
        $data = $generator->generate_h5p_data(false, $filerecord);
        $h5pid = $data->h5pcontent->h5pid;

        // Make sure the H5P content, libraries and xAPI state exist in the DB.
        $this->assertNotEmpty($DB->get_record('h5p', ['id' => $h5pid]));
        $this->assertCount(5, $DB->get_records('h5p_contents_libraries', ['h5pid' => $h5pid]));
        $records = $DB->get_records('xapi_states');
        $record = reset($records);
        $this->assertCount(1, $records);
        $this->assertNotNull($record->statedata);

        // Reset the user data associated to this H5P content.
        $this->framework->resetContentUserData($h5pid);

        // The H5P content should still exist in the db.
        $this->assertNotEmpty($DB->get_record('h5p', ['id' => $h5pid]));
        // The particular content libraries should still exist in the db.
        $this->assertCount(5, $DB->get_records('h5p_contents_libraries', ['h5pid' => $h5pid]));
        // The xAPI state should still exist in the db, but should be reset.
        $records = $DB->get_records('xapi_states');
        $record = reset($records);
        $this->assertCount(1, $records);
        $this->assertNull($record->statedata);
    }

    /**
     * Test the behaviour of deleteLibraryUsage().
     */
    public function test_deleteLibraryUsage() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate some h5p related data.
        $data = $generator->generate_h5p_data();
        $h5pid = $data->h5pcontent->h5pid;

        // Get the h5p content libraries from the DB.
        $h5pcontentlibraries = $DB->get_records('h5p_contents_libraries', ['h5pid' => $h5pid]);

        // The particular h5p content should have 5 content libraries.
        $this->assertNotEmpty($h5pcontentlibraries);
        $this->assertCount(5, $h5pcontentlibraries);

        // Delete the h5p content and it's related data.
        $this->framework->deleteLibraryUsage($h5pid);

        // Get the h5p content libraries from the DB.
        $h5pcontentlibraries = $DB->get_record('h5p_contents_libraries', ['h5pid' => $h5pid]);

        // The particular h5p content libraries should no longer exist in the db.
        $this->assertEmpty($h5pcontentlibraries);
    }

    /**
     * Test the behaviour of test_saveLibraryUsage().
     */
    public function test_saveLibraryUsage() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create a library 'Library'.
        $library = $generator->create_library_record('Library', 'Title');
        // Create a library 'DependencyLibrary1'.
        $dependency1 = $generator->create_library_record('DependencyLibrary1', 'DependencyTitle1');
        // Create a library 'DependencyLibrary2'.
        $dependency2 = $generator->create_library_record('DependencyLibrary2', 'DependencyTitle2');
        // Create an h5p content with 'Library' as it's main library.
        $contentid = $generator->create_h5p_record($library->id);

        $dependencies = array(
            array(
                'library' => array(
                    'libraryId' => $dependency1->id,
                    'machineName' => $dependency1->machinename,
                    'dropLibraryCss' => $dependency1->droplibrarycss
                ),
                'type' => 'preloaded',
                'weight' => 1
            ),
            array(
                'library' => array(
                    'libraryId' => $dependency2->id,
                    'machineName' => $dependency2->machinename,
                    'dropLibraryCss' => $dependency2->droplibrarycss
                ),
                'type' => 'preloaded',
                'weight' => 2
            ),
        );

        // Save 'DependencyLibrary1' and 'DependencyLibrary2' as h5p content libraries.
        $this->framework->saveLibraryUsage($contentid, $dependencies);

        // Get the h5p content libraries from the DB.
        $libdependencies = $DB->get_records('h5p_contents_libraries', ['h5pid' => $contentid], 'id ASC');

        // Make sure that 'DependencyLibrary1' and 'DependencyLibrary2' are properly set as h5p content libraries.
        $this->assertEquals(2, count($libdependencies));
        $this->assertEquals($dependency1->id, reset($libdependencies)->libraryid);
        $this->assertEquals($dependency2->id, end($libdependencies)->libraryid);
    }

    /**
     * Test the behaviour of getLibraryUsage() without skipping a particular h5p content.
     */
    public function test_getLibraryUsage_no_skip_content() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $generateddata = $generator->generate_h5p_data();
        // The Id of the library 'Library1'.
        $library1id = $generateddata->lib1->data->id;
        // The Id of the library 'Library2'.
        $library2id = $generateddata->lib2->data->id;
        // The Id of the library 'Library5'.
        $library5id = $generateddata->lib5->data->id;

        // Get the library usage for 'Library1' (do not skip content).
        $data = $this->framework->getLibraryUsage($library1id);

        $expected = array(
            'content' => 1,
            'libraries' => 1
        );

        // Make sure 'Library1' is used by 1 content and is a dependency to 1 library.
        $this->assertEquals($expected, $data);

        // Get the library usage for 'Library2' (do not skip content).
        $data = $this->framework->getLibraryUsage($library2id);

        $expected = array(
            'content' => 1,
            'libraries' => 2,
        );

        // Make sure 'Library2' is used by 1 content and is a dependency to 2 libraries.
        $this->assertEquals($expected, $data);

         // Get the library usage for 'Library5' (do not skip content).
        $data = $this->framework->getLibraryUsage($library5id);

        $expected = array(
            'content' => 0,
            'libraries' => 1,
        );

        // Make sure 'Library5' is not used by any content and is a dependency to 1 library.
        $this->assertEquals($expected, $data);
    }

    /**
     * Test the behaviour of getLibraryUsage() when skipping a particular content.
     */
    public function test_getLibraryUsage_skip_content() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $generateddata = $generator->generate_h5p_data();
        // The Id of the library 'Library1'.
        $library1id = $generateddata->lib1->data->id;

        // Get the library usage for 'Library1' (skip content).
        $data = $this->framework->getLibraryUsage($library1id, true);
        $expected = array(
            'content' => -1,
            'libraries' => 1,
        );

        // Make sure 'Library1' is a dependency to 1 library.
        $this->assertEquals($expected, $data);
    }

    /**
     * Test the behaviour of loadLibrary() when requesting an existing library.
     */
    public function test_loadLibrary_existing_library() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $generateddata = $generator->generate_h5p_data();
        // The library data of 'Library1'.
        $library1 = $generateddata->lib1->data;
        // The library data of 'Library5'.
        $library5 = $generateddata->lib5->data;

        // The preloaded dependencies.
        $preloadeddependencies = array();

        foreach ($generateddata->lib1->dependencies as $preloadeddependency) {
            $preloadeddependencies[] = array(
                'machineName' => $preloadeddependency->machinename,
                'majorVersion' => $preloadeddependency->majorversion,
                'minorVersion' => $preloadeddependency->minorversion
            );
        }

        // Create a dynamic dependency.
        $generator->create_library_dependency_record($library1->id, $library5->id, 'dynamic');

        $dynamicdependencies[] = array(
            'machineName' => $library5->machinename,
            'majorVersion' => $library5->majorversion,
            'minorVersion' => $library5->minorversion
        );

        // Load 'Library1' data.
        $data = $this->framework->loadLibrary($library1->machinename, $library1->majorversion,
            $library1->minorversion);

        $expected = array(
            'libraryId' => $library1->id,
            'title' => $library1->title,
            'machineName' => $library1->machinename,
            'majorVersion' => $library1->majorversion,
            'minorVersion' => $library1->minorversion,
            'patchVersion' => $library1->patchversion,
            'runnable' => $library1->runnable,
            'fullscreen' => $library1->fullscreen,
            'embedTypes' => $library1->embedtypes,
            'preloadedJs' => $library1->preloadedjs,
            'preloadedCss' => $library1->preloadedcss,
            'dropLibraryCss' => $library1->droplibrarycss,
            'semantics' => $library1->semantics,
            'preloadedDependencies' => $preloadeddependencies,
            'dynamicDependencies' => $dynamicdependencies
        );

        // Make sure the 'Library1' data is properly loaded.
        $this->assertEquals($expected, $data);
    }

    /**
     * Test the behaviour of loadLibrary() when requesting a non-existent library.
     */
    public function test_loadLibrary_non_existent_library() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $generator->generate_h5p_data();

        // Attempt to load a non-existent library.
        $data = $this->framework->loadLibrary('MissingLibrary', 1, 2);

        // Make sure nothing is loaded.
        $this->assertFalse($data);
    }

    /**
     * Test the behaviour of loadLibrarySemantics().
     *
     * @dataProvider loadLibrarySemantics_provider
     * @param array $libraryrecords Array containing data for the library creation
     * @param array $testlibrary Array containing the test library data
     * @param string $expected The expected semantics value
     **/
    public function test_loadLibrarySemantics(array $libraryrecords, array $testlibrary, string $expected): void {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        foreach ($libraryrecords as $library) {
            call_user_func_array([$generator, 'create_library_record'], $library);
        }

        $this->assertEquals($expected, $this->framework->loadLibrarySemantics(
            $testlibrary['machinename'], $testlibrary['majorversion'], $testlibrary['minorversion']));
    }

    /**
     * Data provider for test_loadLibrarySemantics().
     *
     * @return array
     */
    public function loadLibrarySemantics_provider(): array {

        $semantics = json_encode(
            [
                'type' => 'text',
                'name' => 'text',
                'label' => 'Plain text',
                'description' => 'Please add some text'
            ]
        );

        return [
            'Library with semantics' => [
                [
                    ['Library1', 'Lib1', 1, 1, 2, $semantics],
                ],
                [
                    'machinename' => 'Library1',
                    'majorversion' => 1,
                    'minorversion' => 1
                ],
                $semantics,
            ],
            'Library without semantics' => [
                [
                    ['Library2', 'Lib2', 1, 2, 2, ''],
                ],
                [
                    'machinename' => 'Library2',
                    'majorversion' => 1,
                    'minorversion' => 2
                ],
                '',
            ]
        ];
    }

    /**
     * Test the behaviour of alterLibrarySemantics().
     */
    public function test_alterLibrarySemantics() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        $semantics = json_encode(
            array(
                'type' => 'text',
                'name' => 'text',
                'label' => 'Plain text',
                'description' => 'Please add some text'
            )
        );

        // Create a library 'Library1' with semantics.
        $library1 = $generator->create_library_record('Library1', 'Lib1', 1, 1, 2, $semantics);

        $updatedsemantics = array(
            'type' => 'text',
            'name' => 'updated text',
            'label' => 'Updated text',
            'description' => 'Please add some text'
        );

        // Alter the semantics of 'Library1'.
        $this->framework->alterLibrarySemantics($updatedsemantics, 'Library1', 1, 1);

        // Get the semantics of 'Library1' from the DB.
        $currentsemantics = $DB->get_field('h5p_libraries', 'semantics', array('id' => $library1->id));

        // The semantics for Library1 shouldn't be updated.
        $this->assertEquals($semantics, $currentsemantics);
    }

    /**
     * Test the behaviour of deleteLibraryDependencies() when requesting to delete the
     * dependencies of an existing library.
     */
    public function test_deleteLibraryDependencies_existing_library() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();
        // The data of the library 'Library1'.
        $library1 = $data->lib1->data;

        // Get the dependencies of 'Library1'.
        $dependencies = $DB->get_records('h5p_library_dependencies', ['libraryid' => $library1->id]);
        // The 'Library1' should have 3 dependencies ('Library2', 'Library3', 'Library4').
        $this->assertCount(3, $dependencies);

        // Delete the dependencies of 'Library1'.
        $this->framework->deleteLibraryDependencies($library1->id);

        $dependencies = $DB->get_records('h5p_library_dependencies', ['libraryid' => $library1->id]);
        // The 'Library1' should have 0 dependencies.
        $this->assertCount(0, $dependencies);
    }

    /**
     * Test the behaviour of deleteLibraryDependencies() when requesting to delete the
     * dependencies of a non-existent library.
     */
    public function test_deleteLibraryDependencies_non_existent_library() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();
        // The data of the library 'Library1'.
        $library1 = $data->lib1->data;

        // Get the dependencies of 'Library1'.
        $dependencies = $DB->get_records('h5p_library_dependencies', ['libraryid' => $library1->id]);
        // The 'Library1' should have 3 dependencies ('Library2', 'Library3', 'Library4').
        $this->assertCount(3, $dependencies);

        // Delete the dependencies of a non-existent library.
        $this->framework->deleteLibraryDependencies(0);

        $dependencies = $DB->get_records('h5p_library_dependencies', ['libraryid' => $library1->id]);
        // The 'Library1' should have 3 dependencies.
        $this->assertCount(3, $dependencies);
    }

    /**
     * Test the behaviour of deleteLibrary().
     */
    public function test_deleteLibrary() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data(true);
        // The data of the 'Library1' library.
        $library1 = $data->lib1->data;

        // Get the library dependencies of 'Library1'.
        $dependencies = $DB->get_records('h5p_library_dependencies', ['libraryid' => $library1->id]);

        // The 'Library1' should have 3 library dependencies ('Library2', 'Library3', 'Library4').
        $this->assertCount(3, $dependencies);

        // Return the created 'Library1' files.
        $libraryfiles = $DB->get_records('files',
            array(
                'component' => \core_h5p\file_storage::COMPONENT,
                'filearea' => \core_h5p\file_storage::LIBRARY_FILEAREA,
                'itemid' => $library1->id
            )
        );

        // The library ('Library1') should have 7 related folders/files.
        $this->assertCount(7, $libraryfiles);

        // Delete the library.
        $this->framework->deleteLibrary($library1);

        $lib1 = $DB->get_record('h5p_libraries', ['machinename' => $library1->machinename]);
        $dependencies = $DB->get_records('h5p_library_dependencies', ['libraryid' => $library1->id]);
        $libraryfiles = $DB->get_records('files',
            array(
                'component' => \core_h5p\file_storage::COMPONENT,
                'filearea' => \core_h5p\file_storage::LIBRARY_FILEAREA,
                'itemid' => $library1->id
            )
        );

        // The 'Library1' should not exist.
        $this->assertEmpty($lib1);
        // The library ('Library1')  should have 0 dependencies.
        $this->assertCount(0, $dependencies);
        // The library (library1) should have 0 related folders/files.
        $this->assertCount(0, $libraryfiles);
    }

    /**
     * Test the behaviour of loadContent().
     */
    public function test_loadContent() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();
        // The Id of the created h5p content.
        $h5pid = $data->h5pcontent->h5pid;
        // Get the h5p content data from the DB.
        $h5p = $DB->get_record('h5p', ['id' => $h5pid]);
        // The data of content's main library ('MainLibrary').
        $mainlibrary = $data->mainlib->data;

        // Load the h5p content.
        $content = $this->framework->loadContent($h5pid);

        $expected = array(
            'id' => $h5p->id,
            'params' => $h5p->jsoncontent,
            'embedType' => 'iframe',
            'disable' => $h5p->displayoptions,
            'title' => $mainlibrary->title,
            'slug' => H5PCore::slugify($mainlibrary->title) . '-' . $h5p->id,
            'filtered' => $h5p->filtered,
            'libraryId' => $mainlibrary->id,
            'libraryName' => $mainlibrary->machinename,
            'libraryMajorVersion' => $mainlibrary->majorversion,
            'libraryMinorVersion' => $mainlibrary->minorversion,
            'libraryEmbedTypes' => $mainlibrary->embedtypes,
            'libraryFullscreen' => $mainlibrary->fullscreen,
            'metadata' => '',
            'pathnamehash' => $h5p->pathnamehash
        );

        $params = json_decode($h5p->jsoncontent);
        if (empty($params->metadata)) {
            $params->metadata = new \stdClass();
        }
        $expected['metadata'] = $params->metadata;
        $expected['params'] = json_encode($params->params ?? $params);

        // The returned content should match the expected array.
        $this->assertEquals($expected, $content);
    }

    /**
     * Test the behaviour of loadContentDependencies() when requesting content dependencies
     * without specifying the dependency type.
     */
    public function test_loadContentDependencies_no_type_defined() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();
        // The Id of the h5p content.
        $h5pid = $data->h5pcontent->h5pid;
        // The content dependencies.
        $dependencies = $data->h5pcontent->contentdependencies;

        // Add Library5 as a content dependency (dynamic dependency type).
        $library5 = $data->lib5->data;
        $generator->create_contents_libraries_record($h5pid, $library5->id, 'dynamic');

        // Get all content dependencies.
        $contentdependencies = $this->framework->loadContentDependencies($h5pid);

        $expected = array();
        foreach ($dependencies as $dependency) {
            $expected[$dependency->machinename] = array(
                'libraryId' => $dependency->id,
                'machineName' => $dependency->machinename,
                'majorVersion' => $dependency->majorversion,
                'minorVersion' => $dependency->minorversion,
                'patchVersion' => $dependency->patchversion,
                'preloadedCss' => $dependency->preloadedcss,
                'preloadedJs' => $dependency->preloadedjs,
                'dropCss' => '0',
                'dependencyType' => 'preloaded'
            );
        }

        $expected = array_merge($expected,
            array(
                'Library5' => array(
                    'libraryId' => $library5->id,
                    'machineName' => $library5->machinename,
                    'majorVersion' => $library5->majorversion,
                    'minorVersion' => $library5->minorversion,
                    'patchVersion' => $library5->patchversion,
                    'preloadedCss' => $library5->preloadedcss,
                    'preloadedJs' => $library5->preloadedjs,
                    'dropCss' => '0',
                    'dependencyType' => 'dynamic'
                )
            )
        );

        // The loaded content dependencies should return 6 libraries.
        $this->assertCount(6, $contentdependencies);
        $this->assertEquals($expected, $contentdependencies);
    }

    /**
     * Test the behaviour of loadContentDependencies() when requesting content dependencies
     * with specifying the dependency type.
     */
    public function test_loadContentDependencies_type_defined() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();
        // The Id of the h5p content.
        $h5pid = $data->h5pcontent->h5pid;
        // The content dependencies.
        $dependencies = $data->h5pcontent->contentdependencies;

        // Add Library5 as a content dependency (dynamic dependency type).
        $library5 = $data->lib5->data;
        $generator->create_contents_libraries_record($h5pid, $library5->id, 'dynamic');

        // Load all content dependencies of dependency type 'preloaded'.
        $preloadeddependencies = $this->framework->loadContentDependencies($h5pid, 'preloaded');

        $expected = array();
        foreach ($dependencies as $dependency) {
            $expected[$dependency->machinename] = array(
                'libraryId' => $dependency->id,
                'machineName' => $dependency->machinename,
                'majorVersion' => $dependency->majorversion,
                'minorVersion' => $dependency->minorversion,
                'patchVersion' => $dependency->patchversion,
                'preloadedCss' => $dependency->preloadedcss,
                'preloadedJs' => $dependency->preloadedjs,
                'dropCss' => '0',
                'dependencyType' => 'preloaded'
            );
        }

        // The loaded content dependencies should return 5 libraries.
        $this->assertCount(5, $preloadeddependencies);
        $this->assertEquals($expected, $preloadeddependencies);

        // Load all content dependencies of dependency type 'dynamic'.
        $dynamicdependencies = $this->framework->loadContentDependencies($h5pid, 'dynamic');

        $expected = array(
            'Library5' => array(
                'libraryId' => $library5->id,
                'machineName' => $library5->machinename,
                'majorVersion' => $library5->majorversion,
                'minorVersion' => $library5->minorversion,
                'patchVersion' => $library5->patchversion,
                'preloadedCss' => $library5->preloadedcss,
                'preloadedJs' => $library5->preloadedjs,
                'dropCss' => '0',
                'dependencyType' => 'dynamic'
            )
        );

        // The loaded content dependencies should now return 1 library.
        $this->assertCount(1, $dynamicdependencies);
        $this->assertEquals($expected, $dynamicdependencies);
    }

    /**
     * Test the behaviour of getOption().
     */
    public function test_getOption(): void {
        $this->resetAfterTest();

        // Get value for display_option_download.
        $value = $this->framework->getOption(H5PCore::DISPLAY_OPTION_DOWNLOAD);
        $expected = H5PDisplayOptionBehaviour::CONTROLLED_BY_AUTHOR_DEFAULT_OFF;
        $this->assertEquals($expected, $value);

        // Get value for display_option_embed using default value (it should be ignored).
        $value = $this->framework->getOption(H5PCore::DISPLAY_OPTION_EMBED, H5PDisplayOptionBehaviour::NEVER_SHOW);
        $expected = H5PDisplayOptionBehaviour::CONTROLLED_BY_AUTHOR_DEFAULT_OFF;
        $this->assertEquals($expected, $value);

        // Get value for unexisting setting without default.
        $value = $this->framework->getOption('unexistingsetting');
        $expected = false;
        $this->assertEquals($expected, $value);

        // Get value for unexisting setting with default.
        $value = $this->framework->getOption('unexistingsetting', 'defaultvalue');
        $expected = 'defaultvalue';
        $this->assertEquals($expected, $value);
    }

    /**
     * Test the behaviour of setOption().
     */
    public function test_setOption(): void {
        $this->resetAfterTest();

        // Set value for 'newsetting' setting.
        $name = 'newsetting';
        $value = $this->framework->getOption($name);
        $this->assertEquals(false, $value);
        $newvalue = 'value1';
        $this->framework->setOption($name, $newvalue);
        $value = $this->framework->getOption($name);
        $this->assertEquals($newvalue, $value);

        // Set value for display_option_download and then get it again. Check it hasn't changed.
        $name = H5PCore::DISPLAY_OPTION_DOWNLOAD;
        $newvalue = H5PDisplayOptionBehaviour::NEVER_SHOW;
        $this->framework->setOption($name, $newvalue);
        $value = $this->framework->getOption($name);
        $expected = H5PDisplayOptionBehaviour::CONTROLLED_BY_AUTHOR_DEFAULT_OFF;
        $this->assertEquals($expected, $value);
    }

    /**
     * Test the behaviour of updateContentFields().
     */
    public function test_updateContentFields() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create 'Library1' library.
        $library1 = $generator->create_library_record('Library1', 'Lib1', 1, 1, 2);
        // Create 'Library2' library.
        $library2 = $generator->create_library_record('Library2', 'Lib2', 1, 1, 2);

        // Create an h5p content with 'Library1' as it's main library.
        $h5pid = $generator->create_h5p_record($library1->id, 'iframe');

        $updatedata = array(
            'jsoncontent' => json_encode(['value' => 'test']),
            'mainlibraryid' => $library2->id
        );

        // Update h5p content fields.
        $this->framework->updateContentFields($h5pid, $updatedata);

        // Get the h5p content from the DB.
        $h5p = $DB->get_record('h5p', ['id' => $h5pid]);

        $expected = json_encode(['value' => 'test']);

        // Make sure the h5p content fields are properly updated.
        $this->assertEquals($expected, $h5p->jsoncontent);
        $this->assertEquals($library2->id, $h5p->mainlibraryid);
    }

    /**
     * Test the behaviour of clearFilteredParameters().
     */
    public function test_clearFilteredParameters() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create 3 libraries.
        $library1 = $generator->create_library_record('Library1', 'Lib1', 1, 1, 2);
        $library2 = $generator->create_library_record('Library2', 'Lib2', 1, 1, 2);
        $library3 = $generator->create_library_record('Library3', 'Lib3', 1, 1, 2);

        // Create h5p content with 'Library1' as a main library.
        $h5pcontentid1 = $generator->create_h5p_record($library1->id);
        // Create h5p content with 'Library1' as a main library.
        $h5pcontentid2 = $generator->create_h5p_record($library1->id);
        // Create h5p content with 'Library2' as a main library.
        $h5pcontentid3 = $generator->create_h5p_record($library2->id);
        // Create h5p content with 'Library3' as a main library.
        $h5pcontentid4 = $generator->create_h5p_record($library3->id);

        $h5pcontent1 = $DB->get_record('h5p', ['id' => $h5pcontentid1]);
        $h5pcontent2 = $DB->get_record('h5p', ['id' => $h5pcontentid2]);
        $h5pcontent3 = $DB->get_record('h5p', ['id' => $h5pcontentid3]);
        $h5pcontent4 = $DB->get_record('h5p', ['id' => $h5pcontentid4]);

        // The filtered parameters should be present in each h5p content.
        $this->assertNotEmpty($h5pcontent1->filtered);
        $this->assertNotEmpty($h5pcontent2->filtered);
        $this->assertNotEmpty($h5pcontent3->filtered);
        $this->assertNotEmpty($h5pcontent4->filtered);

        // Clear the filtered parameters for contents that have library1 and library3 as
        // their main library.
        $this->framework->clearFilteredParameters([$library1->id, $library3->id]);

        $h5pcontent1 = $DB->get_record('h5p', ['id' => $h5pcontentid1]);
        $h5pcontent2 = $DB->get_record('h5p', ['id' => $h5pcontentid2]);
        $h5pcontent3 = $DB->get_record('h5p', ['id' => $h5pcontentid3]);
        $h5pcontent4 = $DB->get_record('h5p', ['id' => $h5pcontentid4]);

        // The filtered parameters should be still present only for the content that has
        // library 2 as a main library.
        $this->assertEmpty($h5pcontent1->filtered);
        $this->assertEmpty($h5pcontent2->filtered);
        $this->assertNotEmpty($h5pcontent3->filtered);
        $this->assertEmpty($h5pcontent4->filtered);
    }

    /**
     * Test the behaviour of getNumNotFiltered().
     */
    public function test_getNumNotFiltered() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Create 3 libraries.
        $library1 = $generator->create_library_record('Library1', 'Lib1', 1, 1, 2);
        $library2 = $generator->create_library_record('Library2', 'Lib2', 1, 1, 2);
        $library3 = $generator->create_library_record('Library3', 'Lib3', 1, 1, 2);

        // Create h5p content with library1 as a main library.
        $h5pcontentid1 = $generator->create_h5p_record($library1->id);
        // Create h5p content with library1 as a main library.
        $h5pcontentid2 = $generator->create_h5p_record($library1->id);
        // Create h5p content with library2 as a main library.
        $h5pcontentid3 = $generator->create_h5p_record($library2->id);
        // Create h5p content with library3 as a main library.
        $h5pcontentid4 = $generator->create_h5p_record($library3->id);

        $h5pcontent1 = $DB->get_record('h5p', ['id' => $h5pcontentid1]);
        $h5pcontent2 = $DB->get_record('h5p', ['id' => $h5pcontentid2]);
        $h5pcontent3 = $DB->get_record('h5p', ['id' => $h5pcontentid3]);
        $h5pcontent4 = $DB->get_record('h5p', ['id' => $h5pcontentid4]);

        // The filtered parameters should be present in each h5p content.
        $this->assertNotEmpty($h5pcontent1->filtered);
        $this->assertNotEmpty($h5pcontent2->filtered);
        $this->assertNotEmpty($h5pcontent3->filtered);
        $this->assertNotEmpty($h5pcontent4->filtered);

        // Clear the filtered parameters for contents that have library1 and library3 as
        // their main library.
        $this->framework->clearFilteredParameters([$library1->id, $library3->id]);

        $countnotfiltered = $this->framework->getNumNotFiltered();

        // 3 contents don't have their parameters filtered.
        $this->assertEquals(3, $countnotfiltered);
    }

    /**
     * Test the behaviour of getNumContent().
     */
    public function test_getNumContent() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();

        // The 'MainLibrary' library data.
        $mainlibrary = $data->mainlib->data;

        // The 'Library1' library data.
        $library1 = $data->lib1->data;

        // Create new h5p content with MainLibrary as a main library.
        $generator->create_h5p_record($mainlibrary->id);

        // Get the number of h5p contents that are using 'MainLibrary' as their main library.
        $countmainlib = $this->framework->getNumContent($mainlibrary->id);

        // Get the number of h5p contents that are using 'Library1' as their main library.
        $countlib1 = $this->framework->getNumContent($library1->id);

        // Make sure that 2 contents are using MainLibrary as their main library.
        $this->assertEquals(2, $countmainlib);
        // Make sure that 0 contents are using Library1 as their main library.
        $this->assertEquals(0, $countlib1);
    }

    /**
     * Test the behaviour of getNumContent() when certain contents are being skipped.
     */
    public function test_getNumContent_skip_content() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();

        // The 'MainLibrary' library data.
        $mainlibrary = $data->mainlib->data;

        // Create new h5p content with MainLibrary as a main library.
        $h5pcontentid = $generator->create_h5p_record($mainlibrary->id);

        // Get the number of h5p contents that are using 'MainLibrary' as their main library.
        // Skip the newly created content $h5pcontentid.
        $countmainlib = $this->framework->getNumContent($mainlibrary->id, [$h5pcontentid]);

        // Make sure that 1 content is returned instead of 2 ($h5pcontentid being skipped).
        $this->assertEquals(1, $countmainlib);
    }

    /**
     * Test the behaviour of isContentSlugAvailable().
     */
    public function test_isContentSlugAvailable() {
        $this->resetAfterTest();

        $slug = 'h5p-test-slug-1';

        // Currently this returns always true. The slug is generated as a unique value for
        // each h5p content and it is not stored in the h5p content table.
        $isslugavailable = $this->framework->isContentSlugAvailable($slug);

        $this->assertTrue($isslugavailable);
    }

    /**
     * Test that a record is stored for cached assets.
     */
    public function test_saveCachedAssets() {
        global $DB;

        $this->resetAfterTest();

        $libraries = array(
            array(
                'machineName' => 'H5P.TestLib',
                'libraryId' => 405,
            ),
            array(
                'FontAwesome' => 'FontAwesome',
                'libraryId' => 406,
            ),
            array(
                'machineName' => 'H5P.SecondLib',
                'libraryId' => 407,
            ),
        );

        $key = 'testhashkey';

        $this->framework->saveCachedAssets($key, $libraries);

        $records = $DB->get_records('h5p_libraries_cachedassets');

        $this->assertCount(3, $records);
    }

    /**
     * Test that the correct libraries are removed from the cached assets table
     */
    public function test_deleteCachedAssets() {
        global $DB;

        $this->resetAfterTest();

        $libraries = array(
            array(
                'machineName' => 'H5P.TestLib',
                'libraryId' => 405,
            ),
            array(
                'FontAwesome' => 'FontAwesome',
                'libraryId' => 406,
            ),
            array(
                'machineName' => 'H5P.SecondLib',
                'libraryId' => 407,
            ),
        );

        $key1 = 'testhashkey';
        $this->framework->saveCachedAssets($key1, $libraries);

        $libraries = array(
            array(
                'machineName' => 'H5P.DiffLib',
                'libraryId' => 408,
            ),
            array(
                'FontAwesome' => 'FontAwesome',
                'libraryId' => 406,
            ),
            array(
                'machineName' => 'H5P.ThirdLib',
                'libraryId' => 409,
            ),
        );

        $key2 = 'secondhashkey';
        $this->framework->saveCachedAssets($key2, $libraries);

        $libraries = array(
            array(
                'machineName' => 'H5P.AnotherDiffLib',
                'libraryId' => 410,
            ),
            array(
                'FontAwesome' => 'NotRelated',
                'libraryId' => 411,
            ),
            array(
                'machineName' => 'H5P.ForthLib',
                'libraryId' => 412,
            ),
        );

        $key3 = 'threeforthewin';
        $this->framework->saveCachedAssets($key3, $libraries);

        $records = $DB->get_records('h5p_libraries_cachedassets');
        $this->assertCount(9, $records);

        // Selecting one library id will result in all related library entries also being deleted.
        // Going to use the FontAwesome library id. The first two hashes should be returned.
        $hashes = $this->framework->deleteCachedAssets(406);
        $this->assertCount(2, $hashes);
        $index = array_search($key1, $hashes);
        $this->assertEquals($key1, $hashes[$index]);
        $index = array_search($key2, $hashes);
        $this->assertEquals($key2, $hashes[$index]);
        $index = array_search($key3, $hashes);
        $this->assertFalse($index);

        // Check that the records have been removed as well.
        $records = $DB->get_records('h5p_libraries_cachedassets');
        $this->assertCount(3, $records);
    }

    /**
     * Test the behaviour of getLibraryContentCount().
     */
    public function test_getLibraryContentCount() {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        // Generate h5p related data.
        $data = $generator->generate_h5p_data();

        // The 'MainLibrary' library data.
        $mainlibrary = $data->mainlib->data;

        // The 'Library2' library data.
        $library2 = $data->lib2->data;

        // Create new h5p content with Library2 as it's main library.
        $generator->create_h5p_record($library2->id);

        // Create new h5p content with MainLibrary as it's main library.
        $generator->create_h5p_record($mainlibrary->id);

        $countlibrarycontent = $this->framework->getLibraryContentCount();

        $expected = array(
            "{$mainlibrary->machinename} {$mainlibrary->majorversion}.{$mainlibrary->minorversion}" => 2,
            "{$library2->machinename} {$library2->majorversion}.{$library2->minorversion}" => 1,
        );

        // MainLibrary and Library1 are currently main libraries to the existing h5p contents.
        // Should return the number of cases where MainLibrary and Library1 are main libraries to an h5p content.
        $this->assertEquals($expected, $countlibrarycontent);
    }

    /**
     * Test the behaviour of test_libraryHasUpgrade().
     *
     * @dataProvider libraryHasUpgrade_provider
     * @param array $libraryrecords Array containing data for the library creation
     * @param array $testlibrary Array containing the test library data
     * @param bool $expected The expectation whether the library is patched or not
     **/
    public function test_libraryHasUpgrade(array $libraryrecords, array $testlibrary, bool $expected): void {
        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');

        foreach ($libraryrecords as $library) {
            call_user_func_array([$generator, 'create_library_record'], $library);
        }

        $this->assertEquals($expected, $this->framework->libraryHasUpgrade($testlibrary));
    }

    /**
     * Data provider for test_libraryHasUpgrade().
     *
     * @return array
     */
    public function libraryHasUpgrade_provider(): array {
        return [
            'Lower major version; Identical lower version' => [
                [
                    ['Library', 'Lib', 2, 2],
                ],
                [
                    'machineName' => 'Library',
                    'majorVersion' => 1,
                    'minorVersion' => 2
                ],
                true,
            ],
            'Major version identical; Lower minor version' => [
                [
                    ['Library', 'Lib', 2, 2],
                ],
                [
                    'machineName' => 'Library',
                    'majorVersion' => 2,
                    'minorVersion' => 1
                ],
                true,
            ],
            'Major version identical; Minor version identical' => [
                [
                    ['Library', 'Lib', 2, 2],
                ],
                [
                    'machineName' => 'Library',
                    'majorVersion' => 2,
                    'minorVersion' => 2
                ],
                false,
            ],
            'Major version higher; Minor version identical' => [
                [
                    ['Library', 'Lib', 2, 2],
                ],
                [
                    'machineName' => 'Library',
                    'majorVersion' => 3,
                    'minorVersion' => 2
                ],
                false,
            ],
            'Major version identical; Minor version newer' => [
                [
                    ['Library', 'Lib', 2, 2],
                ],
                [
                    'machineName' => 'Library',
                    'majorVersion' => 2,
                    'minorVersion' => 4
                ],
                false,
            ]
        ];
    }


    /**
     * Test the behaviour of get_latest_library_version().
     */
    public function test_get_latest_library_version() {
        global $DB;

        $this->resetAfterTest();

        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
        // Create a library record.
        $machinename = 'TestLibrary';
        $lib1 = $generator->create_library_record($machinename, 'Test', 1, 1, 2);
        $lib2 = $generator->create_library_record($machinename, 'Test', 1, 2, 1);

        $content = array(
            'params' => json_encode(['param1' => 'Test']),
            'library' => array(
                'libraryId' => 0,
                'machineName' => 'TestLibrary',
            ),
            'disable' => 8
        );

        // Get the latest id (at this point, should be lib2).
        $latestlib = $this->framework->get_latest_library_version($machinename);
        $this->assertEquals($lib2->id, $latestlib->id);

        // Get the latest id (at this point, should be lib3).
        $lib3 = $generator->create_library_record($machinename, 'Test', 2, 1, 0);
        $latestlib = $this->framework->get_latest_library_version($machinename);
        $this->assertEquals($lib3->id, $latestlib->id);

        // Get the latest id (at this point, should be still lib3).
        $lib4 = $generator->create_library_record($machinename, 'Test', 1, 1, 3);
        $latestlib = $this->framework->get_latest_library_version($machinename);
        $this->assertEquals($lib3->id, $latestlib->id);

        // Get the latest id (at this point, should be lib5).
        $lib5 = $generator->create_library_record($machinename, 'Test', 2, 1, 6);
        $latestlib = $this->framework->get_latest_library_version($machinename);
        $this->assertEquals($lib5->id, $latestlib->id);
    }
}
