MDL-77917 communication_matrix: Support server API versions

This commit brings in support for multiple versions of the Matrix
specification.

A Matrix server is compromised of a number of individually versioned API
endpoints, for example:

    /_matrix/client/v3/createRoom
    /_matrix/client/v3/rooms/:roomid/joined_members
    /_matrix/media/v1/create

The combination of a large number of these individually versioned
endpoints forms a Matrix Specification version.

For example:

* the /_matrix/media/v1/create endpoint was created for version 1.7 of the
  specification, and does not exist in earlier versions.
* in the future a new behaviour or parameter may be created for the
  `createRoom` endpoint and a new endpoint created at:

    /_matrix/client/v4/createRoom

A single server can support multiple versions of the Matrix
specification. The server declares the versions of the specification
that it supports using a non-versioned endpoint at
`/_matrix/client/versions`.

As a Matrix client, Moodle should:
* query the server version endpoint
* determine the combination of mutually supported Matrix specification
  versions
* create a client instance of the highest-supported version of the
  specification.

For example, if Moodle (Matrix client) and a remote server have the
following support:

```
Moodle:      1.1  1.2  1.3  1.4  1.5  1.6  1.7
Server:  r0  1.1  1.2  1.3  1.4  1.5  1.6
```

The versions in common are 1.1 through 1.6, and version 1.6 would be
chosen.

To avoid duplication and allow for support of future features more
easily, the Moodle client is written as:
* a set of classes named `v1p1` through `v1p7` (currently) which extend
  the `matrix_client` abstract class; and
* a set if PHP traits which provide the implementation for individual
  versioned endpoints.

Each client version then imports any relevant traits which are present
in that version of the Matrix Specification. For example versions 1.1 to
1.6 do _not_ have the `/_matrix/media/v1/create` endpoint so they do not
import this trait. This trait was introduced in version 1.7, so the
trait is included from that version onwards.

In the future, if an endpoint is created which conflicts with an
existing endpoint, then it would be easy to create a new client version
which uses the existing common traits, and adds the new trait.

Each endpoint is written using a `command` class which extends the
Guzzle implementation of the PSR-7 Request interface. This command
class adds support for easy creation of:
* path parameters within the URI
* query parameters
* body parameters

This is done to avoid complex patterns of Request creation which are
repeated for every client endpoint.
This commit is contained in:
Andrew Nicols
2023-08-24 11:59:25 +08:00
parent cccc00954d
commit 914686bc5e
40 changed files with 3326 additions and 1901 deletions
+11 -54
View File
@@ -27,7 +27,7 @@ require_once(__DIR__ . '/communication_test_helper_trait.php');
* @category test
* @copyright 2023 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core_communication\api
* @covers \core_communication\api
*/
class api_test extends \advanced_testcase {
@@ -41,8 +41,6 @@ class api_test extends \advanced_testcase {
/**
* Test the communication plugin list for the form element returns the correct number of plugins.
*
* @covers ::get_communication_plugin_list_for_form
*/
public function test_get_communication_plugin_list_for_form(): void {
$communicationplugins = \core_communication\api::get_communication_plugin_list_for_form();
@@ -54,8 +52,6 @@ class api_test extends \advanced_testcase {
/**
* Test set data to the instance.
*
* @covers ::set_data
*/
public function test_set_data(): void {
$course = $this->get_course();
@@ -80,8 +76,6 @@ class api_test extends \advanced_testcase {
/**
* Test get_current_communication_provider method.
*
* @covers ::get_provider
*/
public function test_get_provider(): void {
$course = $this->get_course();
@@ -95,31 +89,8 @@ class api_test extends \advanced_testcase {
$this->assertEquals('communication_matrix', $communication->get_provider());
}
/**
* Test get_avatar_filerecord method.
*
* @covers ::get_avatar_filerecord
*/
public function test_get_avatar_filerecord(): void {
$course = $this->get_course();
$communication = \core_communication\api::load_by_instance(
'core_course',
'coursecommunication',
$course->id
);
$filerecord = $communication->get_avatar_filerecord('avatar.svg');
$this->assertEquals('avatar.svg', $filerecord->filename);
$this->assertEquals('core_communication', $filerecord->component);
$this->assertEquals('avatar', $filerecord->filearea);
}
/**
* Test set_avatar method.
*
* @covers ::set_avatar
* @covers ::get_avatar_filerecord
*/
public function test_set_avatar(): void {
global $CFG;
@@ -135,26 +106,31 @@ class api_test extends \advanced_testcase {
'moodle_logo.jpg',
);
// Create the room, settingthe avatar.
$communication = \core_communication\api::load_by_instance(
'core_course',
'coursecommunication',
$course->id
$course->id,
);
$communication->create_and_configure_room($selectedcommunication, $communicationroomname, $avatar);
// Reload the communication processor.
$communicationprocessor = processor::load_by_instance(
'core_course',
'coursecommunication',
$course->id
$course->id,
);
$this->assertNotNull($communicationprocessor->get_avatar());
// Compare result.
$this->assertEquals(
$avatar->get_contenthash(),
$communicationprocessor->get_avatar()->get_contenthash(),
);
}
/**
* Test the create_and_configure_room method to add/create tasks.
*
* @covers ::create_and_configure_room
*/
public function test_create_and_configure_room(): void {
// Get the course by disabling communication so that we can create it manually calling the api.
@@ -191,8 +167,6 @@ class api_test extends \advanced_testcase {
/**
* Test the create_and_configure_room method to add/create tasks when no communication provider selected.
*
* @covers ::create_and_configure_room
*/
public function test_create_and_configure_room_without_communication_provider_selected(): void {
// Get the course by disabling communication so that we can create it manually calling the api.
@@ -214,8 +188,6 @@ class api_test extends \advanced_testcase {
/**
* Test update operation.
*
* @covers ::update_room
*/
public function test_update_room(): void {
$course = $this->get_course();
@@ -231,14 +203,6 @@ class api_test extends \advanced_testcase {
);
$communication->update_room($selectedcommunication, $communicationroomname);
// Test the tasks added.
$adhoctask = \core\task\manager::get_adhoc_tasks('\\core_communication\\task\\update_room_task');
// Should be 2 as one for create, another for update.
$this->assertCount(1, $adhoctask);
$adhoctask = reset($adhoctask);
$this->assertInstanceOf('\\core_communication\\task\\update_room_task', $adhoctask);
// Test the communication record exists.
$communicationprocessor = processor::load_by_instance(
'core_course',
@@ -252,8 +216,6 @@ class api_test extends \advanced_testcase {
/**
* Test delete operation.
*
* @covers ::delete_room
*/
public function test_delete_room(): void {
$course = $this->get_course();
@@ -290,9 +252,6 @@ class api_test extends \advanced_testcase {
/**
* Test the update_room_membership for adding adn removing members.
*
* @covers ::add_members_to_room
* @covers ::remove_members_from_room
*/
public function test_update_room_membership(): void {
$course = $this->get_course();
@@ -320,8 +279,6 @@ class api_test extends \advanced_testcase {
/**
* Test the enabled communication plugin list and default.
*
* @covers ::get_enabled_providers_and_default
*/
public function test_get_enabled_providers_and_default(): void {
list($communicationproviders, $defaulprovider) = \core_communication\api::get_enabled_providers_and_default();