Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace communication_matrix;
  18  
  19  use communication_matrix\local\command;
  20  use communication_matrix\local\spec\v1p7;
  21  use communication_matrix\local\spec\features;
  22  use communication_matrix\tests\fixtures\mocked_matrix_client;
  23  use core\http_client;
  24  use GuzzleHttp\Handler\MockHandler;
  25  use GuzzleHttp\HandlerStack;
  26  use GuzzleHttp\Middleware;
  27  use GuzzleHttp\Psr7\Response;
  28  use moodle_exception;
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  require_once (__DIR__ . '/matrix_client_test_trait.php');
  32  
  33  /**
  34   * Tests for the matrix_client class.
  35   *
  36   * @package    communication_matrix
  37   * @category   test
  38   * @copyright  2023 Andrew Lyons <andrew@nicols.co.uk>
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   * @covers \communication_matrix\matrix_client
  41   * @coversDefaultClass \communication_matrix\matrix_client
  42   */
  43  class matrix_client_test extends \advanced_testcase {
  44      use matrix_client_test_trait;
  45  
  46      /**
  47       * Data provider for valid calls to ::instance.
  48       * @return array
  49       */
  50      public static function instance_provider(): array {
  51          $testcases = [
  52              'Standard versions' => [
  53                  null,
  54                  v1p7::class,
  55              ],
  56          ];
  57  
  58          // Remove a couple of versions.
  59          $versions = self::get_current_versions();
  60          array_pop($versions);
  61          array_pop($versions);
  62  
  63          $testcases['Older server'] = [
  64              $versions,
  65              array_key_last($versions),
  66          ];
  67  
  68          // Limited version compatibility, including newer than we support now.
  69          $testcases['Newer versions with crossover'] = [
  70              [
  71                  'v1.6',
  72                  'v1.7',
  73                  'v7.9',
  74              ],
  75              \communication_matrix\local\spec\v1p7::class,
  76          ];
  77  
  78          return $testcases;
  79      }
  80  
  81      /**
  82       * Test that the instance method returns a valid instance for the given versions.
  83       *
  84       * @dataProvider instance_provider
  85       * @param array|null $versions
  86       * @param string $expectedversion
  87       */
  88      public function test_instance(
  89          ?array $versions,
  90          string $expectedversion,
  91      ): void {
  92          // Create a mock and queue two responses.
  93  
  94          $mock = new MockHandler([
  95              $this->get_mocked_version_response($versions),
  96          ]);
  97          $handlerstack = HandlerStack::create($mock);
  98          $container = [];
  99          $history = Middleware::history($container);
 100          $handlerstack->push($history);
 101          $client = new http_client(['handler' => $handlerstack]);
 102          mocked_matrix_client::set_client($client);
 103  
 104          $instance = mocked_matrix_client::instance(
 105              'https://example.com',
 106              'testtoken',
 107          );
 108  
 109          $this->assertInstanceOf(matrix_client::class, $instance);
 110  
 111          // Only the version API has been called.
 112          $this->assertCount(1, $container);
 113          $request = reset($container);
 114          $this->assertEquals('/_matrix/client/versions', $request['request']->getUri()->getPath());
 115  
 116          // The client should be a v1p7 client as that is the highest compatible version.
 117          $this->assertInstanceOf($expectedversion, $instance);
 118      }
 119  
 120      /**
 121       * Test that the instance method returns a valid instance for the given versions.
 122       */
 123      public function test_instance_cached(): void {
 124          $mock = new MockHandler([
 125              $this->get_mocked_version_response(),
 126              $this->get_mocked_version_response(),
 127          ]);
 128          $handlerstack = HandlerStack::create($mock);
 129          $container = [];
 130          $history = Middleware::history($container);
 131          $handlerstack->push($history);
 132          $client = new http_client(['handler' => $handlerstack]);
 133          mocked_matrix_client::set_client($client);
 134  
 135          $instance = mocked_matrix_client::instance('https://example.com', 'testtoken');
 136  
 137          $this->assertInstanceOf(matrix_client::class, $instance);
 138  
 139          // Only the version API has been called.
 140          $this->assertCount(1, $container);
 141  
 142          // Call the API again. It should not lead to additional fetches.
 143          $instance = mocked_matrix_client::instance('https://example.com', 'testtoken');
 144          $instance = mocked_matrix_client::instance('https://example.com', 'testtoken');
 145          $this->assertCount(1, $container);
 146  
 147          // But a different endpoint will.
 148          $instance = mocked_matrix_client::instance('https://example.org', 'testtoken');
 149          $this->assertCount(2, $container);
 150      }
 151  
 152      /**
 153       * Test that the instance method throws an appropriate exception if no support is found.
 154       */
 155      public function test_instance_no_support(): void {
 156          // Create a mock and queue two responses.
 157  
 158          $mock = new MockHandler([
 159              $this->get_mocked_version_response(['v99.9']),
 160          ]);
 161          $handlerstack = HandlerStack::create($mock);
 162          $container = [];
 163          $history = Middleware::history($container);
 164          $handlerstack->push($history);
 165          $client = new http_client(['handler' => $handlerstack]);
 166          mocked_matrix_client::set_client($client);
 167  
 168          $this->expectException(moodle_exception::class);
 169          $this->expectExceptionMessage('No supported Matrix API versions found.');
 170  
 171          mocked_matrix_client::instance(
 172              'https://example.com',
 173              'testtoken',
 174          );
 175      }
 176  
 177      /**
 178       * Test the feature implementation check methods.
 179       *
 180       * @covers ::implements_feature
 181       * @covers ::get_supported_versions
 182       * @dataProvider implements_feature_provider
 183       * @param string $version
 184       * @param array|string $features
 185       * @param bool $expected
 186       */
 187      public function test_implements_feature(
 188          string $version,
 189          array|string $features,
 190          bool $expected,
 191      ): void {
 192          $instance = $this->get_mocked_instance_for_version($version);
 193          $this->assertEquals($expected, $instance->implements_feature($features));
 194      }
 195  
 196      /**
 197       * Test the feature implementation requirement methods.
 198       *
 199       * @covers ::implements_feature
 200       * @covers ::get_supported_versions
 201       * @covers ::require_feature
 202       * @dataProvider implements_feature_provider
 203       * @param string $version
 204       * @param array|string $features
 205       * @param bool $expected
 206       */
 207      public function test_require_feature(
 208          string $version,
 209          array|string $features,
 210          bool $expected,
 211      ): void {
 212          $instance = $this->get_mocked_instance_for_version($version);
 213  
 214          if ($expected) {
 215              $this->assertEmpty($instance->require_feature($features));
 216          } else {
 217              $this->expectException('moodle_exception');
 218              $instance->require_feature($features);
 219          }
 220      }
 221  
 222      /**
 223       * Test the feature implementation requirement methods for a require all.
 224       *
 225       * @covers ::implements_feature
 226       * @covers ::get_supported_versions
 227       * @covers ::require_feature
 228       * @covers ::require_features
 229       * @dataProvider require_features_provider
 230       * @param string $version
 231       * @param array|string $features
 232       * @param bool $expected
 233       */
 234      public function test_require_features(
 235          string $version,
 236          array|string $features,
 237          bool $expected,
 238      ): void {
 239          $instance = $this->get_mocked_instance_for_version($version);
 240  
 241          if ($expected) {
 242              $this->assertEmpty($instance->require_features($features));
 243          } else {
 244              $this->expectException('moodle_exception');
 245              $instance->require_features($features);
 246          }
 247      }
 248  
 249      /**
 250       * Data provider for feature implementation check tests.
 251       *
 252       * @return array
 253       */
 254      public static function implements_feature_provider(): array {
 255          return [
 256              'Basic supported feature' => [
 257                  'v1.7',
 258                  features\matrix\media_create_v1::class,
 259                  true,
 260              ],
 261              'Basic unsupported feature' => [
 262                  'v1.6',
 263                  features\matrix\media_create_v1::class,
 264                  false,
 265              ],
 266              '[supported] as array' => [
 267                  'v1.6',
 268                  [features\matrix\create_room_v3::class],
 269                  true,
 270              ],
 271              '[supported, supported] as array' => [
 272                  'v1.6',
 273                  [
 274                      features\matrix\create_room_v3::class,
 275                      features\matrix\update_room_avatar_v3::class,
 276                  ],
 277                  true,
 278              ],
 279              '[unsupported] as array' => [
 280                  'v1.6',
 281                  [
 282                      features\matrix\media_create_v1::class,
 283                  ],
 284                  false,
 285              ],
 286              '[unsupported, supported] as array' => [
 287                  'v1.6',
 288                  [
 289                      features\matrix\media_create_v1::class,
 290                      features\matrix\update_room_avatar_v3::class,
 291                  ],
 292                  true,
 293              ],
 294          ];
 295      }
 296  
 297      /**
 298       * Data provider for feature implementation check tests.
 299       *
 300       * @return array
 301       */
 302      public static function require_features_provider(): array {
 303          // We'll just add to the standard testcases.
 304          $testcases = array_map(static function (array $testcase): array {
 305              $testcase[1] = [$testcase[1]];
 306              return $testcase;
 307          }, self::implements_feature_provider());
 308  
 309          $testcases['Require many supported features'] = [
 310              'v1.6',
 311              [
 312                  features\matrix\create_room_v3::class,
 313                  features\matrix\update_room_avatar_v3::class,
 314              ],
 315              true,
 316          ];
 317  
 318          $testcases['Require many including an unsupported feature'] = [
 319              'v1.6',
 320              [
 321                  features\matrix\create_room_v3::class,
 322                  features\matrix\media_create_v1::class,
 323              ],
 324              false,
 325          ];
 326  
 327          $testcases['Require many including an unsupported feature which has an alternate'] = [
 328              'v1.6',
 329              [
 330                  features\matrix\create_room_v3::class,
 331                  [
 332                      features\matrix\media_create_v1::class,
 333                      features\matrix\update_room_avatar_v3::class,
 334                  ],
 335              ],
 336              true,
 337          ];
 338  
 339          return $testcases;
 340      }
 341  
 342      /**
 343       * Test the get_version method.
 344       *
 345       * @param string $version
 346       * @param string $expectedversion
 347       * @dataProvider get_version_provider
 348       * @covers ::get_version
 349       * @covers ::get_version_from_classname
 350       */
 351      public function test_get_version(
 352          string $version,
 353          string $expectedversion,
 354      ): void {
 355          $instance = $this->get_mocked_instance_for_version($version);
 356          $this->assertEquals($expectedversion, $instance->get_version());
 357      }
 358  
 359      /**
 360       * Data provider for get_version tests.
 361       *
 362       * @return array
 363       */
 364      public static function get_version_provider(): array {
 365          return [
 366              ['v1.1', '1.1'],
 367              ['v1.7', '1.7'],
 368          ];
 369      }
 370  
 371      /**
 372       * Tests the meets_version method.
 373       *
 374       * @param string $version The version of the API to test against
 375       * @param string $testversion The version to test
 376       * @param bool $expected Whether the version meets the requirement
 377       * @dataProvider meets_version_provider
 378       * @covers ::meets_version
 379       */
 380      public function test_meets_version(
 381          string $version,
 382          string $testversion,
 383          bool $expected,
 384      ): void {
 385          $instance = $this->get_mocked_instance_for_version($version);
 386          $this->assertEquals($expected, $instance->meets_version($testversion));
 387      }
 388  
 389      /**
 390       * Tests the requires_version method.
 391       *
 392       * @param string $version The version of the API to test against
 393       * @param string $testversion The version to test
 394       * @param bool $expected Whether the version meets the requirement
 395       * @dataProvider meets_version_provider
 396       * @covers ::requires_version
 397       */
 398      public function test_requires_version(
 399          string $version,
 400          string $testversion,
 401          bool $expected,
 402      ): void {
 403          $instance = $this->get_mocked_instance_for_version($version);
 404  
 405          if ($expected) {
 406              $this->assertEmpty($instance->requires_version($testversion));
 407          } else {
 408              $this->expectException('moodle_exception');
 409              $instance->requires_version($testversion);
 410          }
 411      }
 412  
 413      /**
 414       * Data provider for meets_version tests.
 415       *
 416       * @return array
 417       */
 418      public static function meets_version_provider(): array {
 419          return [
 420              'Same version' => ['v1.1', '1.1', true],
 421              'Same version latest' => ['v1.7', '1.7', true],
 422              'Newer version rejected' => ['v1.1', '1.7', false],
 423              'Older version accepted' => ['v1.7', '1.1', true],
 424          ];
 425      }
 426  
 427      /**
 428       * Test the execute method with a command.
 429       *
 430       * @covers ::execute
 431       */
 432      public function test_command_is_executed(): void {
 433          $historycontainer = [];
 434          $mock = new MockHandler();
 435  
 436          $instance = $this->get_mocked_instance_for_version('v1.6', $historycontainer, $mock);
 437          $command = new command(
 438              $instance,
 439              method: 'GET',
 440              endpoint: 'test/endpoint',
 441              params: [
 442                  'test' => 'test',
 443              ],
 444          );
 445  
 446          $mock->append(new Response(200));
 447  
 448          $rc = new \ReflectionClass($instance);
 449          $rcm = $rc->getMethod('execute');
 450          $rcm->setAccessible(true);
 451          $result = $rcm->invoke($instance, $command);
 452  
 453          $this->assertEquals(200, $result->getStatusCode());
 454          $this->assertCount(1, $historycontainer);
 455          $request = array_shift($historycontainer);
 456          $this->assertEquals('GET', $request['request']->getMethod());
 457          $this->assertEquals('/test/endpoint', $request['request']->getUri()->getPath());
 458      }
 459  }