Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

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