. /** * Tests for tour. * * @package tool_usertours * @copyright 2016 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->libdir . '/formslib.php'); use tool_usertours\tour; /** * Tests for tour. * * @package tool_usertours * @copyright 2016 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class tour_testcase extends advanced_testcase { /** * @var moodle_database */ protected $db; /** * Setup to store the DB reference. */ public function setUp() { global $DB; $this->db = $DB; } /** * Tear down to restore the original DB reference. */ public function tearDown() { global $DB; $DB = $this->db; } /** * Helper to mock the database. * * @return moodle_database */ public function mock_database() { global $DB; $DB = $this->getMockBuilder(\moodle_database::class) ->getMock() ; return $DB; } /** * Data provider for the dirty value tester. * * @return array */ public function dirty_value_provider() { return [ 'name' => [ 'name', ['Lorem'], ], 'description' => [ 'description', ['Lorem'], ], 'pathmatch' => [ 'pathmatch', ['Lorem'], ], 'enabled' => [ 'enabled', ['Lorem'], ], 'sortorder' => [ 'sortorder', [1], ], 'config' => [ 'config', ['key', 'value'], ], ]; } /** * Test that setters mark things as dirty. * * @dataProvider dirty_value_provider * @param string $name The name of the key being tested * @param mixed $value The value being set */ public function test_dirty_values($name, $value) { $tour = new \tool_usertours\tour(); $method = 'set_' . $name; call_user_func_array([$tour, $method], $value); $rc = new \ReflectionClass(\tool_usertours\tour::class); $rcp = $rc->getProperty('dirty'); $rcp->setAccessible(true); $this->assertTrue($rcp->getValue($tour)); } /** * Data provider for the get_ tests. * * @return array */ public function getter_provider() { return [ 'id' => [ 'id', rand(1, 100), ], 'name' => [ 'name', 'Lorem', ], 'description' => [ 'description', 'Lorem', ], 'pathmatch' => [ 'pathmatch', 'Lorem', ], 'enabled' => [ 'enabled', 'Lorem', ], 'sortorder' => [ 'sortorder', rand(1, 100), ], 'config' => [ 'config', ['key', 'value'], ], ]; } /** * Test that getters return the configured value. * * @dataProvider getter_provider * @param string $key The name of the key being tested * @param mixed $value The value being set */ public function test_getters($key, $value) { $tour = new \tool_usertours\tour(); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty($key); $rcp->setAccessible(true); $rcp->setValue($tour, $value); $getter = 'get_' . $key; $this->assertEquals($value, $tour->$getter()); } /** * Ensure that non-dirty tours are not persisted. */ public function test_persist_non_dirty() { $tour = $this->getMockBuilder(tour::class) ->setMethods(['to_record']) ->getMock() ; $tour->expects($this->never()) ->method('to_record') ; $this->assertSame($tour, $tour->persist()); } /** * Ensure that new dirty tours are persisted. */ public function test_persist_dirty_new() { // Mock the database. $DB = $this->mock_database(); $DB->expects($this->never()) ->method('update_record') ; $id = rand(1, 100); $DB->expects($this->once()) ->method('insert_record') ->willReturn($id) ; // Mock the tour. $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'to_record', 'reload', ]) ->getMock() ; $tour->expects($this->once()) ->method('to_record') ; $tour->expects($this->once()) ->method('reload') ; $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('dirty'); $rcp->setAccessible(true); $rcp->setValue($tour, true); $this->assertSame($tour, $tour->persist()); $rcp = $rc->getProperty('id'); $rcp->setAccessible(true); $this->assertEquals($id, $rcp->getValue($tour)); } /** * Ensure that non-dirty, forced tours are persisted. */ public function test_persist_force_new() { global $DB; // Mock the database. $DB = $this->mock_database(); $DB->expects($this->never()) ->method('update_record') ; $id = rand(1, 100); $DB->expects($this->once()) ->method('insert_record') ->willReturn($id) ; // Mock the tour. $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'to_record', 'reload', ]) ->getMock() ; $tour->expects($this->once()) ->method('to_record') ; $tour->expects($this->once()) ->method('reload') ; $this->assertSame($tour, $tour->persist(true)); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('id'); $rcp->setAccessible(true); $this->assertEquals($id, $rcp->getValue($tour)); } /** * Ensure that dirty tours are persisted. */ public function test_persist_dirty_existing() { // Mock the database. $DB = $this->mock_database(); $DB->expects($this->once()) ->method('update_record') ->willReturn($this->returnSelf()) ; $DB->expects($this->never()) ->method('insert_record') ; // Mock the tour. $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'to_record', 'reload', ]) ->getMock() ; $tour->expects($this->once()) ->method('to_record') ; $tour->expects($this->once()) ->method('reload') ; $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('id'); $rcp->setAccessible(true); $rcp->setValue($tour, 42); $rcp = $rc->getProperty('dirty'); $rcp->setAccessible(true); $rcp->setValue($tour, true); $this->assertSame($tour, $tour->persist()); } /** * Ensure that non-dirty, forced tours are persisted. */ public function test_persist_force() { global $DB; // Mock the database. $DB = $this->mock_database(); $DB->expects($this->once()) ->method('update_record') ->willReturn($this->returnSelf()) ; $DB->expects($this->never()) ->method('insert_record') ; // Mock the tour. $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'to_record', 'reload', ]) ->getMock() ; $tour->expects($this->once()) ->method('to_record') ; $tour->expects($this->once()) ->method('reload') ; $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('id'); $rcp->setAccessible(true); $rcp->setValue($tour, 42); $rcp = $rc->getProperty('dirty'); $rcp->setAccessible(true); $rcp->setValue($tour, true); $this->assertSame($tour, $tour->persist(true)); } /** * Test setting config. */ public function test_set_config() { $tour = new \tool_usertours\tour(); $tour->set_config('key', 'value'); $tour->set_config('another', [ 'foo' => 'bar', ]); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('config'); $rcp->setAccessible(true); $this->assertEquals((object) [ 'key' => 'value', 'another' => [ 'foo' => 'bar', ], ], $rcp->getValue($tour)); } /** * Test get_config with no keys provided. */ public function test_get_config_no_keys() { $tour = new \tool_usertours\tour(); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('config'); $rcp->setAccessible(true); $allvalues = (object) [ 'some' => 'value', 'another' => 42, 'key' => [ 'somethingelse', ], ]; $rcp->setValue($tour, $allvalues); $this->assertEquals($allvalues, $tour->get_config()); } /** * Data provider for get_config. * * @return array */ public function get_config_provider() { $allvalues = (object) [ 'some' => 'value', 'another' => 42, 'key' => [ 'somethingelse', ], ]; return [ 'No nitial config' => [ null, null, null, (object) [], ], 'All values' => [ $allvalues, null, null, $allvalues, ], 'Valid string value' => [ $allvalues, 'some', null, 'value', ], 'Valid array value' => [ $allvalues, 'key', null, ['somethingelse'], ], 'Invalid value' => [ $allvalues, 'notavalue', null, null, ], 'Configuration value' => [ $allvalues, 'placement', null, \tool_usertours\configuration::get_default_value('placement'), ], 'Invalid value with default' => [ $allvalues, 'notavalue', 'somedefault', 'somedefault', ], ]; } /** * Test get_config with valid keys provided. * * @dataProvider get_config_provider * @param object $values The config values * @param string $key The key * @param mixed $default The default value * @param mixed $expected The expected value */ public function test_get_config_valid_keys($values, $key, $default, $expected) { $tour = new \tool_usertours\tour(); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('config'); $rcp->setAccessible(true); $rcp->setValue($tour, $values); $this->assertEquals($expected, $tour->get_config($key, $default)); } /** * Check that a tour which has never been persisted is removed correctly. */ public function test_remove_non_persisted() { $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'get_steps', ]) ->getMock() ; $tour->expects($this->never()) ->method('get_steps') ; // Mock the database. $DB = $this->mock_database(); $DB->expects($this->never()) ->method('delete_records') ; $this->assertNull($tour->remove()); } /** * Check that a tour which has been persisted is removed correctly. */ public function test_remove_persisted() { $id = rand(1, 100); $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'get_steps', ]) ->getMock() ; $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('id'); $rcp->setAccessible(true); $rcp->setValue($tour, $id); $step = $this->getMockBuilder(\tool_usertours\step::class) ->setMethods([ 'remove', ]) ->getMock() ; $tour->expects($this->once()) ->method('get_steps') ->willReturn([$step]) ; // Mock the database. $DB = $this->mock_database(); $DB->expects($this->once()) ->method('delete_records') ->with($this->equalTo('tool_usertours_tours'), $this->equalTo(['id' => $id])) ->willReturn(null) ; $DB->expects($this->once()) ->method('get_records') ->with($this->equalTo('tool_usertours_tours'), $this->equalTo(null)) ->willReturn([]) ; $this->assertNull($tour->remove()); } /** * Teset that sortorder is reset according to sortorder with values from 0. */ public function test_reset_step_sortorder() { $tour = new \tool_usertours\tour(); $mockdata = []; for ($i = 4; $i >= 0; $i--) { $id = rand($i * 10, ($i * 10) + 9); $mockdata[] = (object) ['id' => $id]; $expectations[] = [$this->equalTo('tool_usertours_steps'), $this->equalTo('sortorder'), 4 - $i, ['id' => $id]]; } // Mock the database. $DB = $this->mock_database(); $DB->expects($this->once()) ->method('get_records') ->willReturn($mockdata) ; $setfield = $DB->expects($this->exactly(5)) ->method('set_field') ; call_user_func_array([$setfield, 'withConsecutive'], $expectations); $tour->reset_step_sortorder(); } /** * Test that a disabled tour should never be shown to users. */ public function test_should_show_for_user_disabled() { $tour = new \tool_usertours\tour(); $tour->set_enabled(false); $this->assertFalse($tour->should_show_for_user()); } /** * Provider for should_show_for_user. * * @return array */ public function should_show_for_user_provider() { $time = time(); return [ 'Not seen by user at all' => [ null, null, null, true, ], 'Completed by user before majorupdatetime' => [ $time - DAYSECS, null, $time, true, ], 'Completed by user since majorupdatetime' => [ $time, null, $time - DAYSECS, false, ], 'Requested by user before current completion' => [ $time, $time - DAYSECS, null, false, ], 'Requested by user since completion' => [ $time - DAYSECS, $time, null, true, ], ]; } /** * Test that a disabled tour should never be shown to users. * * @dataProvider should_show_for_user_provider * @param mixed $completiondate The user's completion date for this tour * @param mixed $requesteddate The user's last requested date for this tour * @param mixed $updateddate The date this tour was last updated * @param string $expectation The expected tour key */ public function test_should_show_for_user($completiondate, $requesteddate, $updateddate, $expectation) { // Uses user preferences so we must be in a user context. $this->resetAfterTest(); $this->setAdminUser(); $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'get_id', 'get_config', 'is_enabled', ]) ->getMock() ; $tour->method('is_enabled') ->willReturn(true) ; $id = rand(1, 100); $tour->method('get_id') ->willReturn($id) ; if ($completiondate !== null) { set_user_preference(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER . $id, $completiondate); } if ($requesteddate !== null) { set_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id, $requesteddate); } if ($updateddate !== null) { $tour->expects($this->once()) ->method('get_config') ->willReturn($updateddate) ; } $this->assertEquals($expectation, $tour->should_show_for_user()); } /** * Provider for get_tour_key. * * @return array */ public function get_tour_key_provider() { $id = rand(1, 100); $time = time(); return [ 'No initial values' => [ $id, [null, $time], $this->greaterThanOrEqual($time), true, null, sprintf('tool_usertours_\d_%d_%s', $id, $time), ], 'Initial tour time, no user pref' => [ $id, [$time], null, false, null, sprintf('tool_usertours_\d_%d_%s', $id, $time), ], 'Initial tour time, with user reset lower' => [ $id, [$time], null, false, $time - DAYSECS, sprintf('tool_usertours_\d_%d_%s', $id, $time), ], 'Initial tour time, with user reset higher' => [ $id, [$time], null, false, $time + DAYSECS, sprintf('tool_usertours_\d_%d_%s', $id, $time + DAYSECS), ], ]; } /** * Test that get_tour_key provides the anticipated unique keys. * * @dataProvider get_tour_key_provider * @param int $id The tour ID * @param array $getconfig The mocked values for get_config calls * @param array $setconfig The mocked values for set_config calls * @param bool $willpersist Whether a persist is expected * @param mixed $userpref The value to set for the user preference * @param string $expectation The expected tour key */ public function test_get_tour_key($id, $getconfig, $setconfig, $willpersist, $userpref, $expectation) { // Uses user preferences so we must be in a user context. $this->resetAfterTest(); $this->setAdminUser(); $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'get_config', 'set_config', 'get_id', 'persist', ]) ->getMock() ; if ($getconfig) { $tour->expects($this->exactly(count($getconfig))) ->method('get_config') ->will(call_user_func_array([$this, 'onConsecutiveCalls'], $getconfig)) ; } if ($setconfig) { $tour->expects($this->once()) ->method('set_config') ->with($this->equalTo('majorupdatetime'), $setconfig) ->will($this->returnSelf()) ; } else { $tour->expects($this->never()) ->method('set_config') ; } if ($willpersist) { $tour->expects($this->once()) ->method('persist') ; } else { $tour->expects($this->never()) ->method('persist') ; } $tour->expects($this->any()) ->method('get_id') ->willReturn($id) ; if ($userpref !== null) { set_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id, $userpref); } $this->assertRegExp( '/' . $expectation . '/', $tour->get_tour_key() ); } /** * Ensure that the request_user_reset function sets an appropriate value for the tour. */ public function test_requested_user_reset() { $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'get_id', ]) ->getMock() ; $id = rand(1, 100); $time = time(); $tour->expects($this->once()) ->method('get_id') ->willReturn($id) ; $tour->request_user_reset(); $this->assertGreaterThanOrEqual($time, get_user_preferences(\tool_usertours\tour::TOUR_REQUESTED_BY_USER . $id)); } /** * Ensure that the request_user_reset function sets an appropriate value for the tour. */ public function test_mark_user_completed() { $tour = $this->getMockBuilder(tour::class) ->setMethods([ 'get_id', ]) ->getMock() ; $id = rand(1, 100); $time = time(); $tour->expects($this->once()) ->method('get_id') ->willReturn($id) ; $tour->mark_user_completed(); $this->assertGreaterThanOrEqual($time, get_user_preferences(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER . $id)); } /** * Provider for the is_first_tour and is_last_tour tests. * * @return array */ public function sortorder_first_last_provider() { $topcount = rand(10, 100); return [ 'Only tour => first + last' => [ 0, true, 1, true, ], 'First tour of many' => [ 0, true, $topcount, false, ], 'Last tour of many' => [ $topcount - 1, false, $topcount, true, ], 'Middle tour of many' => [ 5, false, $topcount, false, ], ]; } /** * Test the is_first_tour() function. * * @dataProvider sortorder_first_last_provider * @param int $sortorder The new sort order * @param bool $isfirst Whether this is the first tour * @param int $total The number of tours * @param bool $islast Whether this is the last tour */ public function test_is_first_tour($sortorder, $isfirst, $total, $islast) { $tour = new \tool_usertours\tour(); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('sortorder'); $rcp->setAccessible(true); $rcp->setValue($tour, $sortorder); $this->assertEquals($isfirst, $tour->is_first_tour()); } /** * Test the is_last_tour() function. * * @dataProvider sortorder_first_last_provider * @param int $sortorder The new sort order * @param bool $isfirst Whether this is the first tour * @param int $total The number of tours * @param bool $islast Whether this is the last tour */ public function test_is_last_tour_calculated($sortorder, $isfirst, $total, $islast) { $tour = new \tool_usertours\tour(); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('sortorder'); $rcp->setAccessible(true); $rcp->setValue($tour, $sortorder); // The total will be calculated. $DB = $this->mock_database(); $DB->expects($this->once()) ->method('count_records') ->willReturn($total) ; $this->assertEquals($islast, $tour->is_last_tour()); } /** * Test the is_last_tour() function. * * @dataProvider sortorder_first_last_provider * @param int $sortorder The new sort order * @param bool $isfirst Whether this is the first tour * @param int $total The number of tours * @param bool $islast Whether this is the last tour */ public function test_is_last_tour_provided($sortorder, $isfirst, $total, $islast) { $tour = new \tool_usertours\tour(); $rc = new \ReflectionClass(tour::class); $rcp = $rc->getProperty('sortorder'); $rcp->setAccessible(true); $rcp->setValue($tour, $sortorder); // The total is provided. // No DB calls expected. $DB = $this->mock_database(); $DB->expects($this->never()) ->method('count_records') ->willReturn(0) ; $this->assertEquals($islast, $tour->is_last_tour($total)); } /** * Data provider for the get_filter_values tests. * * @return array */ public function get_filter_values_provider() { $cheese = ['cheddar', 'boursin', 'mozzarella']; $horses = ['coolie', 'dakota', 'leo', 'twiggy']; return [ 'No config' => [ [], 'cheese', [], ], 'Some config for another filter' => [ [ 'horses' => $horses, ], 'cheese', [], ], 'Some config for this filter' => [ [ 'horses' => $horses, ], 'horses', $horses, ], 'Some config for several filters' => [ [ 'horses' => $horses, 'cheese' => $cheese ], 'horses', $horses, ], ]; } /** * Tests for the get_filter_values function. * * @dataProvider get_filter_values_provider * @param array $fullconfig The config value being tested * @param string $filtername The name of the filter being tested * @param array $expectedvalues The expected result */ public function test_get_filter_values($fullconfig, $filtername, $expectedvalues) { $tour = $this->getMockBuilder(tour::class) ->setMethods(['get_config']) ->getMock(); $tour->expects($this->once()) ->method('get_config') ->will($this->returnValue($fullconfig)); $this->assertEquals($expectedvalues, $tour->get_filter_values($filtername)); } /** * Data provider for set_filter_values tests. * * @return array */ public function set_filter_values_provider() { $cheese = ['cheddar', 'boursin', 'mozzarella']; $horses = ['coolie', 'dakota', 'leo', 'twiggy']; return [ 'No initial value' => [ [], 'cheese', $cheese, ['cheese' => $cheese], ], 'Existing filter merged' => [ ['horses' => $horses], 'cheese', $cheese, ['horses' => $horses, 'cheese' => $cheese], ], 'Existing filter updated' => [ ['cheese' => $cheese], 'cheese', ['cheddar'], ['cheese' => ['cheddar']], ], 'Existing filter updated with merge' => [ ['horses' => $horses, 'cheese' => $cheese], 'cheese', ['cheddar'], ['horses' => $horses, 'cheese' => ['cheddar']], ], ]; } /** * Base tests for set_filter_values. * * @dataProvider set_filter_values_provider * @param array $currentvalues The current value * @param string $filtername The name of the filter to add to * @param array $newvalues The new values to store * @param array $expectedvalues The combined values */ public function test_set_filter_values_merge($currentvalues, $filtername, $newvalues, $expectedvalues) { $tour = $this->getMockBuilder(tour::class) ->setMethods(['get_config', 'set_config']) ->getMock(); $tour->expects($this->once()) ->method('get_config') ->will($this->returnValue($currentvalues)); $tour->expects($this->once()) ->method('set_config') ->with( $this->equalTo('filtervalues'), $this->equalTo($expectedvalues) ); $tour->set_filter_values($filtername, $newvalues); } }