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 401 and 402] [Versions 401 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  namespace mod_bigbluebuttonbn\task;
  18  
  19  use advanced_testcase;
  20  use core\task\manager;
  21  use mod_bigbluebuttonbn\instance;
  22  use mod_bigbluebuttonbn\logger;
  23  use mod_bigbluebuttonbn\recording;
  24  use mod_bigbluebuttonbn\test\testcase_helper_trait;
  25  
  26  /**
  27   * Class containing the scheduled task for lti module.
  28   *
  29   * @package   mod_bigbluebuttonbn
  30   * @copyright 2019 onwards, Blindside Networks Inc
  31   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32   * @covers \mod_bigbluebuttonbn\task\upgrade_recordings_task
  33   */
  34  class upgrade_recordings_task_test extends advanced_testcase {
  35  
  36      use testcase_helper_trait;
  37  
  38      /**
  39       * @var object $instance
  40       */
  41      protected $instance = null;
  42  
  43      /**
  44       * @var array $groups all groups
  45       */
  46      protected $groups = [];
  47  
  48      /**
  49       * Setup for test
  50       */
  51      public function setUp(): void {
  52          parent::setUp();
  53          $this->initialise_mock_server();
  54          $this->resetAfterTest();
  55      }
  56  
  57      /**
  58       * Upgrade task test
  59       */
  60      public function test_upgrade_recordings_basic(): void {
  61          global $DB;
  62          $this->setup_basic_data();
  63          upgrade_recordings_task::schedule_upgrade_per_meeting(false);;
  64          // The first run will lead to all of them being processed, and none left over.
  65          // A new job is always queued on a successful run.
  66          $this->runAdhocTasks(upgrade_recordings_task::class);
  67          $this->assertEquals(0, $DB->count_records('bigbluebuttonbn_logs', ['log' => logger::EVENT_CREATE]));
  68          $this->assertEquals(75, recording::count_records(['imported' => '0']));
  69          // Old logs are kept but renamed.
  70          $this->assertEquals(75, $DB->count_records('bigbluebuttonbn_logs', ['log' => logger::EVENT_CREATE_MIGRATED]));
  71          $this->assertEquals(15, recording::count_records(['groupid' => $this->groups[0]->id, 'imported' => '0',
  72              'status' => recording::RECORDING_STATUS_PROCESSED]));
  73          $this->assertEquals(15, recording::count_records(['groupid' => $this->groups[1]->id, 'imported' => '0',
  74              'status' => recording::RECORDING_STATUS_PROCESSED]));
  75          $this->assertEquals(45,
  76              recording::count_records(['groupid' => 0, 'imported' => '0', 'status' => recording::RECORDING_STATUS_PROCESSED]));
  77  
  78          // The second run will lead to no change in the number of logs, but no further jobs will be queued.
  79          upgrade_recordings_task::schedule_upgrade_per_meeting();
  80          $this->runAdhocTasks(upgrade_recordings_task::class);
  81          $this->assertEquals(75, recording::count_records());
  82          // The first run will lead to all of them being processed, and none left over.
  83          // A new job is always queued on a successful run.
  84          $this->assertEmpty($DB->get_records_select(
  85              'bigbluebuttonbn_logs',
  86              'log = :logmatch AND ' . $DB->sql_like('meta', ':match'),
  87              [
  88                  'logmatch' => 'Create',
  89                  'match' => '%true%'
  90              ]
  91          ));
  92          $this->runAdhocTasks(upgrade_recordings_task::class);
  93          $this->assertEmpty($DB->get_records_select(
  94              'bigbluebuttonbn_logs',
  95              'log = :logmatch AND ' . $DB->sql_like('meta', ':match'),
  96              [
  97                  'logmatch' => 'Create',
  98                  'match' => '%true%'
  99              ]
 100          ));
 101          $this->assertEquals(0, $DB->count_records('bigbluebuttonbn_logs', ['log' => 'Create']));
 102          // Ensure that logs match.
 103          $matchesarray = [
 104              [
 105                  'Executing .*',
 106                  'Fetching logs for conversion',
 107                  "Creating new recording records",
 108                  'Migrated 30 recordings',
 109              ],
 110              [
 111                  'Executing .*',
 112                  'Fetching logs for conversion',
 113                  "Creating new recording records",
 114                  'Migrated 15 recordings',
 115              ],
 116              [
 117                  'Executing .*',
 118                  'Fetching logs for conversion',
 119                  "Creating new recording records",
 120                  "Unable to find an activity for .*. This recording is headless",
 121                  'Migrated 15 recordings'
 122              ]
 123          ];
 124          foreach ($matchesarray as $matches) {
 125              $this->expectOutputRegex('/' . implode('.*', $matches) . '/s');
 126          }
 127      }
 128  
 129      /**
 130       * Upgrade task test
 131       */
 132      public function test_upgrade_recordings_imported_basic(): void {
 133          global $DB;
 134          $this->setup_basic_data(true);
 135          upgrade_recordings_task::schedule_upgrade_per_meeting(true);;
 136          // The first run will lead to all of them being processed, and none left over.
 137          // A new job is always queued on a successful run.
 138          $this->runAdhocTasks(upgrade_recordings_task::class);
 139  
 140          $this->assertEquals(0, $DB->count_records('bigbluebuttonbn_logs', ['log' => logger::EVENT_IMPORT]));
 141          $this->assertEquals(75, $DB->count_records('bigbluebuttonbn_logs', ['log' => logger::EVENT_IMPORT_MIGRATED]));
 142          $this->assertEquals(75, recording::count_records(['imported' => '1']));
 143  
 144          $this->assertEquals(15, recording::count_records(['groupid' => $this->groups[0]->id, 'imported' => '1',
 145              'status' => recording::RECORDING_STATUS_PROCESSED]));
 146          $this->assertEquals(15, recording::count_records(['groupid' => $this->groups[1]->id, 'imported' => '1',
 147              'status' => recording::RECORDING_STATUS_PROCESSED]));
 148          $this->assertEquals(45,
 149              recording::count_records(['groupid' => 0, 'imported' => '1', 'status' => recording::RECORDING_STATUS_PROCESSED]));
 150  
 151          // The second run will lead to no change in the number of logs, but no further jobs will be queued.
 152          upgrade_recordings_task::schedule_upgrade_per_meeting();
 153          $this->runAdhocTasks(upgrade_recordings_task::class);
 154          $this->assertEquals(75, recording::count_records(['imported' => '1']));
 155          // The first run will lead to all of them being processed, and none left over.
 156          // A new job is always queued on a successful run.
 157          $this->assertEmpty($DB->get_records_select(
 158              'bigbluebuttonbn_logs',
 159              'log = :logmatch',
 160              [
 161                  'logmatch' => 'Import'
 162              ]
 163          ));
 164          // Ensure that logs match.
 165          $matchesarray = [
 166              [
 167                  'Executing .*',
 168                  'Fetching logs for conversion',
 169                  "Creating new recording records",
 170                  'Migrated 30 recordings',
 171              ],
 172              [
 173                  'Executing .*',
 174                  'Fetching logs for conversion',
 175                  "Creating new recording records",
 176                  'Migrated 15 recordings',
 177              ],
 178              [
 179                  'Executing .*',
 180                  'Fetching logs for conversion',
 181                  "Creating new recording records",
 182                  "Unable to find an activity for .*. This recording is headless",
 183                  'Migrated 15 recordings'
 184              ]
 185          ];
 186          foreach ($matchesarray as $matches) {
 187              $this->expectOutputRegex('/' . implode('.*', $matches) . '/s');
 188          }
 189      }
 190  
 191      /**
 192       * Upgrade recordings when we have missing recordings on the server
 193       * Basically, the recordings are imported and then we cannot the other because logs have been marked as migrated.
 194       */
 195      public function test_upgrade_recordings_with_missing_recording_on_bbb_server(): void {
 196          global $DB;
 197          $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 198          [$teacher, $groups, $instance, $groupedinstance, $deletedinstance] = $this->setup_basic_course_and_meeting();
 199  
 200          $this->create_legacy_log_entries($instance, $teacher->id, 5, false);
 201          $this->create_legacy_log_entries($instance, $teacher->id, 5, false, false);
 202          $this->assertEquals(10, $DB->count_records('bigbluebuttonbn_logs', ['log' => 'Create']));
 203  
 204          // Schedule the run.
 205          upgrade_recordings_task::schedule_upgrade_per_meeting();
 206          $this->runAdhocTasks(upgrade_recordings_task::class);
 207          // At this point only 5 are created, the rest is still in the queue.
 208          $this->assertEquals(0, $DB->count_records('bigbluebuttonbn_logs', ['log' => 'Create']));
 209          $this->assertEquals(5, recording::count_records());
 210          // Now create 5 recordings on the server.
 211          // Schedule the run.
 212          upgrade_recordings_task::schedule_upgrade_per_meeting();
 213          for ($index = 0; $index < 5; $index++) {
 214              $plugingenerator->create_recording([
 215                  'bigbluebuttonbnid' => $instance->get_instance_id(),
 216                  'groupid' => $instance->get_group_id(),
 217                  'starttime' => time(),
 218                  'endtime' => time() + HOURSECS,
 219              ], true); // Create another recording on the server.
 220          }
 221          $this->assertEquals(0, $DB->count_records('task_adhoc', ['classname' => '\\' . upgrade_recordings_task::class]));
 222          $this->runAdhocTasks(upgrade_recordings_task::class);
 223          // Ensure that logs match.
 224  
 225          // Ensure that logs match.
 226          $matchesarray = [
 227              [
 228                  'Executing .*',
 229                  'Fetching logs for conversion',
 230                  "Creating new recording records",
 231                  'Migrated 5 recordings',
 232              ],
 233          ];
 234          foreach ($matchesarray as $matches) {
 235              $this->expectOutputRegex('/' . implode('.*', $matches) . '/s');
 236          }
 237      }
 238  
 239      /**
 240       * Upgrade task test with more recordings on the server than in the log : we add all recording and should have
 241       * no more logs.
 242       */
 243      public function test_upgrade_recordings_with_more_recordings_on_bbb_server(): void {
 244          global $DB;
 245          $generator = $this->getDataGenerator();
 246          // Create a course with student and teacher, and two groups.
 247          $this->course = $generator->create_course();
 248          $user = $this->getDataGenerator()->create_and_enrol($this->course);
 249          // Create an ungrouped activity.
 250          $activity = $generator->create_module('bigbluebuttonbn', [
 251              'course' => $this->course->id,
 252          ]);
 253          $this->instance = instance::get_from_instanceid($activity->id);
 254          // We create 5 recordings in the log but no recording instance on the server.
 255          $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 256          $plugingenerator->create_meeting([
 257              'instanceid' => $this->instance->get_instance_id(),
 258              'groupid' => $this->instance->get_group_id(),
 259          ]);
 260          $this->create_legacy_log_entries($this->instance, $user->id, 5, false);
 261          $plugingenerator->create_recording([
 262              'bigbluebuttonbnid' => $this->instance->get_instance_id(),
 263              'groupid' => $this->instance->get_group_id(),
 264              'starttime' => time(),
 265              'endtime' => time() + HOURSECS,
 266          ], true); // Create another recording on the server.
 267  
 268          $this->assertEquals(5, $DB->count_records('bigbluebuttonbn_logs', ['log' => 'Create']));
 269          upgrade_recordings_task::schedule_upgrade_per_meeting();
 270          $this->runAdhocTasks(upgrade_recordings_task::class);
 271          $this->assertEquals(6, recording::count_records(['status' => recording::RECORDING_STATUS_PROCESSED]));
 272          $this->assertEquals(0, $DB->count_records('bigbluebuttonbn_logs', ['log' => 'Create']));
 273          // Ensure that logs match.
 274          $matches = [
 275              'Executing .*',
 276              'Fetching logs for conversion',
 277              "Creating new recording records",
 278              'Migrated 6 recordings',
 279          ];
 280          $this->expectOutputRegex('/' . implode('.*', $matches) . '/s');
 281      }
 282  
 283      /**
 284       * Setup basic data for tests
 285       *
 286       * @param bool $importedrecording
 287       * @return void
 288       * @throws \coding_exception
 289       * @throws \moodle_exception
 290       */
 291      protected function setup_basic_data($importedrecording = false) {
 292          global $DB;
 293          [$teacher, $groups, $instance, $groupedinstance, $deletedinstance] = $this->setup_basic_course_and_meeting();
 294  
 295          $this->create_legacy_log_entries($instance, $teacher->id, 30, $importedrecording);
 296          foreach ($groups as $group) {
 297              $groupinstance = instance::get_group_instance_from_instance($groupedinstance, $group->id);
 298              $this->create_legacy_log_entries($groupinstance, $teacher->id, 15, $importedrecording);
 299          }
 300          $this->create_legacy_log_entries($deletedinstance, $teacher->id, 15, $importedrecording);
 301          course_delete_module($deletedinstance->get_cm_id());
 302          // Truncate the recordings table to reflect what it would have looked like before this version.
 303          $DB->delete_records('bigbluebuttonbn_recordings');
 304          $this->groups = $groups;
 305          $this->instance = $instance;
 306      }
 307  
 308      /**
 309       * Setup basic data for tests
 310       *
 311       * @return array
 312       * @throws \coding_exception
 313       * @throws \moodle_exception
 314       */
 315      protected function setup_basic_course_and_meeting() {
 316          $generator = $this->getDataGenerator();
 317          // Create a course with student and teacher, and two groups.
 318          $this->course = $generator->create_course();
 319          $groups = [];
 320          $groups[] = $generator->create_group(['courseid' => $this->course->id]);
 321          $groups[] = $generator->create_group(['courseid' => $this->course->id]);
 322  
 323          $teacher = $generator->create_and_enrol($this->course, 'editingteacher');
 324          $generator->create_and_enrol($this->course, 'student');
 325  
 326          // Create a "normal" meeting.
 327          $instance = $this->create_meeting_for_logs();
 328          // Create an grouped activity.
 329          $groupedinstance = $this->create_meeting_for_logs($groups);
 330          // Create an instance that will then be deleted.
 331          $deletedinstance = $this->create_meeting_for_logs();
 332          // Create logs for an activity which no longer exists (because we deleted it).
 333          return [$teacher, $groups, $instance, $groupedinstance, $deletedinstance];
 334      }
 335  
 336      /**
 337       * Create a meeting and return its instance
 338       *
 339       * @param array|null $groups
 340       * @return instance
 341       */
 342      protected function create_meeting_for_logs(?array $groups = null) {
 343          $generator = $this->getDataGenerator();
 344          $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 345          $params = [
 346              'course' => $this->course->id,
 347          ];
 348          if (!empty($groups)) {
 349              $params['groupmode'] = SEPARATEGROUPS;
 350          }
 351          $activity = $generator->create_module('bigbluebuttonbn', $params);
 352          $instance = instance::get_from_instanceid($activity->id);
 353          if (!empty($groups)) {
 354              foreach ($groups as $group) {
 355                  $groupinstance = instance::get_group_instance_from_instance($instance, $group->id);
 356                  $plugingenerator->create_meeting([
 357                      'instanceid' => $groupinstance->get_instance_id(),
 358                      'groupid' => $groupinstance->get_group_id(),
 359                  ]);
 360              }
 361          } else {
 362              $plugingenerator->create_meeting([
 363                  'instanceid' => $instance->get_instance_id(),
 364                  'groupid' => $instance->get_group_id(),
 365              ]);
 366          }
 367          return $instance;
 368      }
 369  }