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  declare(strict_types=1);
  18  
  19  namespace core_reportbuilder\local\helpers;
  20  
  21  use advanced_testcase;
  22  use invalid_parameter_exception;
  23  use core_cohort\reportbuilder\audience\cohortmember;
  24  use core_reportbuilder_generator;
  25  use core_reportbuilder\local\models\schedule as model;
  26  use core_reportbuilder\reportbuilder\audience\manual;
  27  use core_user\reportbuilder\datasource\users;
  28  
  29  /**
  30   * Unit tests for the schedule helper class
  31   *
  32   * @package     core_reportbuilder
  33   * @covers      \core_reportbuilder\local\helpers\schedule
  34   * @copyright   2021 Paul Holden <paulh@moodle.com>
  35   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class schedule_test extends advanced_testcase {
  38  
  39      /**
  40       * Test create schedule
  41       */
  42      public function test_create_schedule(): void {
  43          $this->resetAfterTest();
  44          $this->setAdminUser();
  45  
  46          /** @var core_reportbuilder_generator $generator */
  47          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  48          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
  49  
  50          $timescheduled = time() + DAYSECS;
  51          $schedule = schedule::create_schedule((object) [
  52              'name' => 'My schedule',
  53              'reportid' => $report->get('id'),
  54              'format' => 'csv',
  55              'subject' => 'Hello',
  56              'message' => 'Hola',
  57              'timescheduled' => $timescheduled,
  58          ]);
  59  
  60          $this->assertEquals('My schedule', $schedule->get('name'));
  61          $this->assertEquals($report->get('id'), $schedule->get('reportid'));
  62          $this->assertEquals('csv', $schedule->get('format'));
  63          $this->assertEquals('Hello', $schedule->get('subject'));
  64          $this->assertEquals('Hola', $schedule->get('message'));
  65          $this->assertEquals($timescheduled, $schedule->get('timescheduled'));
  66          $this->assertEquals($timescheduled, $schedule->get('timenextsend'));
  67      }
  68  
  69      /**
  70       * Test update schedule
  71       */
  72      public function test_update_schedule(): void {
  73          $this->resetAfterTest();
  74          $this->setAdminUser();
  75  
  76          /** @var core_reportbuilder_generator $generator */
  77          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  78          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
  79          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
  80  
  81          // Update some record properties.
  82          $record = $schedule->to_record();
  83          $record->name = 'My updated schedule';
  84          $record->timescheduled = 1861340400; // 25/12/2028 07:00 UTC.
  85  
  86          $schedule = schedule::update_schedule($record);
  87          $this->assertEquals($record->name, $schedule->get('name'));
  88          $this->assertEquals($record->timescheduled, $schedule->get('timescheduled'));
  89      }
  90  
  91      /**
  92       * Test update invalid schedule
  93       */
  94      public function test_update_schedule_invalid(): void {
  95          $this->resetAfterTest();
  96          $this->setAdminUser();
  97  
  98          /** @var core_reportbuilder_generator $generator */
  99          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 100          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 101  
 102          $this->expectException(invalid_parameter_exception::class);
 103          $this->expectExceptionMessage('Invalid schedule');
 104          schedule::update_schedule((object) ['id' => 42, 'reportid' => $report->get('id')]);
 105      }
 106  
 107      /**
 108       * Test toggle schedule
 109       */
 110      public function test_toggle_schedule(): void {
 111          $this->resetAfterTest();
 112          $this->setAdminUser();
 113  
 114          /** @var core_reportbuilder_generator $generator */
 115          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 116          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 117          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
 118  
 119          // Disable the schedule.
 120          schedule::toggle_schedule($report->get('id'), $schedule->get('id'), false);
 121          $schedule = $schedule->read();
 122          $this->assertFalse($schedule->get('enabled'));
 123  
 124          // Enable the schedule.
 125          schedule::toggle_schedule($report->get('id'), $schedule->get('id'), true);
 126          $schedule = $schedule->read();
 127          $this->assertTrue($schedule->get('enabled'));
 128      }
 129  
 130      /**
 131       * Test toggle invalid schedule
 132       */
 133      public function test_toggle_schedule_invalid(): void {
 134          $this->resetAfterTest();
 135          $this->setAdminUser();
 136  
 137          /** @var core_reportbuilder_generator $generator */
 138          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 139          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 140  
 141          $this->expectException(invalid_parameter_exception::class);
 142          $this->expectExceptionMessage('Invalid schedule');
 143          schedule::toggle_schedule($report->get('id'), 42, true);
 144      }
 145  
 146      /**
 147       * Test delete schedule
 148       */
 149      public function test_delete_schedule(): void {
 150          $this->resetAfterTest();
 151          $this->setAdminUser();
 152  
 153          /** @var core_reportbuilder_generator $generator */
 154          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 155          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 156          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
 157  
 158          $scheduleid = $schedule->get('id');
 159  
 160          schedule::delete_schedule($report->get('id'), $scheduleid);
 161          $this->assertFalse($schedule::record_exists($scheduleid));
 162      }
 163  
 164      /**
 165       * Test delete invalid schedule
 166       */
 167      public function test_delete_schedule_invalid(): void {
 168          $this->resetAfterTest();
 169          $this->setAdminUser();
 170  
 171          /** @var core_reportbuilder_generator $generator */
 172          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 173          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 174  
 175          $this->expectException(invalid_parameter_exception::class);
 176          $this->expectExceptionMessage('Invalid schedule');
 177          schedule::delete_schedule($report->get('id'), 42);
 178      }
 179  
 180      /**
 181       * Test getting schedule report users (those in matching audience)
 182       */
 183      public function test_get_schedule_report_users(): void {
 184          $this->resetAfterTest();
 185  
 186          /** @var core_reportbuilder_generator $generator */
 187          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 188          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 189  
 190          // Create cohort, with some members.
 191          $cohort = $this->getDataGenerator()->create_cohort();
 192          $cohortuserone = $this->getDataGenerator()->create_user(['firstname' => 'Zoe', 'lastname' => 'Zebra']);
 193          cohort_add_member($cohort->id, $cohortuserone->id);
 194          $cohortusertwo = $this->getDataGenerator()->create_user(['firstname' => 'Henrietta', 'lastname' => 'Hamster']);
 195          cohort_add_member($cohort->id, $cohortusertwo->id);
 196  
 197          // Create a third user, to be added manually.
 198          $manualuserone = $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Badger']);
 199  
 200          $audiencecohort = $generator->create_audience([
 201              'reportid' => $report->get('id'),
 202              'classname' => cohortmember::class,
 203              'configdata' => ['cohorts' => [$cohort->id]],
 204          ]);
 205  
 206          $audiencemanual = $generator->create_audience([
 207              'reportid' => $report->get('id'),
 208              'classname' => manual::class,
 209              'configdata' => ['users' => [$manualuserone->id]],
 210          ]);
 211  
 212          // Now create our schedule.
 213          $schedule = $generator->create_schedule([
 214              'reportid' => $report->get('id'),
 215              'name' => 'My schedule',
 216              'audiences' => json_encode([
 217                  $audiencecohort->get_persistent()->get('id'),
 218                  $audiencemanual->get_persistent()->get('id'),
 219              ]),
 220          ]);
 221  
 222          $users = schedule::get_schedule_report_users($schedule);
 223          $this->assertEquals([
 224              'Bob',
 225              'Henrietta',
 226              'Zoe',
 227          ], array_column($users, 'firstname'));
 228  
 229          // Now delete one of our users, ensure they are no longer returned.
 230          delete_user($manualuserone);
 231  
 232          $users = schedule::get_schedule_report_users($schedule);
 233          $this->assertEquals([
 234              'Henrietta',
 235              'Zoe',
 236          ], array_column($users, 'firstname'));
 237      }
 238  
 239      /**
 240       * Test getting schedule report row count
 241       */
 242      public function test_get_schedule_report_count(): void {
 243          $this->resetAfterTest();
 244  
 245          /** @var core_reportbuilder_generator $generator */
 246          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 247          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 248          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
 249  
 250          // There is only one row in the report (the only user on the site).
 251          $count = schedule::get_schedule_report_count($schedule);
 252          $this->assertEquals(1, $count);
 253      }
 254  
 255      /**
 256       * Data provider for {@see test_get_schedule_report_file}
 257       *
 258       * @return string[]
 259       */
 260      public function get_schedule_report_file_format(): array {
 261          return [
 262              ['csv'],
 263              ['excel'],
 264              ['html'],
 265              ['json'],
 266              ['ods'],
 267              ['pdf'],
 268          ];
 269      }
 270  
 271      /**
 272       * Test getting schedule report exported file, in each supported format
 273       *
 274       * @param string $format
 275       *
 276       * @dataProvider get_schedule_report_file_format
 277       */
 278      public function test_get_schedule_report_file(string $format): void {
 279          $this->resetAfterTest();
 280          $this->setAdminUser();
 281  
 282          /** @var core_reportbuilder_generator $generator */
 283          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 284          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 285          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule', 'format' => $format]);
 286  
 287          // There is only one row in the report (the only user on the site).
 288          $file = schedule::get_schedule_report_file($schedule);
 289          $this->assertGreaterThan(64, $file->get_filesize());
 290      }
 291  
 292      /**
 293       * Data provider for {@see test_should_send_schedule}
 294       *
 295       * @return array[]
 296       */
 297      public function should_send_schedule_provider(): array {
 298          $time = time();
 299  
 300          // We just need large offsets for dates in the past/future.
 301          $yesterday = $time - DAYSECS;
 302          $tomorrow = $time + DAYSECS;
 303  
 304          return [
 305              'Disabled' => [[
 306                  'enabled' => false,
 307              ], false],
 308              'Time scheduled in the past' => [[
 309                  'recurrence' => model::RECURRENCE_NONE,
 310                  'timescheduled' => $yesterday,
 311              ], true],
 312              'Time scheduled in the past, already sent prior to schedule' => [[
 313                  'recurrence' => model::RECURRENCE_NONE,
 314                  'timescheduled' => $yesterday,
 315                  'timelastsent' => $yesterday - HOURSECS,
 316              ], true],
 317              'Time scheduled in the past, already sent on schedule' => [[
 318                  'recurrence' => model::RECURRENCE_NONE,
 319                  'timescheduled' => $yesterday,
 320                  'timelastsent' => $yesterday,
 321              ], false],
 322              'Time scheduled in the future' => [[
 323                  'recurrence' => model::RECURRENCE_NONE,
 324                  'timescheduled' => $tomorrow,
 325              ], false],
 326              'Time scheduled in the future, already sent prior to schedule' => [[
 327                  'recurrence' => model::RECURRENCE_NONE,
 328                  'timelastsent' => $yesterday,
 329                  'timescheduled' => $tomorrow,
 330              ], false],
 331              'Next send in the past' => [[
 332                  'recurrence' => model::RECURRENCE_DAILY,
 333                  'timescheduled' => $yesterday,
 334                  'timenextsend' => $yesterday,
 335              ], true],
 336              'Next send in the future' => [[
 337                  'recurrence' => model::RECURRENCE_DAILY,
 338                  'timescheduled' => $yesterday,
 339                  'timenextsend' => $tomorrow,
 340              ], false],
 341          ];
 342      }
 343  
 344      /**
 345       * Test for whether a schedule should be sent
 346       *
 347       * @param array $properties
 348       * @param bool $expected
 349       *
 350       * @dataProvider should_send_schedule_provider
 351       */
 352      public function test_should_send_schedule(array $properties, bool $expected): void {
 353          $this->resetAfterTest();
 354  
 355          /** @var core_reportbuilder_generator $generator */
 356          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 357          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 358  
 359          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule'] + $properties);
 360  
 361          // If "Time next send" is specified, then override calculated value.
 362          if (array_key_exists('timenextsend', $properties)) {
 363              $schedule->set('timenextsend', $properties['timenextsend']);
 364          }
 365  
 366          $this->assertEquals($expected, schedule::should_send_schedule($schedule));
 367      }
 368  
 369      /**
 370       * Data provider for {@see test_calculate_next_send_time}
 371       *
 372       * @return array[]
 373       */
 374      public function calculate_next_send_time_provider(): array {
 375          $timescheduled = 1635865200; // Tue Nov 02 2021 15:00:00 GMT+0000.
 376          $timenow = 1639846800; // Sat Dec 18 2021 17:00:00 GMT+0000.
 377  
 378          return [
 379              'No recurrence' => [model::RECURRENCE_NONE, $timescheduled, $timenow, $timescheduled],
 380              'Recurrence, time scheduled in future' => [model::RECURRENCE_DAILY, $timenow + DAYSECS, $timenow, $timenow + DAYSECS],
 381              // Sun Dec 19 2021 15:00:00 GMT+0000.
 382              'Daily recurrence' => [model::RECURRENCE_DAILY, $timescheduled, $timenow, 1639926000],
 383              // Mon Dec 20 2021 15:00:00 GMT+0000.
 384              'Weekday recurrence' => [model::RECURRENCE_WEEKDAYS, $timescheduled, $timenow, 1640012400],
 385              // Tue Dec 21 2021 15:00:00 GMT+0000.
 386              'Weekly recurrence' => [model::RECURRENCE_WEEKLY, $timescheduled, $timenow, 1640098800],
 387              // Sun Jan 02 2022 15:00:00 GMT+0000.
 388              'Monthy recurrence' => [model::RECURRENCE_MONTHLY, $timescheduled, $timenow, 1641135600],
 389              // Wed Nov 02 2022 15:00:00 GMT+0000.
 390              'Annual recurrence' => [model::RECURRENCE_ANNUALLY, $timescheduled, $timenow, 1667401200],
 391           ];
 392      }
 393  
 394      /**
 395       * Test for calculating next schedule send time
 396       *
 397       * @param int $recurrence
 398       * @param int $timescheduled
 399       * @param int $timenow
 400       * @param int $expected
 401       *
 402       * @dataProvider calculate_next_send_time_provider
 403       */
 404      public function test_calculate_next_send_time(int $recurrence, int $timescheduled, int $timenow, int $expected): void {
 405          $this->resetAfterTest();
 406  
 407          /** @var core_reportbuilder_generator $generator */
 408          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 409          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 410  
 411          $schedule = $generator->create_schedule([
 412              'reportid' => $report->get('id'),
 413              'name' => 'My schedule',
 414              'recurrence' => $recurrence,
 415              'timescheduled' => $timescheduled,
 416              'timenow' => $timenow,
 417          ]);
 418  
 419          $this->assertEquals($expected, schedule::calculate_next_send_time($schedule, $timenow));
 420      }
 421  }