Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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  /**
  18   * Unit tests for rating/lib.php
  19   *
  20   * @package    core_ratings
  21   * @category   phpunit
  22   * @copyright  2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  // Include all the needed stuff.
  29  global $CFG;
  30  require_once($CFG->dirroot . '/rating/lib.php');
  31  
  32  
  33  /**
  34   * Unit test case for all the rating/lib.php requiring DB mockup & manipulation
  35   */
  36  class core_rating_testcase extends advanced_testcase {
  37  
  38      protected $syscontext;
  39      protected $neededcaps = array('view', 'viewall', 'viewany', 'rate');
  40      protected $originaldefaultfrontpageroleid;
  41  
  42      public function setUp(): void {
  43          global $CFG;
  44          parent::setUp();
  45  
  46          $this->resetAfterTest(true);
  47  
  48          $CFG->defaultfrontpageroleid = null;
  49      }
  50  
  51      /**
  52       * Test the current get_ratings method main sql
  53       */
  54      public function test_get_ratings_sql() {
  55          global $DB;
  56  
  57          // We load 3 items. Each is rated twice. For simplicity itemid == user id of the item owner.
  58          $ctxid = context_system::instance()->id;
  59          $ratings = array(
  60              // User 1's items. Average == 2.
  61              array('contextid' => $ctxid,
  62                    'component' => 'mod_forum',
  63                    'ratingarea' => 'post',
  64                    'itemid' => 1,
  65                    'scaleid' => 10,
  66                    'rating' => 1,
  67                    'userid' => 2,
  68                    'timecreated' => 1,
  69                    'timemodified' => 1),
  70  
  71              array('contextid' => $ctxid,
  72                    'component' => 'mod_forum',
  73                    'ratingarea' => 'post',
  74                    'itemid' => 1,
  75                    'scaleid' => 10,
  76                    'rating' => 3,
  77                    'userid' => 3,
  78                    'timecreated' => 1,
  79                    'timemodified' => 1),
  80  
  81              // User 2's items. Average == 3.
  82              array('contextid' => $ctxid,
  83                    'component' => 'mod_forum',
  84                    'ratingarea' => 'post',
  85                    'itemid' => 2,
  86                    'scaleid' => 10,
  87                    'rating' => 1,
  88                    'userid' => 1,
  89                    'timecreated' => 1,
  90                    'timemodified' => 1),
  91  
  92              array('contextid' => $ctxid,
  93                    'component' => 'mod_forum',
  94                    'ratingarea' => 'post',
  95                    'itemid' => 2,
  96                    'scaleid' => 10,
  97                    'rating' => 5,
  98                    'userid' => 3,
  99                    'timecreated' => 1,
 100                    'timemodified' => 1),
 101  
 102              // User 3's items. Average == 4.
 103              array('contextid' => $ctxid,
 104                    'component' => 'mod_forum',
 105                    'ratingarea' => 'post',
 106                    'itemid' => 3,
 107                    'scaleid' => 10,
 108                    'rating' => 3,
 109                    'userid' => 1,
 110                    'timecreated' => 1,
 111                    'timemodified' => 1),
 112  
 113              array('contextid' => $ctxid,
 114                    'component' => 'mod_forum',
 115                    'ratingarea' => 'post',
 116                    'itemid' => 3,
 117                    'scaleid' => 10,
 118                    'rating' => 5,
 119                    'userid' => 2,
 120                    'timecreated' => 1,
 121                    'timemodified' => 1)
 122          );
 123          foreach ($ratings as $rating) {
 124              $DB->insert_record('rating', $rating);
 125          }
 126  
 127          // A post (item) by user 1 (rated above by user 2 and 3 with average = 2).
 128          $user1posts = array(
 129              (object)array('id' => 1, 'userid' => 1, 'message' => 'hello'));
 130          // A post (item) by user 2 (rated above by user 1 and 3 with average = 3).
 131          $user2posts = array(
 132              (object)array('id' => 2, 'userid' => 2, 'message' => 'world'));
 133          // A post (item) by user 3 (rated above by user 1 and 2 with average = 4).
 134          $user3posts = array(
 135              (object)array('id' => 3, 'userid' => 3, 'message' => 'moodle'));
 136  
 137          // Prepare the default options.
 138          $defaultoptions = array (
 139              'context'    => context_system::instance(),
 140              'component'  => 'mod_forum',
 141              'ratingarea' => 'post',
 142              'scaleid'    => 10,
 143              'aggregate'  => RATING_AGGREGATE_AVERAGE);
 144  
 145          $rm = new mockup_rating_manager();
 146  
 147          // STEP 1: Retreive ratings using the current user.
 148  
 149          // Get results for user 1's item (expected average 1 + 3 / 2 = 2).
 150          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts));
 151          $result = $rm->get_ratings($toptions);
 152          $this->assertEquals(count($result), count($user1posts));
 153          $this->assertEquals($result[0]->id, $user1posts[0]->id);
 154          $this->assertEquals($result[0]->userid, $user1posts[0]->userid);
 155          $this->assertEquals($result[0]->message, $user1posts[0]->message);
 156          $this->assertEquals($result[0]->rating->count, 2);
 157          $this->assertEquals($result[0]->rating->aggregate, 2);
 158          // Note that $result[0]->rating->rating is somewhat random.
 159          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 160  
 161          // Get results for items of user 2 (expected average 1 + 5 / 2 = 3).
 162          $toptions = (object)array_merge($defaultoptions, array('items' => $user2posts));
 163          $result = $rm->get_ratings($toptions);
 164          $this->assertEquals(count($result), count($user2posts));
 165          $this->assertEquals($result[0]->id, $user2posts[0]->id);
 166          $this->assertEquals($result[0]->userid, $user2posts[0]->userid);
 167          $this->assertEquals($result[0]->message, $user2posts[0]->message);
 168          $this->assertEquals($result[0]->rating->count, 2);
 169          $this->assertEquals($result[0]->rating->aggregate, 3);
 170          // Note that $result[0]->rating->rating is somewhat random.
 171          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 172  
 173          // Get results for items of user 3 (expected average 3 + 5 / 2 = 4).
 174          $toptions = (object)array_merge($defaultoptions, array('items' => $user3posts));
 175          $result = $rm->get_ratings($toptions);
 176          $this->assertEquals(count($result), count($user3posts));
 177          $this->assertEquals($result[0]->id, $user3posts[0]->id);
 178          $this->assertEquals($result[0]->userid, $user3posts[0]->userid);
 179          $this->assertEquals($result[0]->message, $user3posts[0]->message);
 180          $this->assertEquals($result[0]->rating->count, 2);
 181          $this->assertEquals($result[0]->rating->aggregate, 4);
 182          // Note that $result[0]->rating->rating is somewhat random.
 183          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 184  
 185          // Get results for items of user 1 & 2 together (expected averages are 2 and 3, as tested above).
 186          $posts = array_merge($user1posts, $user2posts);
 187          $toptions = (object)array_merge($defaultoptions, array('items' => $posts));
 188          $result = $rm->get_ratings($toptions);
 189          $this->assertEquals(count($result), count($posts));
 190          $this->assertEquals($result[0]->id, $posts[0]->id);
 191          $this->assertEquals($result[0]->userid, $posts[0]->userid);
 192          $this->assertEquals($result[0]->message, $posts[0]->message);
 193          $this->assertEquals($result[0]->rating->count, 2);
 194          $this->assertEquals($result[0]->rating->aggregate, 2);
 195          // Note that $result[0]->rating->rating is somewhat random.
 196          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 197  
 198          $this->assertEquals($result[1]->id, $posts[1]->id);
 199          $this->assertEquals($result[1]->userid, $posts[1]->userid);
 200          $this->assertEquals($result[1]->message, $posts[1]->message);
 201          $this->assertEquals($result[1]->rating->count, 2);
 202          $this->assertEquals($result[1]->rating->aggregate, 3);
 203          // Note that $result[0]->rating->rating is somewhat random.
 204          // We didn't supply a user ID so $USER was used which will vary depending on who runs the tests.
 205  
 206          // STEP 2: Retrieve ratings by a specified user.
 207          //         We still expect complete aggregations and counts.
 208  
 209          // Get results for items of user 1 rated by user 2 (avg 2, rating 1).
 210          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts, 'userid' => 2));
 211          $result = $rm->get_ratings($toptions);
 212          $this->assertEquals(count($result), count($user1posts));
 213          $this->assertEquals($result[0]->id, $user1posts[0]->id);
 214          $this->assertEquals($result[0]->userid, $user1posts[0]->userid);
 215          $this->assertEquals($result[0]->message, $user1posts[0]->message);
 216          $this->assertEquals($result[0]->rating->count, 2);
 217          $this->assertEquals($result[0]->rating->aggregate, 2);
 218          $this->assertEquals($result[0]->rating->rating, 1); // User 2 rated user 1 "1".
 219          $this->assertEquals($result[0]->rating->userid, $toptions->userid); // Must be the passed userid.
 220  
 221          // Get results for items of user 1 rated by user 3.
 222          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts, 'userid' => 3));
 223          $result = $rm->get_ratings($toptions);
 224          $this->assertEquals(count($result), count($user1posts));
 225          $this->assertEquals($result[0]->id, $user1posts[0]->id);
 226          $this->assertEquals($result[0]->userid, $user1posts[0]->userid);
 227          $this->assertEquals($result[0]->message, $user1posts[0]->message);
 228          $this->assertEquals($result[0]->rating->count, 2);
 229          $this->assertEquals($result[0]->rating->aggregate, 2);
 230          $this->assertEquals($result[0]->rating->rating, 3); // User 3 rated user 1 "3".
 231          $this->assertEquals($result[0]->rating->userid, $toptions->userid); // Must be the passed userid.
 232  
 233          // Get results for items of user 1 & 2 together rated by user 3.
 234          $posts = array_merge($user1posts, $user2posts);
 235          $toptions = (object)array_merge($defaultoptions, array('items' => $posts, 'userid' => 3));
 236          $result = $rm->get_ratings($toptions);
 237          $this->assertEquals(count($result), count($posts));
 238          $this->assertEquals($result[0]->id, $posts[0]->id);
 239          $this->assertEquals($result[0]->userid, $posts[0]->userid);
 240          $this->assertEquals($result[0]->message, $posts[0]->message);
 241          $this->assertEquals($result[0]->rating->count, 2);
 242          $this->assertEquals($result[0]->rating->aggregate, 2);
 243          $this->assertEquals($result[0]->rating->rating, 3); // User 3 rated user 1 "3".
 244          $this->assertEquals($result[0]->rating->userid, $toptions->userid); // Must be the passed userid.
 245  
 246          $this->assertEquals($result[1]->id, $posts[1]->id);
 247          $this->assertEquals($result[1]->userid, $posts[1]->userid);
 248          $this->assertEquals($result[1]->message, $posts[1]->message);
 249          $this->assertEquals($result[1]->rating->count, 2);
 250          $this->assertEquals($result[1]->rating->aggregate, 3);
 251          $this->assertEquals($result[0]->rating->rating, 3); // User 3 rated user 2 "5".
 252          $this->assertEquals($result[1]->rating->userid, $toptions->userid); // Must be the passed userid.
 253  
 254          // STEP 3: Some special cases.
 255  
 256          // Get results for user 1's items (expected average 1 + 3 / 2 = 2).
 257          // Supplying a non-existent user id so no rating from that user should be found.
 258          $toptions = (object)array_merge($defaultoptions, array('items' => $user1posts));
 259          $toptions->userid = 123456; // Non-existent user.
 260          $result = $rm->get_ratings($toptions);
 261          $this->assertNull($result[0]->rating->userid);
 262          $this->assertNull($result[0]->rating->rating);
 263          $this->assertEquals($result[0]->rating->aggregate, 2); // Should still get the aggregate.
 264  
 265          // Get results for items of user 2 (expected average 1 + 5 / 2 = 3).
 266          // Supplying the user id of the user who owns the items so no rating should be found.
 267          $toptions = (object)array_merge($defaultoptions, array('items' => $user2posts));
 268          $toptions->userid = 2; // User 2 viewing the ratings of their own item.
 269          $result = $rm->get_ratings($toptions);
 270          // These should be null as the user is viewing their own item and thus cannot rate.
 271          $this->assertNull($result[0]->rating->userid);
 272          $this->assertNull($result[0]->rating->rating);
 273          $this->assertEquals($result[0]->rating->aggregate, 3); // Should still get the aggregate.
 274      }
 275  
 276      /**
 277       * Data provider for get_aggregate_string tests.
 278       *
 279       * @return array
 280       */
 281      public function get_aggregate_string_provider() {
 282          return [
 283              'Non-numeric aggregate produces empty string' => [
 284                  RATING_AGGREGATE_NONE,
 285                  'string',
 286                  null,
 287                  ['Foo', 'Bar'],
 288                  '',
 289              ],
 290              'Aggregate count produces empty string' => [
 291                  RATING_AGGREGATE_COUNT,
 292                  0,
 293                  null,
 294                  ['Foo', 'Bar'],
 295                  '',
 296              ],
 297              'Numeric SUM with non-numeric scale produces returns original value' => [
 298                  RATING_AGGREGATE_SUM,
 299                  10,
 300                  false,
 301                  ['Foo', 'Bar'],
 302                  '10',
 303              ],
 304              'Numeric SUM with non-numeric scale produces returns rounded value' => [
 305                  RATING_AGGREGATE_SUM,
 306                  10.45,
 307                  false,
 308                  ['Foo', 'Bar'],
 309                  '10.5',
 310              ],
 311              'Numeric SUM with numeric scale produces returns rounded value' => [
 312                  RATING_AGGREGATE_SUM,
 313                  10.45,
 314                  true,
 315                  ['Foo', 'Bar'],
 316                  '10.5',
 317              ],
 318              'Numeric AVERAGE with numeric scale produces returns rounded value' => [
 319                  RATING_AGGREGATE_AVERAGE,
 320                  10.45,
 321                  true,
 322                  ['Foo', 'Bar'],
 323                  '10.5',
 324              ],
 325              'Numeric AVERAGE with non-numeric scale produces returns indexed value (0)' => [
 326                  RATING_AGGREGATE_AVERAGE,
 327                  0,
 328                  false,
 329                  ['Foo', 'Bar'],
 330                  'Foo',
 331              ],
 332              'Numeric AVERAGE with non-numeric scale produces returns indexed value (1)' => [
 333                  RATING_AGGREGATE_AVERAGE,
 334                  1,
 335                  false,
 336                  ['Foo', 'Bar'],
 337                  'Bar',
 338              ],
 339          ];
 340      }
 341  
 342      /**
 343       * Test the value returned by get_aggregate_string().
 344       *
 345       * @dataProvider get_aggregate_string_provider
 346       */
 347      public function test_get_aggregate_string($method, $aggregate, $isnumeric, $scaleitems, $expectation) {
 348          $options = new stdClass();
 349          $options->aggregate = $aggregate;
 350          $options->context = null;
 351          $options->component = null;
 352          $options->ratingarea = null;
 353          $options->itemid = null;
 354          $options->scaleid = null;
 355          $options->userid = null;
 356  
 357          $options->settings = new stdClass();
 358          $options->settings->aggregationmethod = $method;
 359          $options->settings->scale = new stdClass();
 360          $options->settings->scale->isnumeric = $isnumeric;
 361          $options->settings->scale->scaleitems = $scaleitems;
 362  
 363          $rating = new rating($options);
 364          $this->assertEquals($expectation, $rating->get_aggregate_string());
 365      }
 366  }
 367  
 368  /**
 369   * rating_manager subclass for unit testing without requiring capabilities to be loaded
 370   */
 371  class mockup_rating_manager extends rating_manager {
 372  
 373      /**
 374       * Overwrite get_plugin_permissions_array() so it always return granted perms for unit testing
 375       */
 376      public function get_plugin_permissions_array($contextid, $component, $ratingarea) {
 377          return array(
 378              'rate' => true,
 379              'view' => true,
 380              'viewany' => true,
 381              'viewall' => true);
 382      }
 383  
 384  }