Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401]

   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  /**
  18   * Meeting test.
  19   *
  20   * @package   mod_bigbluebuttonbn
  21   * @copyright 2018 - present, Blindside Networks Inc
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
  24   */
  25  
  26  namespace mod_bigbluebuttonbn;
  27  
  28  use mod_bigbluebuttonbn\test\testcase_helper_trait;
  29  
  30  /**
  31   * Meeting tests class.
  32   *
  33   * @package   mod_bigbluebuttonbn
  34   * @copyright 2018 - present, Blindside Networks Inc
  35   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
  37   * @covers \mod_bigbluebuttonbn\meeting
  38   * @coversDefaultClass \mod_bigbluebuttonbn\meeting
  39   */
  40  class meeting_test extends \advanced_testcase {
  41      use testcase_helper_trait;
  42  
  43      /**
  44       * Setup Test
  45       */
  46      public function setUp(): void {
  47          parent::setUp();
  48          $this->initialise_mock_server();
  49          // We do not force the group mode so we can change the activity group mode during test.
  50          $this->course = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS]);
  51          $this->getDataGenerator()->create_group(['name' => 'G1', 'courseid' => $this->course->id]);
  52          $this->getDataGenerator()->create_group(['name' => 'G2', 'courseid' => $this->course->id]);
  53      }
  54  
  55      /**
  56       * Get a list of possible test (dataprovider)
  57       *
  58       * @return array[]
  59       */
  60      public function get_instance_types_meeting_info(): array {
  61          return [
  62              'Instance Type ALL - No Group' => [
  63                  'type' => instance::TYPE_ALL,
  64                  'groupname' => null,
  65                  'groupmode' => NOGROUPS,
  66                  'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
  67              ],
  68              'Instance Type ALL - Group 1 - Visible groups' => [
  69                  'type' => instance::TYPE_ALL,
  70                  'groupname' => 'G1',
  71                  'groupmode' => VISIBLEGROUPS,
  72                  'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
  73              ],
  74              'Instance Type ALL - Group 1 - Separate groups' => [
  75                  'type' => instance::TYPE_ALL,
  76                  'groupname' => 'G1',
  77                  'groupmode' => SEPARATEGROUPS,
  78                  'canjoin' => ['useringroup' => true, 'usernotingroup' => false],
  79              ],
  80              'Instance Type ROOM Only - No Group' => [
  81                  'type' => instance::TYPE_ROOM_ONLY,
  82                  'groupname' => null,
  83                  'groupmode' => NOGROUPS,
  84                  'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
  85              ],
  86              'Instance Type ROOM Only - Group 1 - Visible groups' => [
  87                  'type' => instance::TYPE_ROOM_ONLY,
  88                  'groupname' => 'G1',
  89                  'groupmode' => VISIBLEGROUPS,
  90                  'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
  91              ],
  92              'Instance Type ROOM Only - Group 1 - Separate groups' => [
  93                  'type' => instance::TYPE_ROOM_ONLY,
  94                  'groupname' => 'G1',
  95                  'groupmode' => SEPARATEGROUPS,
  96                  'canjoin' => ['useringroup' => true, 'usernotingroup' => false],
  97              ],
  98              'Instance Type Recording Only - No Group' => [
  99                  'type' => instance::TYPE_RECORDING_ONLY,
 100                  'groupname' => null,
 101                  'groupmode' => NOGROUPS,
 102                  'canjoin' => ['useringroup' => false, 'usernotingroup' => false]
 103              ],
 104              'Instance Type Recording Only - Group 1' => [
 105                  'type' => instance::TYPE_RECORDING_ONLY,
 106                  'groupname' => 'G1',
 107                  'groupmode' => VISIBLEGROUPS,
 108                  'canjoin' => ['useringroup' => false, 'usernotingroup' => false]
 109              ]
 110          ];
 111      }
 112  
 113      /**
 114       * Test that create meeing is working for all types.
 115       *
 116       * @dataProvider get_instance_types_meeting_info
 117       * @param int $type
 118       * @param string|null $groupname
 119       * @covers ::create_meeting
 120       * @covers ::create_meeting_data
 121       * @covers ::create_meeting_metadata
 122       */
 123      public function test_create_meeting(int $type, ?string $groupname) {
 124          $this->resetAfterTest();
 125          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
 126              $this->prepare_meeting($type, $groupname, SEPARATEGROUPS, false);
 127          $meeting->create_meeting();
 128          $meetinginfo = $meeting->get_meeting_info();
 129          $this->assertNotNull($meetinginfo);
 130          $this->assertEquals($activity->id, $meetinginfo->bigbluebuttonbnid);
 131          $this->assertFalse($meetinginfo->statusrunning);
 132          $this->assertStringContainsString("is ready", $meetinginfo->statusmessage);
 133          $this->assertEquals($groupid, $meetinginfo->groupid);
 134      }
 135  
 136      /**
 137       * Test for get meeting info for all types
 138       *
 139       * @param int $type
 140       * @param string|null $groupname
 141       * @dataProvider get_instance_types_meeting_info
 142       * @covers ::get_meeting_info
 143       * @covers ::do_get_meeting_info
 144       */
 145      public function test_get_meeting_info(int $type, ?string $groupname) {
 146          $this->resetAfterTest();
 147          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting($type, $groupname);
 148          $meetinginfo = $meeting->get_meeting_info();
 149          $this->assertNotNull($meetinginfo);
 150          $this->assertEquals($activity->id, $meetinginfo->bigbluebuttonbnid);
 151          $this->assertTrue($meetinginfo->statusrunning);
 152          $this->assertStringContainsString("in progress", $meetinginfo->statusmessage);
 153          $this->assertEquals($groupid, $meetinginfo->groupid);
 154          $meeting->end_meeting();
 155          $meeting->update_cache();
 156          $meetinginfo = $meeting->get_meeting_info();
 157          $this->assertFalse($meetinginfo->statusrunning);
 158  
 159          if ($type == instance::TYPE_ALL) {
 160              $this->assertTrue($meetinginfo->features['showroom']);
 161              $this->assertTrue($meetinginfo->features['showrecordings']);
 162          } else if ($type == instance::TYPE_ROOM_ONLY) {
 163              $this->assertTrue($meetinginfo->features['showroom']);
 164              $this->assertFalse($meetinginfo->features['showrecordings']);
 165          } else if ($type == instance::TYPE_RECORDING_ONLY) {
 166              $this->assertFalse($meetinginfo->features['showroom']);
 167              $this->assertTrue($meetinginfo->features['showrecordings']);
 168          }
 169      }
 170  
 171      /**
 172       * Test can join is working for all types
 173       *
 174       * @param int $type
 175       * @param string|null $groupname
 176       * @param int $groupmode
 177       * @param array $canjoin
 178       * @dataProvider get_instance_types_meeting_info
 179       * @covers ::can_join
 180       */
 181      public function test_can_join(int $type, ?string $groupname, int $groupmode, array $canjoin) {
 182          $this->resetAfterTest();
 183          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting($type, $groupname, $groupmode);
 184          $this->setUser($useringroup);
 185          $meeting->update_cache();
 186          $this->assertEquals($canjoin['useringroup'], $meeting->can_join());
 187          if ($meeting->can_join()) {
 188              $meetinginfo = $meeting->get_meeting_info();
 189              $this->assertStringContainsString("The session is in progress.", $meetinginfo->statusmessage);
 190          }
 191          if ($groupname) {
 192              $this->setUser($usernotingroup);
 193              $meeting->update_cache();
 194              $this->assertEquals($canjoin['usernotingroup'], $meeting->can_join());
 195          }
 196      }
 197  
 198      /**
 199       * Test can join is working if opening/closing time are set
 200       *
 201       * @param int $type
 202       * @param string|null $groupname
 203       * @param int $groupmode
 204       * @param array $canjoin
 205       * @param array $dates
 206       * @dataProvider get_data_can_join_with_dates
 207       * @covers ::can_join
 208       */
 209      public function test_can_join_with_dates(int $type, ?string $groupname, int $groupmode, array $canjoin, array $dates) {
 210          // Apply the data provider relative values to now.
 211          array_walk($dates, function(&$val) {
 212              $val = time() + $val;
 213          });
 214          $this->resetAfterTest();
 215          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
 216              $this->prepare_meeting($type, $groupname, $groupmode, true, $dates);
 217          $this->setUser($useringroup);
 218          $meeting->update_cache();
 219          $this->assertEquals($canjoin['useringroup'], $meeting->can_join());
 220          // We check that admin can not join outside opening/closing times either.
 221          $this->setAdminUser();
 222          $this->assertEquals(false, $meeting->can_join());
 223          if ($groupname) {
 224              $this->setUser($usernotingroup);
 225              $meeting->update_cache();
 226              $this->assertEquals($canjoin['usernotingroup'], $meeting->can_join());
 227              $this->setAdminUser();
 228              $this->assertEquals(false, $meeting->can_join());
 229          }
 230      }
 231  
 232      /**
 233       * Test can join is working if the "Wait for moderator to join" setting is set and a moderator has not yet joined.
 234       *
 235       * @covers ::join
 236       * @covers ::join_meeting
 237       */
 238      public function test_join_wait_for_moderator_not_joined() {
 239          $this->resetAfterTest();
 240  
 241          $this->setAdminUser();
 242          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 243          $student = $this->getDataGenerator()->create_and_enrol($this->get_course());
 244          $meetinginfo = [
 245              'course' => $this->get_course()->id,
 246              'type' => instance::TYPE_ALL,
 247              'wait' => 1,
 248          ];
 249          $activity = $bbbgenerator->create_instance($meetinginfo, [
 250              'wait' => 1,
 251          ]);
 252          $instance = instance::get_from_instanceid($activity->id);
 253          $meeting = new meeting($instance);
 254  
 255          // The moderator has not joined.
 256          $this->setUser($student);
 257          $meeting->update_cache();
 258          $this->expectException(\mod_bigbluebuttonbn\local\exceptions\meeting_join_exception::class);
 259          meeting::join_meeting($instance);
 260      }
 261  
 262      /**
 263       * Test can join is working if the "Wait for moderator to join" setting is set and a moderator has already joined.
 264       *
 265       * @covers ::join
 266       * @covers ::join_meeting
 267       */
 268      public function test_join_wait_for_moderator_is_joined() {
 269          $this->resetAfterTest();
 270  
 271          $this->setAdminUser();
 272          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 273          $moderator = $this->getDataGenerator()->create_and_enrol($this->get_course(), 'editingteacher');
 274          $student = $this->getDataGenerator()->create_and_enrol($this->get_course());
 275          $meetinginfo = [
 276              'course' => $this->get_course()->id,
 277              'type' => instance::TYPE_ALL,
 278              'wait' => 1,
 279              'moderators' => 'role:editingteacher',
 280          ];
 281          $activity = $bbbgenerator->create_instance($meetinginfo, [
 282              'wait' => 1,
 283          ]);
 284          $instance = instance::get_from_instanceid($activity->id);
 285          $meeting = new meeting($instance);
 286          $bbbgenerator->create_meeting([
 287              'instanceid' => $instance->get_instance_id(),
 288          ]);
 289  
 290          $this->setUser($moderator);
 291          $meeting->update_cache();
 292          $joinurl = $meeting->join(logger::ORIGIN_BASE);
 293          $this->assertIsString($joinurl);
 294          $this->join_meeting($joinurl);
 295          $meeting->update_cache();
 296          $this->assertCount(1, $meeting->get_attendees());
 297  
 298          // The student can now join the meeting as a moderator is present.
 299          $this->setUser($student);
 300          $joinurl = $meeting->join(logger::ORIGIN_BASE);
 301          $this->assertIsString($joinurl);
 302      }
 303  
 304      /**
 305       * Test can join is working if the "user limit" setting is set and reached.
 306       *
 307       * @covers ::join
 308       * @covers ::join_meeting
 309       */
 310      public function test_join_user_limit_reached() {
 311          $this->resetAfterTest();
 312          set_config('bigbluebuttonbn_userlimit_editable', true);
 313          $this->setAdminUser();
 314          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 315          $moderator = $this->getDataGenerator()->create_and_enrol($this->get_course(), 'editingteacher');
 316          $student1 = $this->getDataGenerator()->create_and_enrol($this->get_course());
 317          $student2 = $this->getDataGenerator()->create_and_enrol($this->get_course());
 318          $meetinginfo = [
 319              'course' => $this->get_course()->id,
 320              'type' => instance::TYPE_ALL,
 321              'userlimit' => 2,
 322          ];
 323          $activity = $bbbgenerator->create_instance($meetinginfo, [
 324              'userlimit' => 2,
 325          ]);
 326          $instance = instance::get_from_instanceid($activity->id);
 327          $meeting = new meeting($instance);
 328          $bbbgenerator->create_meeting([
 329              'instanceid' => $instance->get_instance_id(),
 330          ]);
 331          // Moderator joins the meeting.
 332          $this->setUser($moderator);
 333          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 334          $meeting->update_cache();
 335          $this->assertEquals(1, $meeting->get_participant_count());
 336  
 337          // Student1 joins the meeting.
 338          $this->setUser($student1);
 339          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 340          $meeting->update_cache();
 341          $this->assertEquals(2, $meeting->get_participant_count());
 342          $this->assertTrue($instance->has_user_limit_been_reached($meeting->get_participant_count()));
 343  
 344          // Student2 tries to join but the limit has been reached.
 345          $this->setUser($student2);
 346          $meeting->update_cache();
 347          $this->assertFalse($meeting->can_join());
 348          $this->expectException(\mod_bigbluebuttonbn\local\exceptions\meeting_join_exception::class);
 349          meeting::join_meeting($instance);
 350      }
 351  
 352      /**
 353       * Test that attendees returns the right list of attendees
 354       *
 355       * @covers ::get_attendees
 356       */
 357      public function test_get_attendees() {
 358          $this->resetAfterTest();
 359          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
 360              $this->prepare_meeting(instance::TYPE_ALL, null, NOGROUPS, true);
 361          $this->setUser($useringroup);
 362          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 363          $meeting->update_cache();
 364          $this->assertCount(1, $meeting->get_attendees());
 365          $otheruser = $this->getDataGenerator()->create_and_enrol($this->get_course());
 366          $this->setUser($otheruser);
 367          $meeting->update_cache();
 368          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 369          $meeting->update_cache();
 370          $this->assertCount(2, $meeting->get_attendees());
 371      }
 372  
 373      /**
 374       * Test that attendees returns the right list of attendees
 375       *
 376       * @covers ::get_attendees
 377       */
 378      public function test_participant_count() {
 379          $this->resetAfterTest();
 380          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
 381              $this->prepare_meeting(instance::TYPE_ALL, null, NOGROUPS, true);
 382          $this->setUser($useringroup);
 383          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 384          $meeting->update_cache();
 385          $meetinginfo = $meeting->get_meeting_info();
 386          $this->assertEquals(1, $meetinginfo->participantcount);
 387          $this->assertEquals(1, $meetinginfo->totalusercount);
 388          $this->assertEquals(0, $meetinginfo->moderatorcount);
 389          $this->setUser($usernotingroup);
 390          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 391          $meeting->update_cache();
 392          $meetinginfo = $meeting->get_meeting_info();
 393          $this->assertEquals(2, $meetinginfo->participantcount);
 394          $this->assertEquals(2, $meetinginfo->totalusercount);
 395          $this->assertEquals(0, $meetinginfo->moderatorcount);
 396          $this->setAdminUser();
 397          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 398          $meeting->update_cache();
 399          $meetinginfo = $meeting->get_meeting_info();
 400          $this->assertEquals(2, $meetinginfo->participantcount);
 401          $this->assertEquals(3, $meetinginfo->totalusercount);
 402          $this->assertEquals(1, $meetinginfo->moderatorcount);
 403      }
 404      /**
 405       * Send a join meeting API CALL
 406       *
 407       * @param string $url
 408       */
 409      protected function join_meeting(string $url) {
 410          $curl = new \curl();
 411          $url = new \moodle_url($url);
 412          $curl->get($url->out_omit_querystring(), $url->params());
 413      }
 414  
 415      /**
 416       * Get a list of possible test (dataprovider)
 417       *
 418       * @return array[]
 419       */
 420      public function get_data_can_join_with_dates(): array {
 421          return [
 422              'Instance Type ALL - No Group - Closed in past' => [
 423                  'type' => instance::TYPE_ALL,
 424                  'groupname' => null,
 425                  'groupmode' => NOGROUPS,
 426                  'canjoin' => ['useringroup' => false, 'usernotingroup' => false],
 427                  'dates' => ['openingtime' => -7200, 'closingtime' => -3600]
 428              ],
 429              'Instance Type ALL - No Group - Open in future' => [
 430                  'type' => instance::TYPE_ALL,
 431                  'groupname' => null,
 432                  'groupmode' => NOGROUPS,
 433                  'canjoin' => ['useringroup' => false, 'usernotingroup' => false],
 434                  'dates' => ['openingtime' => 3600, 'closingtime' => 7200]
 435              ],
 436          ];
 437      }
 438  
 439      /**
 440       * Helper to prepare for a meeting
 441       *
 442       * @param int $type
 443       * @param string|null $groupname
 444       * @param int $groupmode
 445       * @param bool $createmeeting
 446       * @param array $dates
 447       * @return array
 448       */
 449      protected function prepare_meeting(int $type, ?string $groupname, int $groupmode = SEPARATEGROUPS, bool $createmeeting = true,
 450          array $dates = []) {
 451          $this->setAdminUser();
 452          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 453          $groupid = 0;
 454          $useringroup = $this->getDataGenerator()->create_and_enrol($this->get_course());
 455          $usernotingroup = $this->getDataGenerator()->create_and_enrol($this->get_course());
 456          if (!empty($groupname)) {
 457              $groupid = groups_get_group_by_name($this->get_course()->id, $groupname);
 458              $this->getDataGenerator()->create_group_member(['groupid' => $groupid, 'userid' => $useringroup->id]);
 459          }
 460          $meetinginfo = [
 461              'course' => $this->get_course()->id,
 462              'type' => $type
 463          ];
 464          if ($dates) {
 465              $meetinginfo = array_merge($meetinginfo, $dates);
 466          };
 467          $activity = $bbbgenerator->create_instance($meetinginfo, ['groupmode' => $groupmode]);
 468          $instance = instance::get_from_instanceid($activity->id);
 469          if ($groupid) {
 470              $instance->set_group_id($groupid);
 471          }
 472          if ($createmeeting) {
 473              // Create the meetings on the mock server, so we can join it as a simple user.
 474              $bbbgenerator->create_meeting([
 475                  'instanceid' => $instance->get_instance_id(),
 476                  'groupid' => $instance->get_group_id()
 477              ]);
 478          }
 479          $meeting = new meeting($instance);
 480          return [$meeting, $useringroup, $usernotingroup, $groupid, $activity];
 481      }
 482  }