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  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  
 230      /**
 231       * Test getting schedule report row count
 232       */
 233      public function test_get_schedule_report_count(): void {
 234          $this->resetAfterTest();
 235  
 236          /** @var core_reportbuilder_generator $generator */
 237          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 238          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 239          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']);
 240  
 241          // There is only one row in the report (the only user on the site).
 242          $count = schedule::get_schedule_report_count($schedule);
 243          $this->assertEquals(1, $count);
 244      }
 245  
 246      /**
 247       * Data provider for {@see test_get_schedule_report_file}
 248       *
 249       * @return string[]
 250       */
 251      public function get_schedule_report_file_format(): array {
 252          return [
 253              ['csv'],
 254              ['excel'],
 255              ['html'],
 256              ['json'],
 257              ['ods'],
 258              ['pdf'],
 259          ];
 260      }
 261  
 262      /**
 263       * Test getting schedule report exported file, in each supported format
 264       *
 265       * @param string $format
 266       *
 267       * @dataProvider get_schedule_report_file_format
 268       */
 269      public function test_get_schedule_report_file(string $format): void {
 270          $this->resetAfterTest();
 271          $this->setAdminUser();
 272  
 273          /** @var core_reportbuilder_generator $generator */
 274          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 275          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 276          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule', 'format' => $format]);
 277  
 278          // There is only one row in the report (the only user on the site).
 279          $file = schedule::get_schedule_report_file($schedule);
 280          $this->assertGreaterThan(64, $file->get_filesize());
 281      }
 282  
 283      /**
 284       * Data provider for {@see test_should_send_schedule}
 285       *
 286       * @return array[]
 287       */
 288      public function should_send_schedule_provider(): array {
 289          $time = time();
 290  
 291          // We just need large offsets for dates in the past/future.
 292          $yesterday = $time - DAYSECS;
 293          $tomorrow = $time + DAYSECS;
 294  
 295          return [
 296              'Disabled' => [[
 297                  'enabled' => false,
 298              ], false],
 299              'Time scheduled in the past' => [[
 300                  'recurrence' => model::RECURRENCE_NONE,
 301                  'timescheduled' => $yesterday,
 302              ], true],
 303              'Time scheduled in the past, already sent prior to schedule' => [[
 304                  'recurrence' => model::RECURRENCE_NONE,
 305                  'timescheduled' => $yesterday,
 306                  'timelastsent' => $yesterday - HOURSECS,
 307              ], true],
 308              'Time scheduled in the past, already sent on schedule' => [[
 309                  'recurrence' => model::RECURRENCE_NONE,
 310                  'timescheduled' => $yesterday,
 311                  'timelastsent' => $yesterday,
 312              ], false],
 313              'Time scheduled in the future' => [[
 314                  'recurrence' => model::RECURRENCE_NONE,
 315                  'timescheduled' => $tomorrow,
 316              ], false],
 317              'Time scheduled in the future, already sent prior to schedule' => [[
 318                  'recurrence' => model::RECURRENCE_NONE,
 319                  'timelastsent' => $yesterday,
 320                  'timescheduled' => $tomorrow,
 321              ], false],
 322              'Next send in the past' => [[
 323                  'recurrence' => model::RECURRENCE_DAILY,
 324                  'timescheduled' => $yesterday,
 325                  'timenextsend' => $yesterday,
 326              ], true],
 327              'Next send in the future' => [[
 328                  'recurrence' => model::RECURRENCE_DAILY,
 329                  'timescheduled' => $yesterday,
 330                  'timenextsend' => $tomorrow,
 331              ], false],
 332          ];
 333      }
 334  
 335      /**
 336       * Test for whether a schedule should be sent
 337       *
 338       * @param array $properties
 339       * @param bool $expected
 340       *
 341       * @dataProvider should_send_schedule_provider
 342       */
 343      public function test_should_send_schedule(array $properties, bool $expected): void {
 344          $this->resetAfterTest();
 345  
 346          /** @var core_reportbuilder_generator $generator */
 347          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 348          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 349  
 350          $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule'] + $properties);
 351  
 352          // If "Time next send" is specified, then override calculated value.
 353          if (array_key_exists('timenextsend', $properties)) {
 354              $schedule->set('timenextsend', $properties['timenextsend']);
 355          }
 356  
 357          $this->assertEquals($expected, schedule::should_send_schedule($schedule));
 358      }
 359  
 360      /**
 361       * Data provider for {@see test_calculate_next_send_time}
 362       *
 363       * @return array[]
 364       */
 365      public function calculate_next_send_time_provider(): array {
 366          $timescheduled = 1635865200; // Tue Nov 02 2021 15:00:00 GMT+0000.
 367          $timenow = 1639846800; // Sat Dec 18 2021 17:00:00 GMT+0000.
 368  
 369          return [
 370              'No recurrence' => [model::RECURRENCE_NONE, $timescheduled, $timenow, $timescheduled],
 371              'Recurrence, time scheduled in future' => [model::RECURRENCE_DAILY, $timenow + DAYSECS, $timenow, $timenow + DAYSECS],
 372              // Sun Dec 19 2021 15:00:00 GMT+0000.
 373              'Daily recurrence' => [model::RECURRENCE_DAILY, $timescheduled, $timenow, 1639926000],
 374              // Mon Dec 20 2021 15:00:00 GMT+0000.
 375              'Weekday recurrence' => [model::RECURRENCE_WEEKDAYS, $timescheduled, $timenow, 1640012400],
 376              // Tue Dec 21 2021 15:00:00 GMT+0000.
 377              'Weekly recurrence' => [model::RECURRENCE_WEEKLY, $timescheduled, $timenow, 1640098800],
 378              // Sun Jan 02 2022 15:00:00 GMT+0000.
 379              'Monthy recurrence' => [model::RECURRENCE_MONTHLY, $timescheduled, $timenow, 1641135600],
 380              // Wed Nov 02 2022 15:00:00 GMT+0000.
 381              'Annual recurrence' => [model::RECURRENCE_ANNUALLY, $timescheduled, $timenow, 1667401200],
 382           ];
 383      }
 384  
 385      /**
 386       * Test for calculating next schedule send time
 387       *
 388       * @param int $recurrence
 389       * @param int $timescheduled
 390       * @param int $timenow
 391       * @param int $expected
 392       *
 393       * @dataProvider calculate_next_send_time_provider
 394       */
 395      public function test_calculate_next_send_time(int $recurrence, int $timescheduled, int $timenow, int $expected): void {
 396          $this->resetAfterTest();
 397  
 398          /** @var core_reportbuilder_generator $generator */
 399          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 400          $report = $generator->create_report(['name' => 'My report', 'source' => users::class]);
 401  
 402          $schedule = $generator->create_schedule([
 403              'reportid' => $report->get('id'),
 404              'name' => 'My schedule',
 405              'recurrence' => $recurrence,
 406              'timescheduled' => $timescheduled,
 407              'timenow' => $timenow,
 408          ]);
 409  
 410          $this->assertEquals($expected, schedule::calculate_next_send_time($schedule, $timenow));
 411      }
 412  }