Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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  use core_reportbuilder\manager;
  20  use core_reportbuilder\local\helpers\aggregation;
  21  use core_reportbuilder\local\helpers\report;
  22  use core_reportbuilder\local\helpers\user_filter_manager;
  23  use core_reportbuilder\table\custom_report_table_view;
  24  
  25  /**
  26   * Helper base class for reportbuilder unit tests
  27   *
  28   * @package     core_reportbuilder
  29   * @copyright   2021 Paul Holden <paulh@moodle.com>
  30   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  abstract class core_reportbuilder_testcase extends advanced_testcase {
  33  
  34      /**
  35       * Retrieve content for given report as array of report data
  36       *
  37       * @param int $reportid
  38       * @param int $pagesize
  39       * @param array $filtervalues
  40       * @return array[]
  41       */
  42      protected function get_custom_report_content(int $reportid, int $pagesize = 30, array $filtervalues = []): array {
  43          $records = [];
  44  
  45          // Apply filter values.
  46          user_filter_manager::set($reportid, $filtervalues);
  47  
  48          // Create table instance.
  49          $table = custom_report_table_view::create($reportid);
  50          $table->setup();
  51          $table->query_db($pagesize, false);
  52  
  53          // Extract raw data.
  54          foreach ($table->rawdata as $record) {
  55              $records[] = $table->format_row($record);
  56          }
  57  
  58          $table->close_recordset();
  59  
  60          return $records;
  61      }
  62  
  63      /**
  64       * Stress test a report source by iterating over all it's columns, enabling sorting where possible and asserting we can
  65       * create a report for each
  66       *
  67       * @param string $source
  68       */
  69      protected function datasource_stress_test_columns(string $source): void {
  70  
  71          /** @var core_reportbuilder_generator $generator */
  72          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  73  
  74          $report = $generator->create_report(['name' => 'Stress columns', 'source' => $source, 'default' => 0]);
  75          $instance = manager::get_report_from_persistent($report);
  76  
  77          // Iterate over each available column, ensure each works correctly independent of any others.
  78          foreach ($instance->get_columns() as $columnidentifier => $columninstance) {
  79              $column = report::add_report_column($report->get('id'), $columnidentifier);
  80  
  81              // Enable sorting of the column where possible.
  82              if ($columninstance->get_is_sortable()) {
  83                  report::toggle_report_column_sorting($report->get('id'), $column->get('id'), true, SORT_DESC);
  84              }
  85  
  86              // We are only asserting the report returns content without errors, not the content itself.
  87              try {
  88                  $content = $this->get_custom_report_content($report->get('id'));
  89                  $this->assertNotEmpty($content);
  90  
  91                  // Ensure appropriate debugging was triggered for deprecated column.
  92                  if ($columninstance->get_is_deprecated()) {
  93                      $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
  94                  }
  95              } catch (Throwable $exception) {
  96                  $this->fail("Error for column '{$columnidentifier}': " . $exception->getMessage());
  97              }
  98  
  99              report::delete_report_column($report->get('id'), $column->get('id'));
 100          }
 101      }
 102  
 103      /**
 104       * Stress test a report source by iterating over all columns and asserting we can create a report while aggregating each
 105       *
 106       * @param string $source
 107       */
 108      protected function datasource_stress_test_columns_aggregation(string $source): void {
 109  
 110          /** @var core_reportbuilder_generator $generator */
 111          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 112  
 113          $report = $generator->create_report(['name' => 'Stress aggregation', 'source' => $source, 'default' => 0]);
 114          $instance = manager::get_report_from_persistent($report);
 115  
 116          // Add every column.
 117          $columndeprecatedcount = 0;
 118          foreach ($instance->get_columns() as $columnidentifier => $column) {
 119              $columndeprecatedcount += (int) $column->get_is_deprecated();
 120              report::add_report_column($report->get('id'), $columnidentifier);
 121          }
 122  
 123          // Now iterate over each column, and apply all suitable aggregation types.
 124          $columns = $instance->get_active_columns();
 125          $this->assertDebuggingCalledCount($columndeprecatedcount, null,
 126              array_fill(0, $columndeprecatedcount, DEBUG_DEVELOPER));
 127          foreach ($columns as $column) {
 128              $aggregations = aggregation::get_column_aggregations($column->get_type(), $column->get_disabled_aggregation());
 129              foreach (array_keys($aggregations) as $aggregation) {
 130                  $column->get_persistent()->set('aggregation', $aggregation)->update();
 131  
 132                  // We are only asserting the report returns content without errors, not the content itself.
 133                  try {
 134                      $content = $this->get_custom_report_content($report->get('id'));
 135                      $this->assertNotEmpty($content);
 136  
 137                      // Ensure appropriate debugging was triggered for deprecated columns.
 138                      $this->assertDebuggingCalledCount($columndeprecatedcount, null,
 139                          array_fill(0, $columndeprecatedcount, DEBUG_DEVELOPER));
 140                  } catch (Throwable $exception) {
 141                      $this->fail("Error for column '{$column->get_unique_identifier()}' with aggregation '{$aggregation}': " .
 142                          $exception->getMessage());
 143                  }
 144              }
 145  
 146              // Reset the column aggregation.
 147              $column->get_persistent()->set('aggregation', null)->update();
 148          }
 149      }
 150  
 151      /**
 152       * Stress test a report source by iterating over all it's conditions and asserting we can create a report using each
 153       *
 154       * @param string $source
 155       * @param string $columnidentifier Should be a simple column, with as few fields and joins as possible, ideally selected
 156       *      from the base table itself
 157       */
 158      protected function datasource_stress_test_conditions(string $source, string $columnidentifier): void {
 159  
 160          /** @var core_reportbuilder_generator $generator */
 161          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 162  
 163          $report = $generator->create_report(['name' => 'Stress conditions', 'source' => $source, 'default' => 0]);
 164          $instance = manager::get_report_from_persistent($report);
 165  
 166          // Add single column only (to ensure no conditions have reliance on any columns).
 167          report::add_report_column($report->get('id'), $columnidentifier);
 168  
 169          // Iterate over each available condition, ensure each works correctly independent of any others.
 170          $conditionidentifiers = array_keys($instance->get_conditions());
 171          foreach ($conditionidentifiers as $conditionidentifier) {
 172              $condition = report::add_report_condition($report->get('id'), $conditionidentifier);
 173              $conditioninstance = $instance->get_condition($condition->get('uniqueidentifier'));
 174  
 175              /** @var \core_reportbuilder\local\filters\base $conditionclass */
 176              $conditionclass = $conditioninstance->get_filter_class();
 177  
 178              // Set report condition values in order to activate it.
 179              $conditionvalues = $conditionclass::create($conditioninstance)->get_sample_values();
 180              if (empty($conditionvalues)) {
 181                  debugging("Missing sample values from filter '{$conditionclass}'", DEBUG_DEVELOPER);
 182              }
 183              $instance->set_condition_values($conditionvalues);
 184  
 185              // We are only asserting the report returns content without errors, not the content itself.
 186              try {
 187                  $content = $this->get_custom_report_content($report->get('id'));
 188                  $this->assertIsArray($content);
 189  
 190                  // Ensure appropriate debugging was triggered for deprecated condition.
 191                  if ($conditioninstance->get_is_deprecated()) {
 192                      $this->assertDebuggingCalled(null, DEBUG_DEVELOPER);
 193                  }
 194              } catch (Throwable $exception) {
 195                  $this->fail("Error for condition '{$conditionidentifier}': " . $exception->getMessage());
 196              }
 197  
 198              report::delete_report_condition($report->get('id'), $condition->get('id'));
 199          }
 200      }
 201  }