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 core\context; 20 use core_communication\api; 21 use core_communication\communication_test_helper_trait; 22 use core_communication\processor; 23 use stored_file; 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once (__DIR__ . '/matrix_test_helper_trait.php'); 28 require_once (__DIR__ . '/../../../tests/communication_test_helper_trait.php'); 29 30 /** 31 * Class communication_feature_test to test the matrix features implemented using the core interfaces. 32 * 33 * @package communication_matrix 34 * @category test 35 * @copyright 2023 Safat Shahin <safat.shahin@moodle.com> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 * @covers \communication_matrix\communication_feature 38 * @coversDefaultClass \communication_matrix\communication_feature 39 */ 40 class communication_feature_test extends \advanced_testcase { 41 use matrix_test_helper_trait; 42 use communication_test_helper_trait; 43 44 public function setUp(): void { 45 parent::setUp(); 46 $this->resetAfterTest(); 47 $this->setup_communication_configs(); 48 $this->initialise_mock_server(); 49 } 50 51 /** 52 * Test create or update chat room. 53 * 54 * @covers ::create_chat_room 55 */ 56 public function test_create_chat_room(): void { 57 // Set up the test data first. 58 $communication = \core_communication\api::load_by_instance( 59 context: \core\context\system::instance(), 60 component: 'communication_matrix', 61 instancetype: 'example', 62 instanceid: 1, 63 provider: 'communication_matrix', 64 ); 65 66 $communication->create_and_configure_room( 67 communicationroomname: 'Room name', 68 instance: (object) [ 69 'matrixroomtopic' => 'A fun topic', 70 ], 71 ); 72 73 // phpcs:ignore moodle.Commenting.InlineComment.DocBlock 74 /** @var communication_feature */ 75 $provider = $communication->get_room_provider(); 76 $this->assertInstanceOf( 77 communication_feature::class, 78 $provider, 79 ); 80 81 // Run the create_chat_room task. 82 $result = $provider->create_chat_room(); 83 $this->assertTrue($result); 84 85 // Ensure that a room_id was set. 86 $this->assertNotEmpty($provider->get_room_id()); 87 88 // Fetch the back office room data. 89 $remoteroom = $this->backoffice_get_room(); 90 91 // The roomid set in the database must match the one set on the remote server. 92 $this->assertEquals( 93 $remoteroom->room_id, 94 $provider->get_room_id(), 95 ); 96 97 // The name is a feature of the communication API itself. 98 $this->assertEquals( 99 'Room name', 100 $communication->get_room_name(), 101 ); 102 $this->assertEquals( 103 $communication->get_room_name(), 104 $remoteroom->name, 105 ); 106 107 // The topic is a Matrix feature. 108 $roomconfig = $provider->get_room_configuration(); 109 $this->assertEquals( 110 'A fun topic', 111 $roomconfig->get_topic(), 112 ); 113 $this->assertEquals( 114 $remoteroom->topic, 115 $roomconfig->get_topic(), 116 ); 117 118 // The avatar features are checked in a separate test. 119 } 120 121 /** 122 * Test update of a chat room. 123 * 124 * @covers ::update_chat_room 125 */ 126 public function test_update_chat_room(): void { 127 $communication = $this->create_room( 128 roomname: 'Our room name', 129 roomtopic: 'Our room topic', 130 ); 131 132 // phpcs:ignore moodle.Commenting.InlineComment.DocBlock 133 /** @var communication_feature */ 134 $provider = $communication->get_room_provider(); 135 $this->assertInstanceOf( 136 communication_feature::class, 137 $provider, 138 ); 139 140 // Update the room name. 141 // Note: We have to update the record via the API, and then call the provider update method. 142 // That's because the update is performed asynchronously. 143 $communication->update_room( 144 communicationroomname: 'Our updated room name', 145 ); 146 $provider->reload(); 147 148 // Now call the provider's update method. 149 $provider->update_chat_room(); 150 151 // And assert that it was updated remotely. 152 $remoteroom = $this->backoffice_get_room(); 153 154 $this->assertEquals( 155 'Our updated room name', 156 $communication->get_room_name(), 157 ); 158 $this->assertEquals( 159 $communication->get_room_name(), 160 $remoteroom->name, 161 ); 162 // The remote topic should not have changed. 163 $this->assertEquals( 164 'Our room topic', 165 $remoteroom->topic, 166 ); 167 168 // Now update just the topic. 169 // First in the local API. 170 $communication->update_room( 171 instance: (object) [ 172 'matrixroomtopic' => 'Our updated room topic', 173 ], 174 ); 175 176 $provider->reload(); 177 178 // Then call the provider's update method to actually perform the change. 179 $provider->update_chat_room(); 180 181 // And assert that it was updated remotely. 182 $remoteroom = $this->backoffice_get_room(); 183 184 $this->assertEquals( 185 'Our updated room topic', 186 $provider->get_room_configuration()->get_topic(), 187 ); 188 189 // The remote topic should have been updated. 190 $this->assertEquals( 191 'Our updated room topic', 192 $remoteroom->topic, 193 ); 194 195 // The name should not have changed. 196 $this->assertEquals( 197 'Our updated room name', 198 $communication->get_room_name(), 199 ); 200 } 201 202 /** 203 * Test delete chat room. 204 * 205 * @covers ::delete_chat_room 206 */ 207 public function test_delete_chat_room(): void { 208 $communication = $this->create_room(); 209 210 $processor = $communication->get_processor(); 211 $provider = $communication->get_room_provider(); 212 $room = matrix_room::load_by_processor_id($processor->get_id()); 213 214 // Run the delete method. 215 $this->assertTrue($provider->delete_chat_room()); 216 217 // The record of the room should have been removed. 218 $this->assertNull(matrix_room::load_by_processor_id($processor->get_id())); 219 220 // But the room itself shoudl exist. 221 $matrixroomdata = $this->get_matrix_room_data($room->get_room_id()); 222 223 $this->assertNotEmpty($matrixroomdata); 224 $this->assertEquals($processor->get_room_name(), $matrixroomdata->name); 225 $this->assertEquals($room->get_topic(), $matrixroomdata->topic); 226 } 227 228 /** 229 * Test update room avatar. 230 * 231 * @covers ::update_room_avatar 232 * @dataProvider avatar_provider 233 */ 234 public function test_update_room_avatar( 235 ?string $before, 236 ?string $after, 237 ): void { 238 $this->setAdminUser(); 239 240 // Create a new draft file. 241 $logo = $this->create_communication_file('moodle_logo.jpg', 'logo.jpg'); 242 $circle = $this->create_communication_file('circle.png', 'circle.png'); 243 244 if ($before === 'logo') { 245 $before = $logo; 246 } else if ($before === 'circle') { 247 $before = $circle; 248 } 249 250 if ($after === 'logo') { 251 $after = $logo; 252 } else if ($after === 'circle') { 253 $after = $circle; 254 } 255 256 $communication = $this->create_matrix_room( 257 component: 'communication_matrix', 258 itemtype: 'example_room', 259 itemid: 1, 260 roomname: 'Example room name', 261 roomavatar: $before, 262 ); 263 264 // Confirm that the avatar was set remotely. 265 $remoteroom = $this->backoffice_get_room(); 266 267 if ($before) { 268 $this->assertStringEndsWith($before->get_filename(), $remoteroom->avatar); 269 $avatarcontent = download_file_content($remoteroom->avatar); 270 $this->assertEquals($before->get_content(), $avatarcontent); 271 } else { 272 $this->assertEmpty($remoteroom->avatar); 273 } 274 275 // Reload the API instance as the information stored has changed. 276 $communication->reload(); 277 278 // Update the avatar with the 'after' avatar. 279 $communication->update_room( 280 avatar: $after, 281 ); 282 $this->run_all_adhoc_tasks(); 283 284 // Confirm that the avatar was updated remotely. 285 $remoteroom = $this->backoffice_get_room(); 286 287 if ($after) { 288 $this->assertStringEndsWith($after->get_filename(), $remoteroom->avatar); 289 $avatarcontent = download_file_content($remoteroom->avatar); 290 $this->assertEquals($after->get_content(), $avatarcontent); 291 } else { 292 $this->assertEmpty($remoteroom->avatar); 293 } 294 } 295 296 /** 297 * Tests for setting and updating the room avatar. 298 * 299 * @return array 300 */ 301 public static function avatar_provider(): array { 302 return [ 303 'Empty to avatar' => [ 304 null, 305 'circle', 306 ], 307 'Avatar to empty' => [ 308 'circle', 309 null, 310 ], 311 'Avatar to new avatar' => [ 312 'circle', 313 'logo', 314 ], 315 ]; 316 } 317 318 /** 319 * Test get chat room url. 320 * 321 * @covers ::get_chat_room_url 322 */ 323 public function test_get_chat_room_url(): void { 324 $communication = $this->create_room(); 325 326 $provider = $communication->get_room_provider(); 327 328 $url = $provider->get_chat_room_url(); 329 $this->assertNotNull($url); 330 331 // Fetch the room information from the server. 332 $remoteroom = $this->backoffice_get_room(); 333 334 $this->assertStringEndsWith( 335 $remoteroom->room_id, 336 $url, 337 ); 338 } 339 340 /** 341 * Test create members. 342 * 343 * @covers ::create_members 344 * @covers ::add_registered_matrix_user_to_room 345 */ 346 public function test_create_members(): void { 347 $user = $this->getDataGenerator()->create_user(); 348 349 $communication = $this->create_room( 350 members: [ 351 $user->id, 352 ], 353 ); 354 355 $remoteroom = $this->backoffice_get_room(); 356 $this->assertCount(1, $remoteroom->members); 357 $member = reset($remoteroom->members); 358 $this->assertStringStartsWith("@{$user->username}", $member->userid); 359 } 360 361 /** 362 * Test add/remove members from room. 363 * 364 * @covers ::remove_members_from_room 365 * @covers ::add_members_to_room 366 * @covers ::add_registered_matrix_user_to_room 367 * @covers ::check_room_membership 368 * @covers ::set_matrix_power_levels 369 */ 370 public function test_add_and_remove_members_from_room(): void { 371 $user = $this->getDataGenerator()->create_user(); 372 $user2 = $this->getDataGenerator()->create_user(); 373 374 $communication = $this->create_room(); 375 $provider = $communication->get_room_user_provider(); 376 377 $remoteroom = $this->backoffice_get_room(); 378 $this->assertCount(0, $remoteroom->members); 379 380 // Add the members to the room. 381 $provider->add_members_to_room([$user->id, $user2->id]); 382 383 // Ensure that they have been created. 384 $remoteroom = $this->backoffice_get_room(); 385 $this->assertCount(2, $remoteroom->members); 386 387 $userids = array_map(fn($member) => $member->userid, $remoteroom->members); 388 $userids = array_map(fn($userid) => substr($userid, 0, strpos($userid, ':')), $userids); 389 $this->assertContains("@{$user->username}", $userids); 390 $this->assertContains("@{$user2->username}", $userids); 391 392 // Remove member from matrix room. 393 $provider->remove_members_from_room([$user->id]); 394 395 // Ensure that they have been removed. 396 $remoteroom = $this->backoffice_get_room(); 397 $members = (array) $remoteroom->members; 398 $this->assertCount(1, $members); 399 $userids = array_map(fn ($member) => $member->userid, $members); 400 $userids = array_map(fn ($userid) => substr($userid, 0, strpos($userid, ':')), $userids); 401 $this->assertNotContains("@{$user->username}", $userids); 402 $this->assertContains("@{$user2->username}", $userids); 403 } 404 405 /** 406 * Test update of room membership. 407 * 408 * @covers ::update_room_membership 409 * @covers ::set_matrix_power_levels 410 * @covers ::is_power_levels_update_required 411 * @covers ::get_user_allowed_power_level 412 */ 413 public function test_update_room_membership(): void { 414 $this->resetAfterTest(); 415 416 global $DB; 417 418 // Create a new room. 419 $course = $this->get_course('Sampleroom', 'none'); 420 $coursecontext = \context_course::instance($course->id); 421 $user = $this->get_user(); 422 423 $communication = $this->create_room( 424 component: 'core_course', 425 itemtype: 'coursecommunication', 426 itemid: $course->id, 427 roomname: 'sampleroom', 428 roomtopic: 'sampltopic', 429 roomavatar: null, 430 members: [$user->id], 431 context: $coursecontext, 432 ); 433 434 $provider = $communication->get_room_user_provider(); 435 436 // Add the members to the room. 437 $provider->add_members_to_room([$user->id]); 438 439 // Assign teacher role to the user. 440 $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']); 441 $this->getDataGenerator()->enrol_user($user->id, $course->id); 442 role_assign($teacherrole->id, $user->id, $coursecontext->id); 443 444 // Test the tasks added as the role is a teacher. 445 $provider->update_room_membership([$user->id]); 446 447 $processor = \core_communication\processor::load_by_instance( 448 context: $coursecontext, 449 component: 'core_course', 450 instancetype: 'coursecommunication', 451 instanceid: $course->id, 452 ); 453 $synceduser = $processor->get_instance_userids( 454 synced: true, 455 ); 456 $synceduser = reset($synceduser); 457 458 // Test if the communication user record is synced. 459 $this->assertEquals($user->id, $synceduser); 460 } 461 462 /** 463 * Test the user power level allocation according to context. 464 * 465 * @covers ::get_user_allowed_power_level 466 */ 467 public function test_get_user_allowed_power_level(): void { 468 $this->resetAfterTest(); 469 global $DB; 470 471 // Create users. 472 $user1 = $this->getDataGenerator()->create_user(); 473 $user2 = $this->getDataGenerator()->create_user(); 474 475 $course = $this->get_course(); 476 $coursecontext = \context_course::instance($course->id); 477 $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); 478 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 479 $this->getDataGenerator()->enrol_user($user1->id, $course->id); 480 $this->getDataGenerator()->enrol_user($user2->id, $course->id); 481 // Assign roles. 482 role_assign($teacherrole->id, $user1->id, $coursecontext->id); 483 role_assign($studentrole->id, $user2->id, $coursecontext->id); 484 485 $communicationprocessor = processor::load_by_instance( 486 context: \core\context\course::instance($course->id), 487 component: 'core_course', 488 instancetype: 'coursecommunication', 489 instanceid: $course->id 490 ); 491 492 // Test if the power level is set according to the context. 493 $this->assertEquals( 494 matrix_constants::POWER_LEVEL_MOODLE_MODERATOR, 495 $communicationprocessor->get_room_provider()->get_user_allowed_power_level($user1->id) 496 ); 497 $this->assertEquals( 498 matrix_constants::POWER_LEVEL_DEFAULT, 499 $communicationprocessor->get_room_provider()->get_user_allowed_power_level($user2->id) 500 ); 501 } 502 503 /** 504 * Helper to create a room. 505 * 506 * @param null|string $component 507 * @param null|string $itemtype 508 * @param null|int $itemid 509 * @param null|string $roomname 510 * @param null|string $roomtopic 511 * @param null|stored_file $roomavatar 512 * @param array $members 513 * @return api 514 */ 515 protected function create_room( 516 ?string $component = 'communication_matrix', 517 ?string $itemtype = 'example', 518 ?int $itemid = 1, 519 ?string $roomname = null, 520 ?string $roomtopic = null, 521 ?\stored_file $roomavatar = null, 522 array $members = [], 523 ?context $context = null, 524 ): \core_communication\api { 525 // Create a new room. 526 $communication = \core_communication\api::load_by_instance( 527 context: $context ?? \core\context\system::instance(), 528 component: $component, 529 instancetype: $itemtype, 530 instanceid: $itemid, 531 provider: 'communication_matrix', 532 ); 533 534 $communication->create_and_configure_room( 535 communicationroomname: $roomname ?? 'Room name', 536 avatar: $roomavatar, 537 instance: (object) [ 538 'matrixroomtopic' => $roomtopic ?? 'A fun topic', 539 ], 540 ); 541 542 $communication->add_members_to_room($members); 543 544 // Run the adhoc task. 545 $this->run_all_adhoc_tasks(); 546 547 $communication->reload(); 548 return $communication; 549 } 550 551 /** 552 * Test if the selected provider is configured. 553 * 554 * @covers ::is_configured 555 */ 556 public function test_is_configured(): void { 557 $course = $this->get_course(); 558 $communicationprocessor = processor::load_by_instance( 559 context: \core\context\course::instance($course->id), 560 component: 'core_course', 561 instancetype: 'coursecommunication', 562 instanceid: $course->id 563 ); 564 $this->assertTrue($communicationprocessor->get_room_provider()->is_configured()); 565 566 // Unset communication_matrix settings. 567 unset_config('matrixhomeserverurl', 'communication_matrix'); 568 unset_config('matrixaccesstoken', 'communication_matrix'); 569 $this->assertFalse($communicationprocessor->get_room_provider()->is_configured()); 570 } 571 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body