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   * Tests for tour.
  19   *
  20   * @package    tool_usertours
  21   * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  global $CFG;
  28  require_once($CFG->libdir . '/formslib.php');
  29  
  30  use tool_usertours\tour;
  31  
  32  /**
  33   * Tests for tour.
  34   *
  35   * @package    tool_usertours
  36   * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class tour_testcase extends advanced_testcase {
  40  
  41      /**
  42       * @var moodle_database
  43       */
  44      protected $db;
  45  
  46      /**
  47       * Setup to store the DB reference.
  48       */
  49      public function setUp(): void {
  50          global $DB;
  51  
  52          $this->db = $DB;
  53      }
  54  
  55      /**
  56       * Tear down to restore the original DB reference.
  57       */
  58      public function tearDown(): void {
  59          global $DB;
  60  
  61          $DB = $this->db;
  62      }
  63  
  64      /**
  65       * Helper to mock the database.
  66       *
  67       * @return \PHPUnit\Framework\MockObject\MockObject
  68       */
  69      public function mock_database() {
  70          global $DB;
  71  
  72          $DB = $this->getMockBuilder(\moodle_database::class)
  73              ->getMock()
  74              ;
  75  
  76          return $DB;
  77      }
  78  
  79      /**
  80       * Data provider for the dirty value tester.
  81       *
  82       * @return array
  83       */
  84      public function dirty_value_provider() {
  85          return [
  86                  'name' => [
  87                          'name',
  88                          ['Lorem'],
  89                      ],
  90                  'description' => [
  91                          'description',
  92                          ['Lorem'],
  93                      ],
  94                  'pathmatch' => [
  95                          'pathmatch',
  96                          ['Lorem'],
  97                      ],
  98                  'enabled' => [
  99                          'enabled',
 100                          ['Lorem'],
 101                      ],
 102                  'sortorder' => [
 103                          'sortorder',
 104                          [1],
 105                      ],
 106                  'config' => [
 107                          'config',
 108                          ['key', 'value'],
 109                      ],
 110              ];
 111      }
 112  
 113      /**
 114       * Test that setters mark things as dirty.
 115       *
 116       * @dataProvider dirty_value_provider
 117       * @param   string  $name           The name of the key being tested
 118       * @param   mixed   $value          The value being set
 119       */
 120      public function test_dirty_values($name, $value) {
 121          $tour = new \tool_usertours\tour();
 122          $method = 'set_' . $name;
 123          call_user_func_array([$tour, $method], $value);
 124  
 125          $rc = new \ReflectionClass(\tool_usertours\tour::class);
 126          $rcp = $rc->getProperty('dirty');
 127          $rcp->setAccessible(true);
 128  
 129          $this->assertTrue($rcp->getValue($tour));
 130      }
 131  
 132      /**
 133       * Data provider for the get_ tests.
 134       *
 135       * @return array
 136       */
 137      public function getter_provider() {
 138          return [
 139                  'id' => [
 140                          'id',
 141                          rand(1, 100),
 142                      ],
 143                  'name' => [
 144                          'name',
 145                          'Lorem',
 146                      ],
 147                  'description' => [
 148                          'description',
 149                          'Lorem',
 150                      ],
 151                  'pathmatch' => [
 152                          'pathmatch',
 153                          'Lorem',
 154                      ],
 155                  'enabled' => [
 156                          'enabled',
 157                          'Lorem',
 158                      ],
 159                  'sortorder' => [
 160                          'sortorder',
 161                          rand(1, 100),
 162                      ],
 163                  'config' => [
 164                          'config',
 165                          ['key', 'value'],
 166                      ],
 167              ];
 168      }
 169  
 170      /**
 171       * Test that getters return the configured value.
 172       *
 173       * @dataProvider getter_provider
 174       * @param   string  $key            The name of the key being tested
 175       * @param   mixed   $value          The value being set
 176       */
 177      public function test_getters($key, $value) {
 178          $tour = new \tool_usertours\tour();
 179  
 180          $rc = new \ReflectionClass(tour::class);
 181  
 182          $rcp = $rc->getProperty($key);
 183          $rcp->setAccessible(true);
 184          $rcp->setValue($tour, $value);
 185  
 186          $getter = 'get_' . $key;
 187  
 188          $this->assertEquals($value, $tour->$getter());
 189      }
 190  
 191      /**
 192       * Ensure that non-dirty tours are not persisted.
 193       */
 194      public function test_persist_non_dirty() {
 195          $tour = $this->getMockBuilder(tour::class)
 196              ->setMethods(['to_record'])
 197              ->getMock()
 198              ;
 199  
 200          $tour->expects($this->never())
 201              ->method('to_record')
 202              ;
 203  
 204          $this->assertSame($tour, $tour->persist());
 205      }
 206  
 207      /**
 208       * Ensure that new dirty tours are persisted.
 209       */
 210      public function test_persist_dirty_new() {
 211          // Mock the database.
 212          $DB = $this->mock_database();
 213  
 214          $DB->expects($this->never())
 215              ->method('update_record')
 216              ;
 217  
 218          $id = rand(1, 100);
 219          $DB->expects($this->once())
 220              ->method('insert_record')
 221              ->willReturn($id)
 222              ;
 223  
 224          // Mock the tour.
 225          $tour = $this->getMockBuilder(tour::class)
 226              ->setMethods([
 227                      'to_record',
 228                      'reload',
 229                  ])
 230              ->getMock()
 231              ;
 232  
 233          $tour->expects($this->once())
 234              ->method('to_record')
 235              ;
 236  
 237          $tour->expects($this->once())
 238              ->method('reload')
 239              ;
 240  
 241          $rc = new \ReflectionClass(tour::class);
 242  
 243          $rcp = $rc->getProperty('dirty');
 244          $rcp->setAccessible(true);
 245          $rcp->setValue($tour, true);
 246  
 247          $this->assertSame($tour, $tour->persist());
 248  
 249          $rcp = $rc->getProperty('id');
 250          $rcp->setAccessible(true);
 251          $this->assertEquals($id, $rcp->getValue($tour));
 252      }
 253  
 254      /**
 255       * Ensure that non-dirty, forced tours are persisted.
 256       */
 257      public function test_persist_force_new() {
 258          global $DB;
 259  
 260          // Mock the database.
 261          $DB = $this->mock_database();
 262  
 263          $DB->expects($this->never())
 264              ->method('update_record')
 265              ;
 266  
 267          $id = rand(1, 100);
 268          $DB->expects($this->once())
 269              ->method('insert_record')
 270              ->willReturn($id)
 271              ;
 272  
 273          // Mock the tour.
 274          $tour = $this->getMockBuilder(tour::class)
 275              ->setMethods([
 276                      'to_record',
 277                      'reload',
 278                  ])
 279              ->getMock()
 280              ;
 281  
 282          $tour->expects($this->once())
 283              ->method('to_record')
 284              ;
 285  
 286          $tour->expects($this->once())
 287              ->method('reload')
 288              ;
 289  
 290          $this->assertSame($tour, $tour->persist(true));
 291  
 292          $rc = new \ReflectionClass(tour::class);
 293          $rcp = $rc->getProperty('id');
 294          $rcp->setAccessible(true);
 295          $this->assertEquals($id, $rcp->getValue($tour));
 296      }
 297  
 298      /**
 299       * Ensure that dirty tours are persisted.
 300       */
 301      public function test_persist_dirty_existing() {
 302          // Mock the database.
 303          $DB = $this->mock_database();
 304          $DB->expects($this->once())
 305              ->method('update_record')
 306              ->willReturn($this->returnSelf())
 307              ;
 308  
 309          $DB->expects($this->never())
 310              ->method('insert_record')
 311              ;
 312  
 313          // Mock the tour.
 314          $tour = $this->getMockBuilder(tour::class)
 315              ->setMethods([
 316                      'to_record',
 317                      'reload',
 318                  ])
 319              ->getMock()
 320              ;
 321  
 322          $tour->expects($this->once())
 323              ->method('to_record')
 324              ;
 325  
 326          $tour->expects($this->once())
 327              ->method('reload')
 328              ;
 329  
 330          $rc = new \ReflectionClass(tour::class);
 331  
 332          $rcp = $rc->getProperty('id');
 333          $rcp->setAccessible(true);
 334          $rcp->setValue($tour, 42);
 335  
 336          $rcp = $rc->getProperty('dirty');
 337          $rcp->setAccessible(true);
 338          $rcp->setValue($tour, true);
 339  
 340          $this->assertSame($tour, $tour->persist());
 341      }
 342  
 343      /**
 344       * Ensure that non-dirty, forced tours are persisted.
 345       */
 346      public function test_persist_force() {
 347          global $DB;
 348  
 349          // Mock the database.
 350          $DB = $this->mock_database();
 351  
 352          $DB->expects($this->once())
 353              ->method('update_record')
 354              ->willReturn($this->returnSelf())
 355              ;
 356  
 357          $DB->expects($this->never())
 358              ->method('insert_record')
 359              ;
 360  
 361          // Mock the tour.
 362          $tour = $this->getMockBuilder(tour::class)
 363              ->setMethods([
 364                      'to_record',
 365                      'reload',
 366                  ])
 367              ->getMock()
 368              ;
 369  
 370          $tour->expects($this->once())
 371              ->method('to_record')
 372              ;
 373  
 374          $tour->expects($this->once())
 375              ->method('reload')
 376              ;
 377  
 378          $rc = new \ReflectionClass(tour::class);
 379  
 380          $rcp = $rc->getProperty('id');
 381          $rcp->setAccessible(true);
 382          $rcp->setValue($tour, 42);
 383  
 384          $rcp = $rc->getProperty('dirty');
 385          $rcp->setAccessible(true);
 386          $rcp->setValue($tour, true);
 387  
 388          $this->assertSame($tour, $tour->persist(true));
 389      }
 390  
 391      /**
 392       * Test setting config.
 393       */
 394      public function test_set_config() {
 395          $tour = new \tool_usertours\tour();
 396  
 397          $tour->set_config('key', 'value');
 398          $tour->set_config('another', [
 399                  'foo' => 'bar',
 400              ]);
 401  
 402          $rc = new \ReflectionClass(tour::class);
 403          $rcp = $rc->getProperty('config');
 404          $rcp->setAccessible(true);
 405          $this->assertEquals((object) [
 406                  'key' => 'value',
 407                  'another' => [
 408                      'foo' => 'bar',
 409                  ],
 410              ], $rcp->getValue($tour));
 411      }
 412  
 413      /**
 414       * Test get_config with no keys provided.
 415       */
 416      public function test_get_config_no_keys() {
 417          $tour = new \tool_usertours\tour();
 418  
 419          $rc = new \ReflectionClass(tour::class);
 420          $rcp = $rc->getProperty('config');
 421          $rcp->setAccessible(true);
 422  
 423          $allvalues = (object) [
 424                  'some' => 'value',
 425                  'another' => 42,
 426                  'key' => [
 427                      'somethingelse',
 428                  ],
 429              ];
 430  
 431          $rcp->setValue($tour, $allvalues);
 432  
 433          $this->assertEquals($allvalues, $tour->get_config());
 434      }
 435  
 436      /**
 437       * Data provider for get_config.
 438       *
 439       * @return array
 440       */
 441      public function get_config_provider() {
 442          $allvalues = (object) [
 443                  'some' => 'value',
 444                  'another' => 42,
 445                  'key' => [
 446                      'somethingelse',
 447                  ],
 448              ];
 449  
 450          return [
 451                  'No nitial config' => [
 452                          null,
 453                          null,
 454                          null,
 455                          (object) [],
 456                      ],
 457                  'All values' => [
 458                          $allvalues,
 459                          null,
 460                          null,
 461                          $allvalues,
 462                      ],
 463                  'Valid string value' => [
 464                          $allvalues,
 465                          'some',
 466                          null,
 467                          'value',
 468                      ],
 469                  'Valid array value' => [
 470                          $allvalues,
 471                          'key',
 472                          null,
 473                          ['somethingelse'],
 474                      ],
 475                  'Invalid value' => [
 476                          $allvalues,
 477                          'notavalue',
 478                          null,
 479                          null,
 480                      ],
 481                  'Configuration value' => [
 482                          $allvalues,
 483                          'placement',
 484                          null,
 485                          \tool_usertours\configuration::get_default_value('placement'),
 486                      ],
 487                  'Invalid value with default' => [
 488                          $allvalues,
 489                          'notavalue',
 490                          'somedefault',
 491                          'somedefault',
 492                      ],
 493              ];
 494      }
 495  
 496      /**
 497       * Test get_config with valid keys provided.
 498       *
 499       * @dataProvider get_config_provider
 500       * @param   object  $values     The config values
 501       * @param   string  $key        The key
 502       * @param   mixed   $default    The default value
 503       * @param   mixed   $expected   The expected value
 504       */
 505      public function test_get_config_valid_keys($values, $key, $default, $expected) {
 506          $tour = new \tool_usertours\tour();
 507  
 508          $rc = new \ReflectionClass(tour::class);
 509          $rcp = $rc->getProperty('config');
 510          $rcp->setAccessible(true);
 511          $rcp->setValue($tour, $values);
 512  
 513          $this->assertEquals($expected, $tour->get_config($key, $default));
 514      }
 515  
 516      /**
 517       * Check that a tour which has never been persisted is removed correctly.
 518       */
 519      public function test_remove_non_persisted() {
 520          $tour = $this->getMockBuilder(tour::class)
 521              ->setMethods([
 522                      'get_steps',
 523                  ])
 524              ->getMock()
 525              ;
 526  
 527          $tour->expects($this->never())
 528              ->method('get_steps')
 529              ;
 530  
 531          // Mock the database.
 532          $DB = $this->mock_database();
 533          $DB->expects($this->never())
 534              ->method('delete_records')
 535              ;
 536  
 537          $this->assertNull($tour->remove());
 538      }
 539  
 540      /**
 541       * Check that a tour which has been persisted is removed correctly.
 542       */
 543      public function test_remove_persisted() {
 544          $id = rand(1, 100);
 545  
 546          $tour = $this->getMockBuilder(tour::class)
 547              ->setMethods([
 548                      'get_steps',
 549                  ])
 550              ->getMock()
 551              ;
 552  
 553          $rc = new \ReflectionClass(tour::class);
 554          $rcp = $rc->getProperty('id');
 555          $rcp->setAccessible(true);
 556          $rcp->setValue($tour, $id);
 557  
 558          $step = $this->getMockBuilder(\tool_usertours\step::class)
 559              ->setMethods([
 560                      'remove',
 561                  ])
 562              ->getMock()
 563              ;
 564  
 565          $tour->expects($this->once())
 566              ->method('get_steps')
 567              ->willReturn([$step])
 568              ;
 569  
 570          // Mock the database.
 571          $DB = $this->mock_database();
 572  
 573          $DB->expects($this->exactly(3))
 574              ->method('delete_records')
 575              ->withConsecutive(
 576                  [$this->equalTo('tool_usertours_tours'), $this->equalTo(['id' => $id])],
 577                  [$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_LAST_COMPLETED_BY_USER . $id])],
 578                  [$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_REQUESTED_BY_USER . $id])]
 579              )
 580              ->willReturn(null)
 581              ;
 582  
 583          $DB->expects($this->once())
 584              ->method('get_records')
 585              ->with($this->equalTo('tool_usertours_tours'), $this->equalTo(null))
 586              ->willReturn([])
 587              ;
 588  
 589          $this->assertNull($tour->remove());
 590      }
 591  
 592      /**
 593       * Teset that sortorder is reset according to sortorder with values from 0.
 594       */
 595      public function test_reset_step_sortorder() {
 596          $tour = new \tool_usertours\tour();
 597  
 598          $mockdata = [];
 599          for ($i = 4; $i >= 0; $i--) {
 600              $id = rand($i * 10, ($i * 10) + 9);
 601              $mockdata[] = (object) ['id' => $id];
 602              $expectations[] = [$this->equalTo('tool_usertours_steps'), $this->equalTo('sortorder'), 4 - $i, ['id' => $id]];
 603          }
 604  
 605          // Mock the database.
 606          $DB = $this->mock_database();
 607          $DB->expects($this->once())
 608              ->method('get_records')
 609              ->willReturn($mockdata)
 610              ;
 611  
 612          $setfield = $DB->expects($this->exactly(5))
 613              ->method('set_field')
 614              ;
 615          call_user_func_array([$setfield, 'withConsecutive'], $expectations);
 616  
 617          $tour->reset_step_sortorder();
 618      }
 619  
 620      /**
 621       * Test that a disabled tour should never be shown to users.
 622       */
 623      public function test_should_show_for_user_disabled() {
 624          $tour = new \tool_usertours\tour();
 625          $tour->set_enabled(false);
 626  
 627          $this->assertFalse($tour->should_show_for_user());
 628      }
 629  
 630      /**
 631       * Provider for should_show_for_user.
 632       *
 633       * @return array
 634       */
 635      public function should_show_for_user_provider() {
 636          $time = time();
 637          return [
 638                  'Not seen by user at all' => [
 639                          null,
 640                          null,
 641                          null,
 642                          true,
 643                      ],
 644                  'Completed by user before majorupdatetime' => [
 645                          $time - DAYSECS,
 646                          null,
 647                          $time,
 648                          true,
 649                      ],
 650                  'Completed by user since majorupdatetime' => [
 651                          $time,
 652                          null,
 653                          $time - DAYSECS,
 654                          false,
 655                      ],
 656                  'Requested by user before current completion' => [
 657                          $time,
 658                          $time - DAYSECS,
 659                          null,
 660                          false,
 661                      ],
 662                  'Requested by user since completion' => [
 663                          $time - DAYSECS,
 664                          $time,
 665                          null,
 666                          true,
 667                      ],
 668              ];
 669      }
 670  
 671      /**
 672       * Test that a disabled tour should never be shown to users.
 673       *
 674       * @dataProvider should_show_for_user_provider
 675       * @param   mixed   $completiondate The user's completion date for this tour
 676       * @param   mixed   $requesteddate  The user's last requested date for this tour
 677       * @param   mixed   $updateddate    The date this tour was last updated
 678       * @param   string  $expectation    The expected tour key
 679       */
 680      public function test_should_show_for_user($completiondate, $requesteddate, $updateddate, $expectation) {
 681          // Uses user preferences so we must be in a user context.
 682          $this->resetAfterTest();
 683          $this->setAdminUser();
 684  
 685          $tour = $this->getMockBuilder(tour::class)
 686              ->setMethods([
 687                      'get_id',
 688                      'get_config',
 689                      'is_enabled',
 690                  ])
 691              ->getMock()
 692              ;
 693  
 694          $tour->method('is_enabled')
 695              ->willReturn(true)
 696              ;
 697  
 698          $id = rand(1, 100);
 699          $tour->method('get_id')
 700              ->willReturn($id)
 701              ;
 702  
 703          if ($completiondate !== null) {
 704              set_user_preference(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER . $id, $completiondate);
 705          }
 706  
 707          if ($requesteddate !== null) {
 708              set_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id, $requesteddate);
 709          }
 710  
 711          if ($updateddate !== null) {
 712              $tour->expects($this->once())
 713                  ->method('get_config')
 714                  ->willReturn($updateddate)
 715                  ;
 716          }
 717  
 718          $this->assertEquals($expectation, $tour->should_show_for_user());
 719      }
 720  
 721      /**
 722       * Provider for get_tour_key.
 723       *
 724       * @return array
 725       */
 726      public function get_tour_key_provider() {
 727          $id = rand(1, 100);
 728          $time = time();
 729  
 730          return [
 731              'No initial values' => [
 732                      $id,
 733                      [null, $time],
 734                      $this->greaterThanOrEqual($time),
 735                      true,
 736                      null,
 737                      sprintf('tool_usertours_\d_%d_%s', $id, $time),
 738                  ],
 739  
 740              'Initial tour time, no user pref' => [
 741                      $id,
 742                      [$time],
 743                      null,
 744                      false,
 745                      null,
 746                      sprintf('tool_usertours_\d_%d_%s', $id, $time),
 747                  ],
 748              'Initial tour time, with user reset lower' => [
 749                      $id,
 750                      [$time],
 751                      null,
 752                      false,
 753                      $time - DAYSECS,
 754                      sprintf('tool_usertours_\d_%d_%s', $id, $time),
 755                  ],
 756              'Initial tour time, with user reset higher' => [
 757                      $id,
 758                      [$time],
 759                      null,
 760                      false,
 761                      $time + DAYSECS,
 762                      sprintf('tool_usertours_\d_%d_%s', $id, $time + DAYSECS),
 763                  ],
 764          ];
 765      }
 766  
 767      /**
 768       * Test that get_tour_key provides the anticipated unique keys.
 769       *
 770       * @dataProvider get_tour_key_provider
 771       * @param   int     $id             The tour ID
 772       * @param   array   $getconfig      The mocked values for get_config calls
 773       * @param   array   $setconfig      The mocked values for set_config calls
 774       * @param   bool    $willpersist    Whether a persist is expected
 775       * @param   mixed   $userpref       The value to set for the user preference
 776       * @param   string  $expectation    The expected tour key
 777       */
 778      public function test_get_tour_key($id, $getconfig, $setconfig, $willpersist, $userpref, $expectation) {
 779          // Uses user preferences so we must be in a user context.
 780          $this->resetAfterTest();
 781          $this->setAdminUser();
 782  
 783          $tour = $this->getMockBuilder(tour::class)
 784              ->setMethods([
 785                      'get_config',
 786                      'set_config',
 787                      'get_id',
 788                      'persist',
 789                  ])
 790              ->getMock()
 791              ;
 792  
 793          if ($getconfig) {
 794              $tour->expects($this->exactly(count($getconfig)))
 795                  ->method('get_config')
 796                  ->will(call_user_func_array([$this, 'onConsecutiveCalls'], $getconfig))
 797                  ;
 798          }
 799  
 800          if ($setconfig) {
 801              $tour->expects($this->once())
 802                  ->method('set_config')
 803                  ->with($this->equalTo('majorupdatetime'), $setconfig)
 804                  ->will($this->returnSelf())
 805                  ;
 806          } else {
 807              $tour->expects($this->never())
 808                  ->method('set_config')
 809                  ;
 810          }
 811  
 812          if ($willpersist) {
 813              $tour->expects($this->once())
 814                  ->method('persist')
 815                  ;
 816          } else {
 817              $tour->expects($this->never())
 818                  ->method('persist')
 819                  ;
 820          }
 821  
 822          $tour->expects($this->any())
 823              ->method('get_id')
 824              ->willReturn($id)
 825              ;
 826  
 827          if ($userpref !== null) {
 828              set_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id, $userpref);
 829          }
 830  
 831          $this->assertRegExp(
 832                  '/' . $expectation . '/',
 833                  $tour->get_tour_key()
 834              );
 835      }
 836  
 837      /**
 838       * Ensure that the request_user_reset function sets an appropriate value for the tour.
 839       */
 840      public function test_requested_user_reset() {
 841          $tour = $this->getMockBuilder(tour::class)
 842              ->setMethods([
 843                      'get_id',
 844                  ])
 845              ->getMock()
 846              ;
 847  
 848          $id = rand(1, 100);
 849          $time = time();
 850  
 851          $tour->expects($this->once())
 852              ->method('get_id')
 853              ->willReturn($id)
 854              ;
 855  
 856          $tour->request_user_reset();
 857  
 858          $this->assertGreaterThanOrEqual($time, get_user_preferences(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id));
 859      }
 860  
 861      /**
 862       * Ensure that the request_user_reset function sets an appropriate value for the tour.
 863       */
 864      public function test_mark_user_completed() {
 865          $tour = $this->getMockBuilder(tour::class)
 866              ->setMethods([
 867                      'get_id',
 868                  ])
 869              ->getMock()
 870              ;
 871  
 872          $id = rand(1, 100);
 873          $time = time();
 874  
 875          $tour->expects($this->once())
 876              ->method('get_id')
 877              ->willReturn($id)
 878              ;
 879  
 880          $tour->mark_user_completed();
 881  
 882          $this->assertGreaterThanOrEqual($time, get_user_preferences(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER . $id));
 883      }
 884  
 885      /**
 886       * Provider for the is_first_tour and is_last_tour tests.
 887       *
 888       * @return array
 889       */
 890      public function sortorder_first_last_provider() {
 891          $topcount = rand(10, 100);
 892          return [
 893                  'Only tour => first + last' => [
 894                          0,
 895                          true,
 896                          1,
 897                          true,
 898                      ],
 899                  'First tour of many' => [
 900                          0,
 901                          true,
 902                          $topcount,
 903                          false,
 904                      ],
 905                  'Last tour of many' => [
 906                          $topcount - 1,
 907                          false,
 908                          $topcount,
 909                          true,
 910                      ],
 911                  'Middle tour of many' => [
 912                          5,
 913                          false,
 914                          $topcount,
 915                          false,
 916                      ],
 917              ];
 918      }
 919  
 920      /**
 921       * Test the is_first_tour() function.
 922       *
 923       * @dataProvider sortorder_first_last_provider
 924       * @param   int     $sortorder      The new sort order
 925       * @param   bool    $isfirst        Whether this is the first tour
 926       * @param   int     $total          The number of tours
 927       * @param   bool    $islast         Whether this is the last tour
 928       */
 929      public function test_is_first_tour($sortorder, $isfirst, $total, $islast) {
 930          $tour = new \tool_usertours\tour();
 931  
 932          $rc = new \ReflectionClass(tour::class);
 933          $rcp = $rc->getProperty('sortorder');
 934          $rcp->setAccessible(true);
 935          $rcp->setValue($tour, $sortorder);
 936  
 937          $this->assertEquals($isfirst, $tour->is_first_tour());
 938      }
 939  
 940      /**
 941       * Test the is_last_tour() function.
 942       *
 943       * @dataProvider sortorder_first_last_provider
 944       * @param   int     $sortorder      The new sort order
 945       * @param   bool    $isfirst        Whether this is the first tour
 946       * @param   int     $total          The number of tours
 947       * @param   bool    $islast         Whether this is the last tour
 948       */
 949      public function test_is_last_tour_calculated($sortorder, $isfirst, $total, $islast) {
 950          $tour = new \tool_usertours\tour();
 951  
 952          $rc = new \ReflectionClass(tour::class);
 953          $rcp = $rc->getProperty('sortorder');
 954          $rcp->setAccessible(true);
 955          $rcp->setValue($tour, $sortorder);
 956  
 957          // The total will be calculated.
 958          $DB = $this->mock_database();
 959          $DB->expects($this->once())
 960              ->method('count_records')
 961              ->willReturn($total)
 962              ;
 963          $this->assertEquals($islast, $tour->is_last_tour());
 964      }
 965  
 966      /**
 967       * Test the is_last_tour() function.
 968       *
 969       * @dataProvider sortorder_first_last_provider
 970       * @param   int     $sortorder      The new sort order
 971       * @param   bool    $isfirst        Whether this is the first tour
 972       * @param   int     $total          The number of tours
 973       * @param   bool    $islast         Whether this is the last tour
 974       */
 975      public function test_is_last_tour_provided($sortorder, $isfirst, $total, $islast) {
 976          $tour = new \tool_usertours\tour();
 977  
 978          $rc = new \ReflectionClass(tour::class);
 979          $rcp = $rc->getProperty('sortorder');
 980          $rcp->setAccessible(true);
 981          $rcp->setValue($tour, $sortorder);
 982  
 983          // The total is provided.
 984          // No DB calls expected.
 985          $DB = $this->mock_database();
 986          $DB->expects($this->never())
 987              ->method('count_records')
 988              ->willReturn(0)
 989              ;
 990          $this->assertEquals($islast, $tour->is_last_tour($total));
 991      }
 992  
 993      /**
 994       * Data provider for the get_filter_values tests.
 995       *
 996       * @return array
 997       */
 998      public function get_filter_values_provider() {
 999          $cheese = ['cheddar', 'boursin', 'mozzarella'];
1000          $horses = ['coolie', 'dakota', 'leo', 'twiggy'];
1001          return [
1002              'No config' => [
1003                  [],
1004                  'cheese',
1005                  [],
1006              ],
1007              'Some config for another filter' => [
1008                  [
1009                      'horses' => $horses,
1010                  ],
1011                  'cheese',
1012                  [],
1013              ],
1014              'Some config for this filter' => [
1015                  [
1016                      'horses' => $horses,
1017                  ],
1018                  'horses',
1019                  $horses,
1020              ],
1021              'Some config for several filters' => [
1022                  [
1023                      'horses' => $horses,
1024                      'cheese' => $cheese
1025                  ],
1026                  'horses',
1027                  $horses,
1028              ],
1029          ];
1030      }
1031  
1032      /**
1033       * Tests for the get_filter_values function.
1034       *
1035       * @dataProvider get_filter_values_provider
1036       * @param   array       $fullconfig     The config value being tested
1037       * @param   string      $filtername     The name of the filter being tested
1038       * @param   array       $expectedvalues The expected result
1039       */
1040      public function test_get_filter_values($fullconfig, $filtername, $expectedvalues) {
1041          $tour = $this->getMockBuilder(tour::class)
1042              ->setMethods(['get_config'])
1043              ->getMock();
1044  
1045          $tour->expects($this->once())
1046              ->method('get_config')
1047              ->will($this->returnValue($fullconfig));
1048  
1049          $this->assertEquals($expectedvalues, $tour->get_filter_values($filtername));
1050      }
1051  
1052      /**
1053       * Data provider for set_filter_values tests.
1054       *
1055       * @return  array
1056       */
1057      public function set_filter_values_provider() {
1058          $cheese = ['cheddar', 'boursin', 'mozzarella'];
1059          $horses = ['coolie', 'dakota', 'leo', 'twiggy'];
1060  
1061          return [
1062              'No initial value' => [
1063                  [],
1064                  'cheese',
1065                  $cheese,
1066                  ['cheese' => $cheese],
1067              ],
1068              'Existing filter merged' => [
1069                  ['horses' => $horses],
1070                  'cheese',
1071                  $cheese,
1072                  ['horses' => $horses, 'cheese' => $cheese],
1073              ],
1074              'Existing filter updated' => [
1075                  ['cheese' => $cheese],
1076                  'cheese',
1077                  ['cheddar'],
1078                  ['cheese' => ['cheddar']],
1079              ],
1080              'Existing filter updated with merge' => [
1081                  ['horses' => $horses, 'cheese' => $cheese],
1082                  'cheese',
1083                  ['cheddar'],
1084                  ['horses' => $horses, 'cheese' => ['cheddar']],
1085              ],
1086          ];
1087      }
1088  
1089      /**
1090       * Base tests for set_filter_values.
1091       *
1092       * @dataProvider set_filter_values_provider
1093       * @param   array       $currentvalues  The current value
1094       * @param   string      $filtername     The name of the filter to add to
1095       * @param   array       $newvalues      The new values to store
1096       * @param   array       $expectedvalues The combined values
1097       */
1098      public function test_set_filter_values_merge($currentvalues, $filtername, $newvalues, $expectedvalues) {
1099          $tour = $this->getMockBuilder(tour::class)
1100              ->setMethods(['get_config', 'set_config'])
1101              ->getMock();
1102  
1103          $tour->expects($this->once())
1104              ->method('get_config')
1105              ->will($this->returnValue($currentvalues));
1106  
1107          $tour->expects($this->once())
1108              ->method('set_config')
1109              ->with(
1110                  $this->equalTo('filtervalues'),
1111                  $this->equalTo($expectedvalues)
1112              );
1113  
1114          $tour->set_filter_values($filtername, $newvalues);
1115      }
1116  }