Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   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  
 160      /**
 161       * Test can join is working for all types
 162       *
 163       * @param int $type
 164       * @param string|null $groupname
 165       * @param int $groupmode
 166       * @param array $canjoin
 167       * @dataProvider get_instance_types_meeting_info
 168       * @covers ::can_join
 169       */
 170      public function test_can_join(int $type, ?string $groupname, int $groupmode, array $canjoin) {
 171          $this->resetAfterTest();
 172          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting($type, $groupname, $groupmode);
 173          $this->setUser($useringroup);
 174          $meeting->update_cache();
 175          $this->assertEquals($canjoin['useringroup'], $meeting->can_join());
 176          if ($meeting->can_join()) {
 177              $meetinginfo = $meeting->get_meeting_info();
 178              $this->assertStringContainsString("The session is in progress.", $meetinginfo->statusmessage);
 179          }
 180          if ($groupname) {
 181              $this->setUser($usernotingroup);
 182              $meeting->update_cache();
 183              $this->assertEquals($canjoin['usernotingroup'], $meeting->can_join());
 184          }
 185      }
 186  
 187      /**
 188       * Test can join is working if opening/closing time are set
 189       *
 190       * @param int $type
 191       * @param string|null $groupname
 192       * @param int $groupmode
 193       * @param array $canjoin
 194       * @param array $dates
 195       * @dataProvider get_data_can_join_with_dates
 196       * @covers ::can_join
 197       */
 198      public function test_can_join_with_dates(int $type, ?string $groupname, int $groupmode, array $canjoin, array $dates) {
 199          // Apply the data provider relative values to now.
 200          array_walk($dates, function(&$val) {
 201              $val = time() + $val;
 202          });
 203          $this->resetAfterTest();
 204          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
 205              $this->prepare_meeting($type, $groupname, $groupmode, true, $dates);
 206          $this->setUser($useringroup);
 207          $meeting->update_cache();
 208          $this->assertEquals($canjoin['useringroup'], $meeting->can_join());
 209          // We check that admin can not join outside opening/closing times either.
 210          $this->setAdminUser();
 211          $this->assertEquals(false, $meeting->can_join());
 212          if ($groupname) {
 213              $this->setUser($usernotingroup);
 214              $meeting->update_cache();
 215              $this->assertEquals($canjoin['usernotingroup'], $meeting->can_join());
 216              $this->setAdminUser();
 217              $this->assertEquals(false, $meeting->can_join());
 218          }
 219      }
 220  
 221      /**
 222       * Test can join is working if the "Wait for moderator to join" setting is set and a moderator has not yet joined.
 223       *
 224       * @covers ::join
 225       * @covers ::join_meeting
 226       */
 227      public function test_join_wait_for_moderator_not_joined() {
 228          $this->resetAfterTest();
 229  
 230          $this->setAdminUser();
 231          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 232          $student = $this->getDataGenerator()->create_and_enrol($this->get_course());
 233          $meetinginfo = [
 234              'course' => $this->get_course()->id,
 235              'type' => instance::TYPE_ALL,
 236              'wait' => 1,
 237          ];
 238          $activity = $bbbgenerator->create_instance($meetinginfo, [
 239              'wait' => 1,
 240          ]);
 241          $instance = instance::get_from_instanceid($activity->id);
 242          $meeting = new meeting($instance);
 243  
 244          // The moderator has not joined.
 245          $this->setUser($student);
 246          $meeting->update_cache();
 247          $this->expectException(\mod_bigbluebuttonbn\local\exceptions\meeting_join_exception::class);
 248          meeting::join_meeting($instance);
 249      }
 250  
 251      /**
 252       * Test can join is working if the "Wait for moderator to join" setting is set and a moderator has already joined.
 253       *
 254       * @covers ::join
 255       * @covers ::join_meeting
 256       */
 257      public function test_join_wait_for_moderator_is_joined() {
 258          $this->resetAfterTest();
 259  
 260          $this->setAdminUser();
 261          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 262          $moderator = $this->getDataGenerator()->create_and_enrol($this->get_course(), 'editingteacher');
 263          $student = $this->getDataGenerator()->create_and_enrol($this->get_course());
 264          $meetinginfo = [
 265              'course' => $this->get_course()->id,
 266              'type' => instance::TYPE_ALL,
 267              'wait' => 1,
 268              'moderators' => 'role:editingteacher',
 269          ];
 270          $activity = $bbbgenerator->create_instance($meetinginfo, [
 271              'wait' => 1,
 272          ]);
 273          $instance = instance::get_from_instanceid($activity->id);
 274          $meeting = new meeting($instance);
 275          $bbbgenerator->create_meeting([
 276              'instanceid' => $instance->get_instance_id(),
 277          ]);
 278  
 279          $this->setUser($moderator);
 280          $meeting->update_cache();
 281          $joinurl = $meeting->join(logger::ORIGIN_BASE);
 282          $this->assertIsString($joinurl);
 283          $this->join_meeting($joinurl);
 284          $meeting->update_cache();
 285          $this->assertCount(1, $meeting->get_attendees());
 286  
 287          // The student can now join the meeting as a moderator is present.
 288          $this->setUser($student);
 289          $joinurl = $meeting->join(logger::ORIGIN_BASE);
 290          $this->assertIsString($joinurl);
 291      }
 292  
 293      /**
 294       * Test can join is working if the "user limit" setting is set and reached.
 295       *
 296       * @covers ::join
 297       * @covers ::join_meeting
 298       */
 299      public function test_join_user_limit_reached() {
 300          $this->resetAfterTest();
 301          set_config('bigbluebuttonbn_userlimit_editable', true);
 302          $this->setAdminUser();
 303          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 304          $moderator = $this->getDataGenerator()->create_and_enrol($this->get_course(), 'editingteacher');
 305          $student1 = $this->getDataGenerator()->create_and_enrol($this->get_course());
 306          $student2 = $this->getDataGenerator()->create_and_enrol($this->get_course());
 307          $meetinginfo = [
 308              'course' => $this->get_course()->id,
 309              'type' => instance::TYPE_ALL,
 310              'userlimit' => 2,
 311          ];
 312          $activity = $bbbgenerator->create_instance($meetinginfo, [
 313              'userlimit' => 2,
 314          ]);
 315          $instance = instance::get_from_instanceid($activity->id);
 316          $meeting = new meeting($instance);
 317          $bbbgenerator->create_meeting([
 318              'instanceid' => $instance->get_instance_id(),
 319          ]);
 320          // Moderator joins the meeting.
 321          $this->setUser($moderator);
 322          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 323          $meeting->update_cache();
 324          $this->assertEquals(1, $meeting->get_participant_count());
 325  
 326          // Student1 joins the meeting.
 327          $this->setUser($student1);
 328          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 329          $meeting->update_cache();
 330          $this->assertEquals(2, $meeting->get_participant_count());
 331          $this->assertTrue($instance->has_user_limit_been_reached($meeting->get_participant_count()));
 332  
 333          // Student2 tries to join but the limit has been reached.
 334          $this->setUser($student2);
 335          $meeting->update_cache();
 336          $this->assertFalse($meeting->can_join());
 337          $this->expectException(\mod_bigbluebuttonbn\local\exceptions\meeting_join_exception::class);
 338          meeting::join_meeting($instance);
 339      }
 340  
 341      /**
 342       * Test that attendees returns the right list of attendees
 343       *
 344       * @covers ::get_attendees
 345       */
 346      public function test_get_attendees() {
 347          $this->resetAfterTest();
 348          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
 349              $this->prepare_meeting(instance::TYPE_ALL, null, NOGROUPS, true);
 350          $this->setUser($useringroup);
 351          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 352          $meeting->update_cache();
 353          $this->assertCount(1, $meeting->get_attendees());
 354          $otheruser = $this->getDataGenerator()->create_and_enrol($this->get_course());
 355          $this->setUser($otheruser);
 356          $meeting->update_cache();
 357          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 358          $meeting->update_cache();
 359          $this->assertCount(2, $meeting->get_attendees());
 360      }
 361  
 362      /**
 363       * Test that attendees returns the right list of attendees
 364       *
 365       * @covers ::get_attendees
 366       */
 367      public function test_participant_count() {
 368          $this->resetAfterTest();
 369          [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
 370              $this->prepare_meeting(instance::TYPE_ALL, null, NOGROUPS, true);
 371          $this->setUser($useringroup);
 372          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 373          $meeting->update_cache();
 374          $meetinginfo = $meeting->get_meeting_info();
 375          $this->assertEquals(1, $meetinginfo->participantcount);
 376          $this->assertEquals(1, $meetinginfo->totalusercount);
 377          $this->assertEquals(0, $meetinginfo->moderatorcount);
 378          $this->setUser($usernotingroup);
 379          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 380          $meeting->update_cache();
 381          $meetinginfo = $meeting->get_meeting_info();
 382          $this->assertEquals(2, $meetinginfo->participantcount);
 383          $this->assertEquals(2, $meetinginfo->totalusercount);
 384          $this->assertEquals(0, $meetinginfo->moderatorcount);
 385          $this->setAdminUser();
 386          $this->join_meeting($meeting->join(logger::ORIGIN_BASE));
 387          $meeting->update_cache();
 388          $meetinginfo = $meeting->get_meeting_info();
 389          $this->assertEquals(2, $meetinginfo->participantcount);
 390          $this->assertEquals(3, $meetinginfo->totalusercount);
 391          $this->assertEquals(1, $meetinginfo->moderatorcount);
 392      }
 393      /**
 394       * Send a join meeting API CALL
 395       *
 396       * @param string $url
 397       */
 398      protected function join_meeting(string $url) {
 399          $curl = new \curl();
 400          $url = new \moodle_url($url);
 401          $curl->get($url->out_omit_querystring(), $url->params());
 402      }
 403  
 404      /**
 405       * Get a list of possible test (dataprovider)
 406       *
 407       * @return array[]
 408       */
 409      public function get_data_can_join_with_dates(): array {
 410          return [
 411              'Instance Type ALL - No Group - Closed in past' => [
 412                  'type' => instance::TYPE_ALL,
 413                  'groupname' => null,
 414                  'groupmode' => NOGROUPS,
 415                  'canjoin' => ['useringroup' => false, 'usernotingroup' => false],
 416                  'dates' => ['openingtime' => -7200, 'closingtime' => -3600]
 417              ],
 418              'Instance Type ALL - No Group - Open in future' => [
 419                  'type' => instance::TYPE_ALL,
 420                  'groupname' => null,
 421                  'groupmode' => NOGROUPS,
 422                  'canjoin' => ['useringroup' => false, 'usernotingroup' => false],
 423                  'dates' => ['openingtime' => 3600, 'closingtime' => 7200]
 424              ],
 425          ];
 426      }
 427  
 428      /**
 429       * Helper to prepare for a meeting
 430       *
 431       * @param int $type
 432       * @param string|null $groupname
 433       * @param int $groupmode
 434       * @param bool $createmeeting
 435       * @param array $dates
 436       * @return array
 437       */
 438      protected function prepare_meeting(int $type, ?string $groupname, int $groupmode = SEPARATEGROUPS, bool $createmeeting = true,
 439          array $dates = []) {
 440          $this->setAdminUser();
 441          $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 442          $groupid = 0;
 443          $useringroup = $this->getDataGenerator()->create_and_enrol($this->get_course());
 444          $usernotingroup = $this->getDataGenerator()->create_and_enrol($this->get_course());
 445          if (!empty($groupname)) {
 446              $groupid = groups_get_group_by_name($this->get_course()->id, $groupname);
 447              $this->getDataGenerator()->create_group_member(['groupid' => $groupid, 'userid' => $useringroup->id]);
 448          }
 449          $meetinginfo = [
 450              'course' => $this->get_course()->id,
 451              'type' => $type
 452          ];
 453          if ($dates) {
 454              $meetinginfo = array_merge($meetinginfo, $dates);
 455          };
 456          $activity = $bbbgenerator->create_instance($meetinginfo, ['groupmode' => $groupmode]);
 457          $instance = instance::get_from_instanceid($activity->id);
 458          if ($groupid) {
 459              $instance->set_group_id($groupid);
 460          }
 461          if ($createmeeting) {
 462              // Create the meetings on the mock server, so we can join it as a simple user.
 463              $bbbgenerator->create_meeting([
 464                  'instanceid' => $instance->get_instance_id(),
 465                  'groupid' => $instance->get_group_id()
 466              ]);
 467          }
 468          $meeting = new meeting($instance);
 469          return [$meeting, $useringroup, $usernotingroup, $groupid, $activity];
 470      }
 471  }