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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body