moodle/availability/tests/info_test.php
Eloy Lafuente (stronk7) 1093256560
MDL-81522 phpunit: Add missing void return type to all tests
While this change is not 100% required now, it's good habit
and we are checking for it since Moodle 4.4.

All the changes in this commit have been applied automatically
using the moodle.PHPUnit.TestReturnType sniff and are, exclusively
adding the ": void" return types when missing.
2024-06-11 12:18:04 +02:00

562 lines
26 KiB
PHP

<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_availability;
/**
* Unit tests for info and subclasses.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class info_test extends \advanced_testcase {
public function setUp(): void {
// Load the mock condition so that it can be used.
require_once(__DIR__ . '/fixtures/mock_condition.php');
}
/**
* Tests the info_module class (is_available, get_full_information).
*/
public function test_info_module(): void {
global $DB, $CFG;
// Create a course and pages.
$CFG->enableavailability = 0;
$this->setAdminUser();
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$rec = array('course' => $course);
$page1 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
$page2 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
$page3 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
$page4 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
// Set up the availability option for the pages to mock options.
$DB->set_field('course_modules', 'availability', '{"op":"|","show":true,"c":[' .
'{"type":"mock","a":false,"m":"grandmaster flash"}]}', array('id' => $page1->cmid));
$DB->set_field('course_modules', 'availability', '{"op":"|","show":true,"c":[' .
'{"type":"mock","a":true,"m":"the furious five"}]}', array('id' => $page2->cmid));
// Third page is invalid. (Fourth has no availability settings.)
$DB->set_field('course_modules', 'availability', '{{{', array('id' => $page3->cmid));
$modinfo = get_fast_modinfo($course);
$cm1 = $modinfo->get_cm($page1->cmid);
$cm2 = $modinfo->get_cm($page2->cmid);
$cm3 = $modinfo->get_cm($page3->cmid);
$cm4 = $modinfo->get_cm($page4->cmid);
// Do availability and full information checks.
$info = new info_module($cm1);
$information = '';
$this->assertFalse($info->is_available($information));
$this->assertEquals('SA: grandmaster flash', $information);
$this->assertEquals('SA: [FULL]grandmaster flash', $info->get_full_information());
$info = new info_module($cm2);
$this->assertTrue($info->is_available($information));
$this->assertEquals('', $information);
$this->assertEquals('SA: [FULL]the furious five', $info->get_full_information());
// Check invalid one.
$info = new info_module($cm3);
$this->assertFalse($info->is_available($information));
$debugging = $this->getDebuggingMessages();
$this->resetDebugging();
$this->assertEquals(1, count($debugging));
$this->assertStringContainsString('Invalid availability', $debugging[0]->message);
// Check empty one.
$info = new info_module($cm4);
$this->assertTrue($info->is_available($information));
$this->assertEquals('', $information);
$this->assertEquals('', $info->get_full_information());
}
/**
* Tests the info_section class (is_available, get_full_information).
*/
public function test_info_section(): void {
global $DB;
// Create a course.
$this->setAdminUser();
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('numsections' => 4), array('createsections' => true));
// Set up the availability option for the sections to mock options.
$DB->set_field('course_sections', 'availability', '{"op":"|","show":true,"c":[' .
'{"type":"mock","a":false,"m":"public"}]}',
array('course' => $course->id, 'section' => 1));
$DB->set_field('course_sections', 'availability', '{"op":"|","show":true,"c":[' .
'{"type":"mock","a":true,"m":"enemy"}]}',
array('course' => $course->id, 'section' => 2));
// Third section is invalid. (Fourth has no availability setting.)
$DB->set_field('course_sections', 'availability', '{{{',
array('course' => $course->id, 'section' => 3));
$modinfo = get_fast_modinfo($course);
$sections = $modinfo->get_section_info_all();
// Do availability and full information checks.
$info = new info_section($sections[1]);
$information = '';
$this->assertFalse($info->is_available($information));
$this->assertEquals('SA: public', $information);
$this->assertEquals('SA: [FULL]public', $info->get_full_information());
$info = new info_section($sections[2]);
$this->assertTrue($info->is_available($information));
$this->assertEquals('', $information);
$this->assertEquals('SA: [FULL]enemy', $info->get_full_information());
// Check invalid one.
$info = new info_section($sections[3]);
$this->assertFalse($info->is_available($information));
$debugging = $this->getDebuggingMessages();
$this->resetDebugging();
$this->assertEquals(1, count($debugging));
$this->assertStringContainsString('Invalid availability', $debugging[0]->message);
// Check empty one.
$info = new info_section($sections[4]);
$this->assertTrue($info->is_available($information));
$this->assertEquals('', $information);
$this->assertEquals('', $info->get_full_information());
}
/**
* Tests the is_user_visible() static function in info_module.
*/
public function test_is_user_visible(): void {
global $CFG, $DB;
require_once($CFG->dirroot . '/course/lib.php');
$this->resetAfterTest();
$CFG->enableavailability = 0;
// Create a course and some pages:
// 0. Invisible due to visible=0.
// 1. Availability restriction (mock, set to fail).
// 2. Availability restriction on section (mock, set to fail).
// 3. Actually visible.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('numsections' => 1), array('createsections' => true));
$rec = array('course' => $course, );
$pages = array();
$pagegen = $generator->get_plugin_generator('mod_page');
$pages[0] = $pagegen->create_instance($rec, array('visible' => 0));
$pages[1] = $pagegen->create_instance($rec);
$pages[2] = $pagegen->create_instance($rec);
$pages[3] = $pagegen->create_instance($rec);
$modinfo = get_fast_modinfo($course);
$section = $modinfo->get_section_info(1);
$cm = $modinfo->get_cm($pages[2]->cmid);
moveto_module($cm, $section);
// Set the availability restrictions in database. The enableavailability
// setting is off so these do not take effect yet.
$notavailable = '{"op":"|","show":true,"c":[{"type":"mock","a":false}]}';
$DB->set_field('course_sections', 'availability',
$notavailable, array('id' => $section->id));
$DB->set_field('course_modules', 'availability',
$notavailable, array('id' => $pages[1]->cmid));
get_fast_modinfo($course, 0, true);
// Set up 4 users - a teacher and student plus somebody who isn't even
// on the course. Also going to use admin user and a spare student to
// avoid cache problems.
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
$teacher = $generator->create_user();
$student = $generator->create_user();
$student2 = $generator->create_user();
$other = $generator->create_user();
$admin = $DB->get_record('user', array('username' => 'admin'));
$generator->enrol_user($teacher->id, $course->id, $roleids['teacher']);
$generator->enrol_user($student->id, $course->id, $roleids['student']);
$generator->enrol_user($student2->id, $course->id, $roleids['student']);
// Basic case when availability disabled, for visible item.
$this->assertTrue(info_module::is_user_visible($pages[3]->cmid, $student->id, false));
// Specifying as an object should not make any queries.
$cm = $DB->get_record('course_modules', array('id' => $pages[3]->cmid));
$beforequeries = $DB->perf_get_queries();
$this->assertTrue(info_module::is_user_visible($cm, $student->id, false));
$this->assertEquals($beforequeries, $DB->perf_get_queries());
// Specifying as cm_info for correct user should not make any more queries
// if we have already obtained dynamic data.
$modinfo = get_fast_modinfo($course, $student->id);
$cminfo = $modinfo->get_cm($cm->id);
// This will obtain dynamic data.
$name = $cminfo->name;
$beforequeries = $DB->perf_get_queries();
$this->assertTrue(info_module::is_user_visible($cminfo, $student->id, false));
$this->assertEquals($beforequeries, $DB->perf_get_queries());
// Function does not care if you are in the course (unless $checkcourse).
$this->assertTrue(info_module::is_user_visible($cm, $other->id, false));
// With $checkcourse, check for enrolled, not enrolled, and admin user.
$this->assertTrue(info_module::is_user_visible($cm, $student->id, true));
$this->assertFalse(info_module::is_user_visible($cm, $other->id, true));
$this->assertTrue(info_module::is_user_visible($cm, $admin->id, true));
// With availability off, the student can access all except the
// visible=0 one.
$this->assertFalse(info_module::is_user_visible($pages[0]->cmid, $student->id, false));
$this->assertTrue(info_module::is_user_visible($pages[1]->cmid, $student->id, false));
$this->assertTrue(info_module::is_user_visible($pages[2]->cmid, $student->id, false));
// Teacher and admin can even access the visible=0 one.
$this->assertTrue(info_module::is_user_visible($pages[0]->cmid, $teacher->id, false));
$this->assertTrue(info_module::is_user_visible($pages[0]->cmid, $admin->id, false));
// Now enable availability (and clear cache).
$CFG->enableavailability = true;
get_fast_modinfo($course, 0, true);
// Student cannot access the activity restricted by its own or by the
// section's availability.
$this->assertFalse(info_module::is_user_visible($pages[1]->cmid, $student->id, false));
$this->assertFalse(info_module::is_user_visible($pages[2]->cmid, $student->id, false));
}
/**
* Tests the convert_legacy_fields function used in restore.
*/
public function test_convert_legacy_fields(): void {
// Check with no availability conditions first.
$rec = (object)array('availablefrom' => 0, 'availableuntil' => 0,
'groupingid' => 7, 'showavailability' => 1);
$this->assertNull(info::convert_legacy_fields($rec, false));
// Check same list for a section.
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"grouping","id":7}]}',
info::convert_legacy_fields($rec, true));
// Check groupmembersonly with grouping.
$rec->groupmembersonly = 1;
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"grouping","id":7}]}',
info::convert_legacy_fields($rec, false));
// Check groupmembersonly without grouping.
$rec->groupingid = 0;
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"group"}]}',
info::convert_legacy_fields($rec, false));
// Check start date.
$rec->groupmembersonly = 0;
$rec->availablefrom = 123;
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"date","d":">=","t":123}]}',
info::convert_legacy_fields($rec, false));
// Start date with show = false.
$rec->showavailability = 0;
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"date","d":">=","t":123}]}',
info::convert_legacy_fields($rec, false));
// End date.
$rec->showavailability = 1;
$rec->availablefrom = 0;
$rec->availableuntil = 456;
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"date","d":"<","t":456}]}',
info::convert_legacy_fields($rec, false));
// All together now.
$rec->groupingid = 7;
$rec->groupmembersonly = 1;
$rec->availablefrom = 123;
$this->assertEquals(
'{"op":"&","showc":[false,true,false],"c":[' .
'{"type":"grouping","id":7},' .
'{"type":"date","d":">=","t":123},' .
'{"type":"date","d":"<","t":456}' .
']}',
info::convert_legacy_fields($rec, false));
$this->assertEquals(
'{"op":"&","showc":[false,true,false],"c":[' .
'{"type":"grouping","id":7},' .
'{"type":"date","d":">=","t":123},' .
'{"type":"date","d":"<","t":456}' .
']}',
info::convert_legacy_fields($rec, false, true));
}
/**
* Tests the add_legacy_availability_condition function used in restore.
*/
public function test_add_legacy_availability_condition(): void {
// Completion condition tests.
$rec = (object)array('sourcecmid' => 7, 'requiredcompletion' => 1);
// No previous availability, show = true.
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"completion","cm":7,"e":1}]}',
info::add_legacy_availability_condition(null, $rec, true));
// No previous availability, show = false.
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"completion","cm":7,"e":1}]}',
info::add_legacy_availability_condition(null, $rec, false));
// Existing availability.
$before = '{"op":"&","showc":[true],"c":[{"type":"date","d":">=","t":70}]}';
$this->assertEquals(
'{"op":"&","showc":[true,true],"c":['.
'{"type":"date","d":">=","t":70},' .
'{"type":"completion","cm":7,"e":1}' .
']}',
info::add_legacy_availability_condition($before, $rec, true));
// Grade condition tests.
$rec = (object)array('gradeitemid' => 3, 'grademin' => 7, 'grademax' => null);
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"grade","id":3,"min":7.00000}]}',
info::add_legacy_availability_condition(null, $rec, true));
$rec->grademax = 8;
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"grade","id":3,"min":7.00000,"max":8.00000}]}',
info::add_legacy_availability_condition(null, $rec, true));
unset($rec->grademax);
unset($rec->grademin);
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"grade","id":3}]}',
info::add_legacy_availability_condition(null, $rec, true));
// Note: There is no need to test the grade condition with show
// true/false and existing availability, because this uses the same
// function.
}
/**
* Tests the add_legacy_availability_field_condition function used in restore.
*/
public function test_add_legacy_availability_field_condition(): void {
// User field, normal operator.
$rec = (object)array('userfield' => 'email', 'shortname' => null,
'operator' => 'contains', 'value' => '@');
$this->assertEquals(
'{"op":"&","showc":[true],"c":[' .
'{"type":"profile","op":"contains","sf":"email","v":"@"}]}',
info::add_legacy_availability_field_condition(null, $rec, true));
// User field, non-value operator.
$rec = (object)array('userfield' => 'email', 'shortname' => null,
'operator' => 'isempty', 'value' => '');
$this->assertEquals(
'{"op":"&","showc":[true],"c":[' .
'{"type":"profile","op":"isempty","sf":"email"}]}',
info::add_legacy_availability_field_condition(null, $rec, true));
// Custom field.
$rec = (object)array('userfield' => null, 'shortname' => 'frogtype',
'operator' => 'isempty', 'value' => '');
$this->assertEquals(
'{"op":"&","showc":[true],"c":[' .
'{"type":"profile","op":"isempty","cf":"frogtype"}]}',
info::add_legacy_availability_field_condition(null, $rec, true));
}
/**
* Tests the filter_user_list() and get_user_list_sql() functions.
*/
public function test_filter_user_list(): void {
global $CFG, $DB;
require_once($CFG->dirroot . '/course/lib.php');
$this->resetAfterTest();
$CFG->enableavailability = true;
// Create a course with 2 sections and 2 pages and 3 users.
// Availability is set up initially on the 'page/section 2' items.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('numsections' => 2), array('createsections' => true));
$u1 = $generator->create_user();
$u2 = $generator->create_user();
$u3 = $generator->create_user();
$studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
$allusers = array($u1->id => $u1, $u2->id => $u2, $u3->id => $u3);
$generator->enrol_user($u1->id, $course->id, $studentroleid);
$generator->enrol_user($u2->id, $course->id, $studentroleid);
$generator->enrol_user($u3->id, $course->id, $studentroleid);
// Page 2 allows access to users 2 and 3, while section 2 allows access
// to users 1 and 2.
$pagegen = $generator->get_plugin_generator('mod_page');
$page = $pagegen->create_instance(array('course' => $course));
$page2 = $pagegen->create_instance(array('course' => $course,
'availability' => '{"op":"|","show":true,"c":[{"type":"mock","filter":[' .
$u2->id . ',' . $u3->id . ']}]}'));
$modinfo = get_fast_modinfo($course);
$section = $modinfo->get_section_info(1);
$section2 = $modinfo->get_section_info(2);
$DB->set_field('course_sections', 'availability',
'{"op":"|","show":true,"c":[{"type":"mock","filter":[' . $u1->id . ',' . $u2->id .']}]}',
array('id' => $section2->id));
moveto_module($modinfo->get_cm($page2->cmid), $section2);
// With no restrictions, returns full list.
$info = new info_module($modinfo->get_cm($page->cmid));
$this->assertEquals(array($u1->id, $u2->id, $u3->id),
array_keys($info->filter_user_list($allusers)));
$this->assertEquals(array('', array()), $info->get_user_list_sql(true));
// Set an availability restriction in database for section 1.
// For the section we set it so it doesn't support filters; for the
// module we have a filter.
$DB->set_field('course_sections', 'availability',
'{"op":"|","show":true,"c":[{"type":"mock","a":false}]}',
array('id' => $section->id));
$DB->set_field('course_modules', 'availability',
'{"op":"|","show":true,"c":[{"type":"mock","filter":[' . $u3->id .']}]}',
array('id' => $page->cmid));
rebuild_course_cache($course->id, true);
$modinfo = get_fast_modinfo($course);
// Now it should work (for the module).
$info = new info_module($modinfo->get_cm($page->cmid));
$expected = array($u3->id);
$this->assertEquals($expected,
array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql();
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
$info = new info_section($modinfo->get_section_info(1));
$this->assertEquals(array($u1->id, $u2->id, $u3->id),
array_keys($info->filter_user_list($allusers)));
$this->assertEquals(array('', array()), $info->get_user_list_sql(true));
// With availability disabled, module returns full list too.
$CFG->enableavailability = false;
$info = new info_module($modinfo->get_cm($page->cmid));
$this->assertEquals(array($u1->id, $u2->id, $u3->id),
array_keys($info->filter_user_list($allusers)));
$this->assertEquals(array('', array()), $info->get_user_list_sql(true));
// Check the other section...
$CFG->enableavailability = true;
$info = new info_section($modinfo->get_section_info(2));
$expected = array($u1->id, $u2->id);
$this->assertEquals($expected, array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql(true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// And the module in that section - which has combined the section and
// module restrictions.
$info = new info_module($modinfo->get_cm($page2->cmid));
$expected = array($u2->id);
$this->assertEquals($expected, array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql(true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// If the students have viewhiddenactivities, they get past the module
// restriction.
role_change_permission($studentroleid, \context_module::instance($page2->cmid),
'moodle/course:ignoreavailabilityrestrictions', CAP_ALLOW);
$expected = array($u1->id, $u2->id);
$this->assertEquals($expected, array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql(true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// If they have viewhiddensections, they also get past the section
// restriction.
role_change_permission($studentroleid, \context_course::instance($course->id),
'moodle/course:ignoreavailabilityrestrictions', CAP_ALLOW);
$expected = array($u1->id, $u2->id, $u3->id);
$this->assertEquals($expected, array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql(true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
}
/**
* Tests the info_module class when involved in a recursive call to $cm->name.
*/
public function test_info_recursive_name_call(): void {
global $DB;
$this->resetAfterTest();
// Create a course and page.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$page1 = $generator->create_module('page', ['course' => $course->id, 'name' => 'Page1']);
// Set invalid availability.
$DB->set_field('course_modules', 'availability', 'not valid', ['id' => $page1->cmid]);
// Get the cm_info object.
$this->setAdminUser();
$modinfo = get_fast_modinfo($course);
$cm1 = $modinfo->get_cm($page1->cmid);
// At this point we will generate dynamic data for $cm1, which will cause the debugging
// call below.
$this->assertEquals('Page1', $cm1->name);
$this->assertDebuggingCalled('Error processing availability data for ' .
'&lsquo;Page1&rsquo;: Invalid availability text');
}
/**
* Test for the is_available_for_all() method of the info base class.
* @covers \core_availability\info_module::is_available_for_all
*/
public function test_is_available_for_all(): void {
global $CFG, $DB;
$this->resetAfterTest();
$CFG->enableavailability = 0;
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$page = $generator->get_plugin_generator('mod_page')->create_instance(['course' => $course]);
// Set an availability restriction and reset the modinfo cache.
// The enableavailability setting is disabled so this does not take effect yet.
$notavailable = '{"op":"|","show":true,"c":[{"type":"mock","a":false}]}';
$DB->set_field('course_modules', 'availability', $notavailable, ['id' => $page->cmid]);
get_fast_modinfo($course, 0, true);
// Availability is disabled, so we expect this module to be available for everyone.
$modinfo = get_fast_modinfo($course);
$info = new info_module($modinfo->get_cm($page->cmid));
$this->assertTrue($info->is_available_for_all());
// Now, enable availability restrictions, and check again.
// This time, we expect it to return false, because of the access restriction.
$CFG->enableavailability = 1;
$this->assertFalse($info->is_available_for_all());
}
}