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.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Meeting test.
 *
 * @package   mod_bigbluebuttonbn
 * @copyright 2018 - present, Blindside Networks Inc
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
 */

namespace mod_bigbluebuttonbn;

use mod_bigbluebuttonbn\test\testcase_helper_trait;

/**
 * Meeting tests class.
 *
 * @package   mod_bigbluebuttonbn
 * @copyright 2018 - present, Blindside Networks Inc
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
 * @covers \mod_bigbluebuttonbn\meeting
 * @coversDefaultClass \mod_bigbluebuttonbn\meeting
 */
class meeting_test extends \advanced_testcase {
    use testcase_helper_trait;

    /**
     * Setup Test
     */
    public function setUp(): void {
        parent::setUp();
        $this->initialise_mock_server();
        // We do not force the group mode so we can change the activity group mode during test.
        $this->course = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS]);
        $this->getDataGenerator()->create_group(['name' => 'G1', 'courseid' => $this->course->id]);
        $this->getDataGenerator()->create_group(['name' => 'G2', 'courseid' => $this->course->id]);
    }

    /**
     * Get a list of possible test (dataprovider)
     *
     * @return array[]
     */
    public function get_instance_types_meeting_info(): array {
        return [
            'Instance Type ALL - No Group' => [
                'type' => instance::TYPE_ALL,
                'groupname' => null,
                'groupmode' => NOGROUPS,
                'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
            ],
            'Instance Type ALL - Group 1 - Visible groups' => [
                'type' => instance::TYPE_ALL,
                'groupname' => 'G1',
                'groupmode' => VISIBLEGROUPS,
                'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
            ],
            'Instance Type ALL - Group 1 - Separate groups' => [
                'type' => instance::TYPE_ALL,
                'groupname' => 'G1',
                'groupmode' => SEPARATEGROUPS,
                'canjoin' => ['useringroup' => true, 'usernotingroup' => false],
            ],
            'Instance Type ROOM Only - No Group' => [
                'type' => instance::TYPE_ROOM_ONLY,
                'groupname' => null,
                'groupmode' => NOGROUPS,
                'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
            ],
            'Instance Type ROOM Only - Group 1 - Visible groups' => [
                'type' => instance::TYPE_ROOM_ONLY,
                'groupname' => 'G1',
                'groupmode' => VISIBLEGROUPS,
                'canjoin' => ['useringroup' => true, 'usernotingroup' => true],
            ],
            'Instance Type ROOM Only - Group 1 - Separate groups' => [
                'type' => instance::TYPE_ROOM_ONLY,
                'groupname' => 'G1',
                'groupmode' => SEPARATEGROUPS,
                'canjoin' => ['useringroup' => true, 'usernotingroup' => false],
            ],
            'Instance Type Recording Only - No Group' => [
                'type' => instance::TYPE_RECORDING_ONLY,
                'groupname' => null,
                'groupmode' => NOGROUPS,
                'canjoin' => ['useringroup' => false, 'usernotingroup' => false]
            ],
            'Instance Type Recording Only - Group 1' => [
                'type' => instance::TYPE_RECORDING_ONLY,
                'groupname' => 'G1',
                'groupmode' => VISIBLEGROUPS,
                'canjoin' => ['useringroup' => false, 'usernotingroup' => false]
            ]
        ];
    }

    /**
     * Test that create meeing is working for all types.
     *
     * @dataProvider get_instance_types_meeting_info
     * @param int $type
     * @param string|null $groupname
     * @covers ::create_meeting
     * @covers ::create_meeting_data
     * @covers ::create_meeting_metadata
     */
    public function test_create_meeting(int $type, ?string $groupname) {
        $this->resetAfterTest();
        [$meeting, $useringroup, $usernotingroup, $groupid, $activity] =
            $this->prepare_meeting($type, $groupname, SEPARATEGROUPS, false);
        $meeting->create_meeting();
        $meetinginfo = $meeting->get_meeting_info();
        $this->assertNotNull($meetinginfo);
        $this->assertEquals($activity->id, $meetinginfo->bigbluebuttonbnid);
        $this->assertFalse($meetinginfo->statusrunning);
        $this->assertStringContainsString("is ready", $meetinginfo->statusmessage);
        $this->assertEquals($groupid, $meetinginfo->groupid);
    }

    /**
     * Test for get meeting info for all types
     *
     * @param int $type
     * @param string|null $groupname
     * @dataProvider get_instance_types_meeting_info
     * @covers ::get_meeting_info
     * @covers ::do_get_meeting_info
     */
    public function test_get_meeting_info(int $type, ?string $groupname) {
        $this->resetAfterTest();
        [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting($type, $groupname);
        $meetinginfo = $meeting->get_meeting_info();
        $this->assertNotNull($meetinginfo);
        $this->assertEquals($activity->id, $meetinginfo->bigbluebuttonbnid);
        $this->assertTrue($meetinginfo->statusrunning);
        $this->assertStringContainsString("in progress", $meetinginfo->statusmessage);
        $this->assertEquals($groupid, $meetinginfo->groupid);
        $meeting->end_meeting();
        $meeting->update_cache();
        $meetinginfo = $meeting->get_meeting_info();
        $this->assertFalse($meetinginfo->statusrunning);
> } > if ($type == instance::TYPE_ALL) { > $this->assertTrue($meetinginfo->features['showroom']); /** > $this->assertTrue($meetinginfo->features['showrecordings']); * Test can join is working for all types > } else if ($type == instance::TYPE_ROOM_ONLY) { * > $this->assertTrue($meetinginfo->features['showroom']); * @param int $type > $this->assertFalse($meetinginfo->features['showrecordings']); * @param string|null $groupname > } else if ($type == instance::TYPE_RECORDING_ONLY) { * @param int $groupmode > $this->assertFalse($meetinginfo->features['showroom']); * @param array $canjoin > $this->assertTrue($meetinginfo->features['showrecordings']); * @dataProvider get_instance_types_meeting_info > }
* @covers ::can_join */ public function test_can_join(int $type, ?string $groupname, int $groupmode, array $canjoin) { $this->resetAfterTest(); [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting($type, $groupname, $groupmode); $this->setUser($useringroup); $meeting->update_cache(); $this->assertEquals($canjoin['useringroup'], $meeting->can_join()); if ($meeting->can_join()) { $meetinginfo = $meeting->get_meeting_info(); $this->assertStringContainsString("The session is in progress.", $meetinginfo->statusmessage); } if ($groupname) { $this->setUser($usernotingroup); $meeting->update_cache(); $this->assertEquals($canjoin['usernotingroup'], $meeting->can_join()); } } /** * Test can join is working if opening/closing time are set * * @param int $type * @param string|null $groupname * @param int $groupmode * @param array $canjoin * @param array $dates * @dataProvider get_data_can_join_with_dates * @covers ::can_join */ public function test_can_join_with_dates(int $type, ?string $groupname, int $groupmode, array $canjoin, array $dates) { // Apply the data provider relative values to now. array_walk($dates, function(&$val) { $val = time() + $val; }); $this->resetAfterTest(); [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting($type, $groupname, $groupmode, true, $dates); $this->setUser($useringroup); $meeting->update_cache(); $this->assertEquals($canjoin['useringroup'], $meeting->can_join()); // We check that admin can not join outside opening/closing times either. $this->setAdminUser(); $this->assertEquals(false, $meeting->can_join()); if ($groupname) { $this->setUser($usernotingroup); $meeting->update_cache(); $this->assertEquals($canjoin['usernotingroup'], $meeting->can_join()); $this->setAdminUser(); $this->assertEquals(false, $meeting->can_join()); } } /** * Test can join is working if the "Wait for moderator to join" setting is set and a moderator has not yet joined. * * @covers ::join * @covers ::join_meeting */ public function test_join_wait_for_moderator_not_joined() { $this->resetAfterTest(); $this->setAdminUser(); $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn'); $student = $this->getDataGenerator()->create_and_enrol($this->get_course()); $meetinginfo = [ 'course' => $this->get_course()->id, 'type' => instance::TYPE_ALL, 'wait' => 1, ]; $activity = $bbbgenerator->create_instance($meetinginfo, [ 'wait' => 1, ]); $instance = instance::get_from_instanceid($activity->id); $meeting = new meeting($instance); // The moderator has not joined. $this->setUser($student); $meeting->update_cache(); $this->expectException(\mod_bigbluebuttonbn\local\exceptions\meeting_join_exception::class); meeting::join_meeting($instance); } /** * Test can join is working if the "Wait for moderator to join" setting is set and a moderator has already joined. * * @covers ::join * @covers ::join_meeting */ public function test_join_wait_for_moderator_is_joined() { $this->resetAfterTest(); $this->setAdminUser(); $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn'); $moderator = $this->getDataGenerator()->create_and_enrol($this->get_course(), 'editingteacher'); $student = $this->getDataGenerator()->create_and_enrol($this->get_course()); $meetinginfo = [ 'course' => $this->get_course()->id, 'type' => instance::TYPE_ALL, 'wait' => 1, 'moderators' => 'role:editingteacher', ]; $activity = $bbbgenerator->create_instance($meetinginfo, [ 'wait' => 1, ]); $instance = instance::get_from_instanceid($activity->id); $meeting = new meeting($instance); $bbbgenerator->create_meeting([ 'instanceid' => $instance->get_instance_id(), ]); $this->setUser($moderator); $meeting->update_cache(); $joinurl = $meeting->join(logger::ORIGIN_BASE); $this->assertIsString($joinurl); $this->join_meeting($joinurl); $meeting->update_cache(); $this->assertCount(1, $meeting->get_attendees()); // The student can now join the meeting as a moderator is present. $this->setUser($student); $joinurl = $meeting->join(logger::ORIGIN_BASE); $this->assertIsString($joinurl); } /** * Test can join is working if the "user limit" setting is set and reached. * * @covers ::join * @covers ::join_meeting */ public function test_join_user_limit_reached() { $this->resetAfterTest(); set_config('bigbluebuttonbn_userlimit_editable', true); $this->setAdminUser(); $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn'); $moderator = $this->getDataGenerator()->create_and_enrol($this->get_course(), 'editingteacher'); $student1 = $this->getDataGenerator()->create_and_enrol($this->get_course()); $student2 = $this->getDataGenerator()->create_and_enrol($this->get_course()); $meetinginfo = [ 'course' => $this->get_course()->id, 'type' => instance::TYPE_ALL, 'userlimit' => 2, ]; $activity = $bbbgenerator->create_instance($meetinginfo, [ 'userlimit' => 2, ]); $instance = instance::get_from_instanceid($activity->id); $meeting = new meeting($instance); $bbbgenerator->create_meeting([ 'instanceid' => $instance->get_instance_id(), ]); // Moderator joins the meeting. $this->setUser($moderator); $this->join_meeting($meeting->join(logger::ORIGIN_BASE)); $meeting->update_cache(); $this->assertEquals(1, $meeting->get_participant_count()); // Student1 joins the meeting. $this->setUser($student1); $this->join_meeting($meeting->join(logger::ORIGIN_BASE)); $meeting->update_cache(); $this->assertEquals(2, $meeting->get_participant_count()); $this->assertTrue($instance->has_user_limit_been_reached($meeting->get_participant_count())); // Student2 tries to join but the limit has been reached. $this->setUser($student2); $meeting->update_cache(); $this->assertFalse($meeting->can_join()); $this->expectException(\mod_bigbluebuttonbn\local\exceptions\meeting_join_exception::class); meeting::join_meeting($instance); } /** * Test that attendees returns the right list of attendees * * @covers ::get_attendees */ public function test_get_attendees() { $this->resetAfterTest(); [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting(instance::TYPE_ALL, null, NOGROUPS, true); $this->setUser($useringroup); $this->join_meeting($meeting->join(logger::ORIGIN_BASE)); $meeting->update_cache(); $this->assertCount(1, $meeting->get_attendees()); $otheruser = $this->getDataGenerator()->create_and_enrol($this->get_course()); $this->setUser($otheruser); $meeting->update_cache(); $this->join_meeting($meeting->join(logger::ORIGIN_BASE)); $meeting->update_cache(); $this->assertCount(2, $meeting->get_attendees()); } /** * Test that attendees returns the right list of attendees * * @covers ::get_attendees */ public function test_participant_count() { $this->resetAfterTest(); [$meeting, $useringroup, $usernotingroup, $groupid, $activity] = $this->prepare_meeting(instance::TYPE_ALL, null, NOGROUPS, true); $this->setUser($useringroup); $this->join_meeting($meeting->join(logger::ORIGIN_BASE)); $meeting->update_cache(); $meetinginfo = $meeting->get_meeting_info(); $this->assertEquals(1, $meetinginfo->participantcount); $this->assertEquals(1, $meetinginfo->totalusercount); $this->assertEquals(0, $meetinginfo->moderatorcount); $this->setUser($usernotingroup); $this->join_meeting($meeting->join(logger::ORIGIN_BASE)); $meeting->update_cache(); $meetinginfo = $meeting->get_meeting_info(); $this->assertEquals(2, $meetinginfo->participantcount); $this->assertEquals(2, $meetinginfo->totalusercount); $this->assertEquals(0, $meetinginfo->moderatorcount); $this->setAdminUser(); $this->join_meeting($meeting->join(logger::ORIGIN_BASE)); $meeting->update_cache(); $meetinginfo = $meeting->get_meeting_info(); $this->assertEquals(2, $meetinginfo->participantcount); $this->assertEquals(3, $meetinginfo->totalusercount); $this->assertEquals(1, $meetinginfo->moderatorcount); } /** * Send a join meeting API CALL * * @param string $url */ protected function join_meeting(string $url) { $curl = new \curl(); $url = new \moodle_url($url); $curl->get($url->out_omit_querystring(), $url->params()); } /** * Get a list of possible test (dataprovider) * * @return array[] */ public function get_data_can_join_with_dates(): array { return [ 'Instance Type ALL - No Group - Closed in past' => [ 'type' => instance::TYPE_ALL, 'groupname' => null, 'groupmode' => NOGROUPS, 'canjoin' => ['useringroup' => false, 'usernotingroup' => false], 'dates' => ['openingtime' => -7200, 'closingtime' => -3600] ], 'Instance Type ALL - No Group - Open in future' => [ 'type' => instance::TYPE_ALL, 'groupname' => null, 'groupmode' => NOGROUPS, 'canjoin' => ['useringroup' => false, 'usernotingroup' => false], 'dates' => ['openingtime' => 3600, 'closingtime' => 7200] ], ]; } /** * Helper to prepare for a meeting * * @param int $type * @param string|null $groupname * @param int $groupmode * @param bool $createmeeting * @param array $dates * @return array */ protected function prepare_meeting(int $type, ?string $groupname, int $groupmode = SEPARATEGROUPS, bool $createmeeting = true, array $dates = []) { $this->setAdminUser(); $bbbgenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn'); $groupid = 0; $useringroup = $this->getDataGenerator()->create_and_enrol($this->get_course()); $usernotingroup = $this->getDataGenerator()->create_and_enrol($this->get_course()); if (!empty($groupname)) { $groupid = groups_get_group_by_name($this->get_course()->id, $groupname); $this->getDataGenerator()->create_group_member(['groupid' => $groupid, 'userid' => $useringroup->id]); } $meetinginfo = [ 'course' => $this->get_course()->id, 'type' => $type ]; if ($dates) { $meetinginfo = array_merge($meetinginfo, $dates); }; $activity = $bbbgenerator->create_instance($meetinginfo, ['groupmode' => $groupmode]); $instance = instance::get_from_instanceid($activity->id); if ($groupid) { $instance->set_group_id($groupid); } if ($createmeeting) { // Create the meetings on the mock server, so we can join it as a simple user. $bbbgenerator->create_meeting([ 'instanceid' => $instance->get_instance_id(), 'groupid' => $instance->get_group_id() ]); } $meeting = new meeting($instance); return [$meeting, $useringroup, $usernotingroup, $groupid, $activity]; } }