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.
<?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/>.

/**
 * Completion tests.
 *
 * @package    core_completion
 * @category   phpunit
 * @copyright  2008 Sam Marshall
 * @copyright  2013 Frédéric Massart
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();

global $CFG;
require_once($CFG->libdir.'/completionlib.php');

< class core_completionlib_testcase extends advanced_testcase {
> /** > * Completion tests. > * > * @package core_completion > * @category phpunit > * @copyright 2008 Sam Marshall > * @copyright 2013 Frédéric Massart > * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later > * @coversDefaultClass \completion_info > */ > class completionlib_test extends advanced_testcase {
protected $course; protected $user; protected $module1; protected $module2; protected function mock_setup() { global $DB, $CFG, $USER; $this->resetAfterTest(); $DB = $this->createMock(get_class($DB)); $CFG->enablecompletion = COMPLETION_ENABLED; $USER = (object)array('id' =>314159); } /** * Create course with user and activities. */ protected function setup_data() { global $DB, $CFG; $this->resetAfterTest(); // Enable completion before creating modules, otherwise the completion data is not written in DB. $CFG->enablecompletion = true; // Create a course with activities. $this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); $this->user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id); $this->module1 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id)); $this->module2 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id)); } /** * Asserts that two variables are equal. * * @param mixed $expected * @param mixed $actual * @param string $message * @param float $delta * @param integer $maxDepth * @param boolean $canonicalize * @param boolean $ignoreCase */ public static function assertEquals($expected, $actual, string $message = '', float $delta = 0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void { // Nasty cheating hack: prevent random failures on timemodified field.
> if (is_array($actual) && (is_object($expected) || is_array($expected))) { if (is_object($expected) and is_object($actual)) { > $actual = (object) $actual; if (property_exists($expected, 'timemodified') and property_exists($actual, 'timemodified')) { > $expected = (object) $expected; if ($expected->timemodified + 1 == $actual->timemodified) { > }
$expected = clone($expected); $expected->timemodified = $actual->timemodified; } } } parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); }
> /** public function test_is_enabled() { > * @covers ::is_enabled_for_site global $CFG; > * @covers ::is_enabled $this->mock_setup(); > */
// Config alone. $CFG->enablecompletion = COMPLETION_DISABLED; $this->assertEquals(COMPLETION_DISABLED, completion_info::is_enabled_for_site()); $CFG->enablecompletion = COMPLETION_ENABLED; $this->assertEquals(COMPLETION_ENABLED, completion_info::is_enabled_for_site()); // Course. $course = (object)array('id' =>13); $c = new completion_info($course); $course->enablecompletion = COMPLETION_DISABLED; $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled()); $course->enablecompletion = COMPLETION_ENABLED; $this->assertEquals(COMPLETION_ENABLED, $c->is_enabled()); $CFG->enablecompletion = COMPLETION_DISABLED; $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled()); // Course and CM. $cm = new stdClass(); $cm->completion = COMPLETION_TRACKING_MANUAL; $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled($cm)); $CFG->enablecompletion = COMPLETION_ENABLED; $course->enablecompletion = COMPLETION_DISABLED; $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled($cm)); $course->enablecompletion = COMPLETION_ENABLED; $this->assertEquals(COMPLETION_TRACKING_MANUAL, $c->is_enabled($cm)); $cm->completion = COMPLETION_TRACKING_NONE; $this->assertEquals(COMPLETION_TRACKING_NONE, $c->is_enabled($cm)); $cm->completion = COMPLETION_TRACKING_AUTOMATIC; $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $c->is_enabled($cm)); }
> /** public function test_update_state() { > * @covers ::update_state $this->mock_setup(); > */
$mockbuilder = $this->getMockBuilder('completion_info');
< $mockbuilder->setMethods(array('is_enabled', 'get_data', 'internal_get_state', 'internal_set_data',
> $mockbuilder->onlyMethods(array('is_enabled', 'get_data', 'internal_get_state', 'internal_set_data',
'user_can_override_completion')); $mockbuilder->setConstructorArgs(array((object)array('id' => 42)));
< $c = $mockbuilder->getMock();
$cm = (object)array('id'=>13, 'course'=>42); // Not enabled, should do nothing.
< $c->expects($this->at(0))
> $c = $mockbuilder->getMock(); > $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(false)); $c->update_state($cm); // Enabled, but current state is same as possible result, do nothing.
> $cm->completion = COMPLETION_TRACKING_AUTOMATIC; $current = (object)array('completionstate' => COMPLETION_COMPLETE, 'overrideby' => null); > $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1))
> $c->expects($this->once())
->method('get_data')
< ->with($cm, false, 0)
->will($this->returnValue($current)); $c->update_state($cm, COMPLETION_COMPLETE); // Enabled, but current state is a specific one and new state is just // complete, so do nothing.
> $c = $mockbuilder->getMock();
$current->completionstate = COMPLETION_COMPLETE_PASS;
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1))
> $c->expects($this->once())
->method('get_data')
< ->with($cm, false, 0)
->will($this->returnValue($current)); $c->update_state($cm, COMPLETION_COMPLETE); // Manual, change state (no change).
> $c = $mockbuilder->getMock();
$cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_MANUAL); $current->completionstate=COMPLETION_COMPLETE;
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1))
> $c->expects($this->once())
->method('get_data')
< ->with($cm, false, 0)
->will($this->returnValue($current)); $c->update_state($cm, COMPLETION_COMPLETE); // Manual, change state (change).
< $c->expects($this->at(0))
> $c = $mockbuilder->getMock(); > $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1))
> $c->expects($this->once())
->method('get_data')
< ->with($cm, false, 0)
->will($this->returnValue($current)); $changed = clone($current); $changed->timemodified = time(); $changed->completionstate = COMPLETION_INCOMPLETE; $comparewith = new phpunit_constraint_object_is_equal_with_exceptions($changed); $comparewith->add_exception('timemodified', 'assertGreaterThanOrEqual');
< $c->expects($this->at(2))
> $c->expects($this->once())
->method('internal_set_data') ->with($cm, $comparewith); $c->update_state($cm, COMPLETION_INCOMPLETE); // Auto, change state.
> $c = $mockbuilder->getMock();
$cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_AUTOMATIC); $current = (object)array('completionstate' => COMPLETION_COMPLETE, 'overrideby' => null);
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1))
> $c->expects($this->once())
->method('get_data')
< ->with($cm, false, 0)
->will($this->returnValue($current));
< $c->expects($this->at(2))
> $c->expects($this->once())
->method('internal_get_state') ->will($this->returnValue(COMPLETION_COMPLETE_PASS)); $changed = clone($current); $changed->timemodified = time(); $changed->completionstate = COMPLETION_COMPLETE_PASS; $comparewith = new phpunit_constraint_object_is_equal_with_exceptions($changed); $comparewith->add_exception('timemodified', 'assertGreaterThanOrEqual');
< $c->expects($this->at(3))
> $c->expects($this->once())
->method('internal_set_data') ->with($cm, $comparewith); $c->update_state($cm, COMPLETION_COMPLETE_PASS); // Manual tracking, change state by overriding it manually.
> $c = $mockbuilder->getMock();
$cm = (object)array('id' => 13, 'course' => 42, 'completion' => COMPLETION_TRACKING_MANUAL);
< $current = (object)array('completionstate' => COMPLETION_INCOMPLETE, 'overrideby' => null); < $c->expects($this->at(0))
> $current1 = (object)array('completionstate' => COMPLETION_INCOMPLETE, 'overrideby' => null); > $current2 = (object)array('completionstate' => COMPLETION_COMPLETE, 'overrideby' => null); > $c->expects($this->exactly(2))
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1)) // Pretend the user has the required capability for overriding completion statuses.
> $c->expects($this->exactly(1)) // Pretend the user has the required capability for overriding completion statuses.
->method('user_can_override_completion') ->will($this->returnValue(true));
< $c->expects($this->at(2))
> $c->expects($this->exactly(2))
->method('get_data') ->with($cm, false, 100)
< ->will($this->returnValue($current)); < $changed = clone($current); < $changed->timemodified = time(); < $changed->completionstate = COMPLETION_COMPLETE; < $changed->overrideby = 314159; < $comparewith = new phpunit_constraint_object_is_equal_with_exceptions($changed); < $comparewith->add_exception('timemodified', 'assertGreaterThanOrEqual'); < $c->expects($this->at(3))
> ->willReturnOnConsecutiveCalls($current1, $current2); > $changed1 = clone($current1); > $changed1->timemodified = time(); > $changed1->completionstate = COMPLETION_COMPLETE; > $changed1->overrideby = 314159; > $comparewith1 = new phpunit_constraint_object_is_equal_with_exceptions($changed1); > $comparewith1->add_exception('timemodified', 'assertGreaterThanOrEqual'); > $changed2 = clone($current2); > $changed2->timemodified = time(); > $changed2->overrideby = null; > $changed2->completionstate = COMPLETION_INCOMPLETE; > $comparewith2 = new phpunit_constraint_object_is_equal_with_exceptions($changed2); > $comparewith2->add_exception('timemodified', 'assertGreaterThanOrEqual'); > $c->expects($this->exactly(2))
->method('internal_set_data')
< ->with($cm, $comparewith);
> ->withConsecutive( > array($cm, $comparewith1), > array($cm, $comparewith2) > );
$c->update_state($cm, COMPLETION_COMPLETE, 100, true); // And confirm that the status can be changed back to incomplete without an override. $c->update_state($cm, COMPLETION_INCOMPLETE, 100);
< $c->expects($this->at(0)) < ->method('get_data') < ->with($cm, false, 100) < ->will($this->returnValue($current)); < $c->get_data($cm, false, 100);
// Auto, change state via override, incomplete to complete.
> $c = $mockbuilder->getMock();
$cm = (object)array('id' => 13, 'course' => 42, 'completion' => COMPLETION_TRACKING_AUTOMATIC); $current = (object)array('completionstate' => COMPLETION_INCOMPLETE, 'overrideby' => null);
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1)) // Pretend the user has the required capability for overriding completion statuses.
> $c->expects($this->once()) // Pretend the user has the required capability for overriding completion statuses.
->method('user_can_override_completion') ->will($this->returnValue(true));
< $c->expects($this->at(2))
> $c->expects($this->once())
->method('get_data') ->with($cm, false, 100) ->will($this->returnValue($current)); $changed = clone($current); $changed->timemodified = time(); $changed->completionstate = COMPLETION_COMPLETE; $changed->overrideby = 314159; $comparewith = new phpunit_constraint_object_is_equal_with_exceptions($changed); $comparewith->add_exception('timemodified', 'assertGreaterThanOrEqual');
< $c->expects($this->at(3))
> $c->expects($this->once())
->method('internal_set_data') ->with($cm, $comparewith); $c->update_state($cm, COMPLETION_COMPLETE, 100, true);
< $c->expects($this->at(0)) < ->method('get_data') < ->with($cm, false, 100) < ->will($this->returnValue($changed)); < $c->get_data($cm, false, 100); < < // Now confirm that the status cannot be changed back to incomplete without an override. < // I.e. test that automatic completion won't trigger a change back to COMPLETION_INCOMPLETE when overridden. < $c->update_state($cm, COMPLETION_INCOMPLETE, 100); < $c->expects($this->at(0)) < ->method('get_data') < ->with($cm, false, 100) < ->will($this->returnValue($changed)); < $c->get_data($cm, false, 100);
// Now confirm the status can be changed back from complete to incomplete using an override.
> $c = $mockbuilder->getMock();
$cm = (object)array('id' => 13, 'course' => 42, 'completion' => COMPLETION_TRACKING_AUTOMATIC); $current = (object)array('completionstate' => COMPLETION_COMPLETE, 'overrideby' => 2);
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1)) // Pretend the user has the required capability for overriding completion statuses.
> $c->expects($this->Once()) // Pretend the user has the required capability for overriding completion statuses.
->method('user_can_override_completion') ->will($this->returnValue(true));
< $c->expects($this->at(2))
> $c->expects($this->once())
->method('get_data') ->with($cm, false, 100) ->will($this->returnValue($current)); $changed = clone($current); $changed->timemodified = time(); $changed->completionstate = COMPLETION_INCOMPLETE; $changed->overrideby = 314159; $comparewith = new phpunit_constraint_object_is_equal_with_exceptions($changed); $comparewith->add_exception('timemodified', 'assertGreaterThanOrEqual');
< $c->expects($this->at(3))
> $c->expects($this->once())
->method('internal_set_data') ->with($cm, $comparewith); $c->update_state($cm, COMPLETION_INCOMPLETE, 100, true);
< $c->expects($this->at(0)) < ->method('get_data') < ->with($cm, false, 100) < ->will($this->returnValue($changed)); < $c->get_data($cm, false, 100);
}
< public function test_internal_get_state() { < global $DB; < $this->mock_setup();
> /** > * Data provider for test_internal_get_state(). > * > * @return array[] > */ > public function internal_get_state_provider() { > return [ > 'View required, but not viewed yet' => [ > COMPLETION_VIEW_REQUIRED, 1, '', COMPLETION_INCOMPLETE > ], > 'View not required and not viewed yet' => [ > COMPLETION_VIEW_NOT_REQUIRED, 1, '', COMPLETION_INCOMPLETE > ], > 'View not required, grade required but no grade yet, $cm->modname not set' => [ > COMPLETION_VIEW_NOT_REQUIRED, 1, 'modname', COMPLETION_INCOMPLETE > ], > 'View not required, grade required but no grade yet, $cm->course not set' => [ > COMPLETION_VIEW_NOT_REQUIRED, 1, 'course', COMPLETION_INCOMPLETE > ], > 'View not required, grade not required' => [ > COMPLETION_VIEW_NOT_REQUIRED, 0, '', COMPLETION_COMPLETE > ], > ]; > }
< $mockbuilder = $this->getMockBuilder('completion_info'); < $mockbuilder->setMethods(array('internal_get_grade_state')); < $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); < $c = $mockbuilder->getMock();
> /** > * Test for completion_info::get_state(). > * > * @dataProvider internal_get_state_provider > * @param int $completionview > * @param int $completionusegrade > * @param string $unsetfield > * @param int $expectedstate > * @covers ::internal_get_state > */ > public function test_internal_get_state(int $completionview, int $completionusegrade, string $unsetfield, int $expectedstate) { > $this->setup_data();
< $cm = (object)array('id'=>13, 'course'=>42, 'completiongradeitemnumber'=>null);
> /** @var \mod_assign_generator $assigngenerator */ > $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); > $assign = $assigngenerator->create_instance([ > 'course' => $this->course->id, > 'completion' => COMPLETION_ENABLED, > 'completionview' => $completionview, > 'completionusegrade' => $completionusegrade, > ]); > > $userid = $this->user->id; > $this->setUser($userid);
> $cm = get_coursemodule_from_instance('assign', $assign->id); // If view is required, but they haven't viewed it yet. > if ($unsetfield) { $cm->completionview = COMPLETION_VIEW_REQUIRED; > unset($cm->$unsetfield); $current = (object)array('viewed'=>COMPLETION_NOT_VIEWED); > }
< $cm->completionview = COMPLETION_VIEW_REQUIRED; < $current = (object)array('viewed'=>COMPLETION_NOT_VIEWED); < $this->assertEquals(COMPLETION_INCOMPLETE, $c->internal_get_state($cm, 123, $current));
> $current = (object)['viewed' => COMPLETION_NOT_VIEWED];
< // OK set view not required. < $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED;
> $completioninfo = new completion_info($this->course); > $this->assertEquals($expectedstate, $completioninfo->internal_get_state($cm, $userid, $current)); > }
< // Test not getting module name. < $cm->modname='label'; < $this->assertEquals(COMPLETION_COMPLETE, $c->internal_get_state($cm, 123, $current)); < < // Test getting module name. < $cm->module = 13; < unset($cm->modname); < /** @var $DB PHPUnit_Framework_MockObject_MockObject */ < $DB->expects($this->once()) < ->method('get_field') < ->with('modules', 'name', array('id'=>13)) < ->will($this->returnValue('lable')); < $this->assertEquals(COMPLETION_COMPLETE, $c->internal_get_state($cm, 123, $current)); < < // Note: This function is not fully tested (including kind of the main part) because: < // * the grade_item/grade_grade calls are static and can't be mocked, < // * the plugin_supports call is static and can't be mocked.
> /** > * Covers the case where internal_get_state() is being called for a user different from the logged in user. > * > * @covers ::internal_get_state > */ > public function test_internal_get_state_with_different_user() { > $this->setup_data(); > > /** @var \mod_assign_generator $assigngenerator */ > $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); > $assign = $assigngenerator->create_instance([ > 'course' => $this->course->id, > 'completion' => COMPLETION_ENABLED, > 'completionusegrade' => 1, > ]); > > $userid = $this->user->id; > > $cm = get_coursemodule_from_instance('assign', $assign->id); > $usercm = cm_info::create($cm, $userid); > > // Create a teacher account. > $teacher = $this->getDataGenerator()->create_user(); > $this->getDataGenerator()->enrol_user($teacher->id, $this->course->id, 'editingteacher'); > // Log in as the teacher. > $this->setUser($teacher); > > // Grade the student for this assignment. > $assign = new assign($usercm->context, $cm, $cm->course); > $data = (object)[ > 'sendstudentnotifications' => false, > 'attemptnumber' => 1, > 'grade' => 90, > ]; > $assign->save_grade($userid, $data); > > // The target user already received a grade, so internal_get_state should be already complete. > $completioninfo = new completion_info($this->course); > $this->assertEquals(COMPLETION_COMPLETE, $completioninfo->internal_get_state($cm, $userid, null)); > > // As the teacher which does not have a grade in this cm, internal_get_state should return incomplete. > $this->assertEquals(COMPLETION_INCOMPLETE, $completioninfo->internal_get_state($cm, $teacher->id, null));
}
> /** public function test_set_module_viewed() { > * Test for internal_get_state() for an activity that supports custom completion. $this->mock_setup(); > * > * @covers ::internal_get_state $mockbuilder = $this->getMockBuilder('completion_info'); > */ $mockbuilder->setMethods(array('is_enabled', 'get_data', 'internal_set_data', 'update_state')); > public function test_internal_get_state_with_custom_completion() { $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); > $this->setup_data(); $c = $mockbuilder->getMock(); > $cm = (object)array('id'=>13, 'course'=>42); > $choicerecord = [ > 'course' => $this->course, // Not tracking completion, should do nothing. > 'completion' => COMPLETION_TRACKING_AUTOMATIC, $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED; > 'completionsubmit' => COMPLETION_ENABLED, $c->set_module_viewed($cm); > ]; > $choice = $this->getDataGenerator()->create_module('choice', $choicerecord); // Tracking completion but completion is disabled, should do nothing. > $cminfo = cm_info::create(get_coursemodule_from_instance('choice', $choice->id)); $cm->completionview = COMPLETION_VIEW_REQUIRED; > $c->expects($this->at(0)) > $completioninfo = new completion_info($this->course); ->method('is_enabled') > ->with($cm) > // Fetch completion for the user who hasn't made a choice yet. ->will($this->returnValue(false)); > $completion = $completioninfo->internal_get_state($cminfo, $this->user->id, COMPLETION_INCOMPLETE); $c->set_module_viewed($cm); > $this->assertEquals(COMPLETION_INCOMPLETE, $completion); > // Now it's enabled, we expect it to get data. If data already has > // Have the user make a choice. // viewed, still do nothing. > $choicewithoptions = choice_get_choice($choice->id); $c->expects($this->at(0)) > $optionids = array_keys($choicewithoptions->option); ->method('is_enabled') > choice_user_submit_response($optionids[0], $choice, $this->user->id, $this->course, $cminfo); ->with($cm) > $completion = $completioninfo->internal_get_state($cminfo, $this->user->id, COMPLETION_INCOMPLETE); ->will($this->returnValue(true)); > $this->assertEquals(COMPLETION_COMPLETE, $completion); $c->expects($this->at(1)) > } ->method('get_data') > ->with($cm, 0) > /** ->will($this->returnValue((object)array('viewed'=>COMPLETION_VIEWED))); > * @covers ::set_module_viewed $c->set_module_viewed($cm); > */
< $mockbuilder->setMethods(array('is_enabled', 'get_data', 'internal_set_data', 'update_state'));
> $mockbuilder->onlyMethods(array('is_enabled', 'get_data', 'internal_set_data', 'update_state'));
< $c = $mockbuilder->getMock();
// and update state.
> $c = $mockbuilder->getMock();
$c->expects($this->at(0))
> $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c->expects($this->once())
< $c->expects($this->at(0))
> $c = $mockbuilder->getMock(); > $c->expects($this->once())
< $c->expects($this->at(1))
> $c->expects($this->once())
< $c->expects($this->at(0))
> $c = $mockbuilder->getMock(); > $c->expects($this->once())
< $c->expects($this->at(1))
> $c->expects($this->once())
->with($cm, false, 1337) ->will($this->returnValue((object)array('viewed'=>COMPLETION_NOT_VIEWED)));
< $c->expects($this->at(2))
> $c->expects($this->once())
->method('internal_set_data') ->with($cm, (object)array('viewed'=>COMPLETION_VIEWED));
< $c->expects($this->at(3))
> $c->expects($this->once())
->method('update_state') ->with($cm, COMPLETION_COMPLETE, 1337); $c->set_module_viewed($cm, 1337); }
> /** public function test_count_user_data() { > * @covers ::count_user_data global $DB; > */
$this->mock_setup(); $course = (object)array('id'=>13); $cm = (object)array('id'=>42); /** @var $DB PHPUnit_Framework_MockObject_MockObject */
< $DB->expects($this->at(0))
> $DB->expects($this->once())
->method('get_field_sql') ->will($this->returnValue(666)); $c = new completion_info($course); $this->assertEquals(666, $c->count_user_data($cm)); }
> /** public function test_delete_all_state() { > * @covers ::delete_all_state global $DB; > */
$this->mock_setup(); $course = (object)array('id'=>13); $cm = (object)array('id'=>42, 'course'=>13); $c = new completion_info($course); // Check it works ok without data in session. /** @var $DB PHPUnit_Framework_MockObject_MockObject */
< $DB->expects($this->at(0))
> $DB->expects($this->once())
->method('delete_records') ->with('course_modules_completion', array('coursemoduleid'=>42)) ->will($this->returnValue(true)); $c->delete_all_state($cm); }
> /** public function test_reset_all_state() { > * @covers ::reset_all_state global $DB; > */
$this->mock_setup(); $mockbuilder = $this->getMockBuilder('completion_info');
< $mockbuilder->setMethods(array('delete_all_state', 'get_tracked_users', 'update_state'));
> $mockbuilder->onlyMethods(array('delete_all_state', 'get_tracked_users', 'update_state'));
$mockbuilder->setConstructorArgs(array((object)array('id' => 42))); $c = $mockbuilder->getMock(); $cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_AUTOMATIC); /** @var $DB PHPUnit_Framework_MockObject_MockObject */
< $DB->expects($this->at(0))
> $DB->expects($this->once())
->method('get_recordset') ->will($this->returnValue(
< new core_completionlib_fake_recordset(array((object)array('id'=>1, 'userid'=>100), (object)array('id'=>2, 'userid'=>101)))));
> new core_completionlib_fake_recordset(array((object)array('id' => 1, 'userid' => 100), > (object)array('id' => 2, 'userid' => 101)))));
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('delete_all_state') ->with($cm);
< $c->expects($this->at(1))
> $c->expects($this->once())
->method('get_tracked_users') ->will($this->returnValue(array( (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh'), (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy'))));
< $c->expects($this->at(2)) < ->method('update_state') < ->with($cm, COMPLETION_UNKNOWN, 100); < $c->expects($this->at(3)) < ->method('update_state') < ->with($cm, COMPLETION_UNKNOWN, 101); < $c->expects($this->at(4))
> $c->expects($this->exactly(3))
->method('update_state')
< ->with($cm, COMPLETION_UNKNOWN, 201);
> ->withConsecutive( > array($cm, COMPLETION_UNKNOWN, 100), > array($cm, COMPLETION_UNKNOWN, 101), > array($cm, COMPLETION_UNKNOWN, 201) > );
$c->reset_all_state($cm); } /** * Data provider for test_get_data(). * * @return array[] */ public function get_data_provider() { return [ 'No completion record' => [ false, true, false, COMPLETION_INCOMPLETE ], 'Not completed' => [ false, true, true, COMPLETION_INCOMPLETE ], 'Completed' => [ false, true, true, COMPLETION_COMPLETE ], 'Whole course, complete' => [ true, true, true, COMPLETION_COMPLETE ], 'Get data for another user, result should be not cached' => [ false, false, true, COMPLETION_INCOMPLETE ], 'Get data for another user, including whole course, result should be not cached' => [ true, false, true, COMPLETION_INCOMPLETE ], ]; } /** * Tests for completion_info::get_data(). * * @dataProvider get_data_provider * @param bool $wholecourse Whole course parameter for get_data(). * @param bool $sameuser Whether the user calling get_data() is the user itself. * @param bool $hasrecord Whether to create a course_modules_completion record. * @param int $completion The completion state expected.
> * @covers ::get_data
*/ public function test_get_data(bool $wholecourse, bool $sameuser, bool $hasrecord, int $completion) { global $DB; $this->setup_data(); $user = $this->user;
< /** @var \mod_choice_generator $choicegenerator */
$choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); $choice = $choicegenerator->create_instance([ 'course' => $this->course->id,
< 'completion' => true,
> 'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionview' => true,
> 'completionsubmit' => true,
]); $cm = get_coursemodule_from_instance('choice', $choice->id);
< // Let's manually create a course completion record instead of going thru the hoops to complete an activity.
> // Let's manually create a course completion record instead of going through the hoops to complete an activity.
if ($hasrecord) { $cmcompletionrecord = (object)[ 'coursemoduleid' => $cm->id, 'userid' => $user->id, 'completionstate' => $completion, 'viewed' => 0, 'overrideby' => null, 'timemodified' => 0, ]; $DB->insert_record('course_modules_completion', $cmcompletionrecord); } // Whether we expect for the returned completion data to be stored in the cache. $iscached = true; if (!$sameuser) { $iscached = false; $this->setAdminUser(); } else { $this->setUser($user); } // Mock other completion data. $completioninfo = new completion_info($this->course); $result = $completioninfo->get_data($cm, $wholecourse, $user->id);
>
// Course module ID of the returned completion data must match this activity's course module ID. $this->assertEquals($cm->id, $result->coursemoduleid); // User ID of the returned completion data must match the user's ID. $this->assertEquals($user->id, $result->userid); // The completion state of the returned completion data must match the expected completion state. $this->assertEquals($completion, $result->completionstate); // If the user has no completion record, then the default record should be returned. if (!$hasrecord) { $this->assertEquals(0, $result->id); }
< // Check caching. < $key = "{$user->id}_{$this->course->id}"; < $cache = cache::make('core', 'completion'); < if ($iscached) { < // If we expect this to be cached, then fetching the result must match the cached data. < $this->assertEquals($result, (object)$cache->get($key)[$cm->id]); < < // Check cached data for other course modules in the course. < // The sample module created in setup_data() should suffice to confirm this. < $othercm = get_coursemodule_from_instance('forum', $this->module1->id); < if ($wholecourse) { < $this->assertArrayHasKey($othercm->id, $cache->get($key)); < } else { < $this->assertArrayNotHasKey($othercm->id, $cache->get($key));
> // Check that we are including relevant completion data for the module. > if (!$wholecourse) { > $this->assertTrue(property_exists($result, 'viewed')); > $this->assertTrue(property_exists($result, 'customcompletion'));
}
< } else { < // Otherwise, this should not be cached. < $this->assertFalse($cache->get($key));
}
> } > /** > * @covers ::get_data public function test_internal_set_data() { > */ global $DB; > public function test_get_data_successive_calls(): void { $this->setup_data(); > global $DB; > $this->setUser($this->user); > $this->setup_data(); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); > $this->setUser($this->user); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); > $cm = get_coursemodule_from_instance('forum', $forum->id); > $choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); $c = new completion_info($this->course); > $choice = $choicegenerator->create_instance([ > 'course' => $this->course->id, // 1) Test with new data. > 'completion' => COMPLETION_TRACKING_AUTOMATIC, $data = new stdClass(); > 'completionview' => true, $data->id = 0; > 'completionsubmit' => true, $data->userid = $this->user->id; > ]); $data->coursemoduleid = $cm->id; > $data->completionstate = COMPLETION_COMPLETE; > $cm = get_coursemodule_from_instance('choice', $choice->id); $data->timemodified = time(); > $data->viewed = COMPLETION_NOT_VIEWED; > // Let's manually create a course completion record instead of going through the hoops to complete an activity. $data->overrideby = null; > $cmcompletionrecord = (object) [ > 'coursemoduleid' => $cm->id, $c->internal_set_data($cm, $data); > 'userid' => $this->user->id, $d1 = $DB->get_field('course_modules_completion', 'id', array('coursemoduleid' => $cm->id)); > 'completionstate' => COMPLETION_NOT_VIEWED, $this->assertEquals($d1, $data->id); > 'viewed' => 0, $cache = cache::make('core', 'completion'); > 'overrideby' => null, // Cache was not set for another user. > 'timemodified' => 0, $this->assertEquals(array('cacherev' => $this->course->cacherev, $cm->id => $data), > ]; $cache->get($data->userid . '_' . $cm->course)); > $DB->insert_record('course_modules_completion', $cmcompletionrecord); > // 2) Test with existing data and for different user. > // Mock other completion data. $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); > $completioninfo = new completion_info($this->course); $cm2 = get_coursemodule_from_instance('forum', $forum2->id); > $newuser = $this->getDataGenerator()->create_user(); > $modinfo = get_fast_modinfo($this->course); > $results = []; $d2 = new stdClass(); > foreach ($modinfo->cms as $testcm) { $d2->id = 7; > $result = $completioninfo->get_data($testcm, true); $d2->userid = $newuser->id; > $this->assertTrue(property_exists($result, 'id')); $d2->coursemoduleid = $cm2->id; > $this->assertTrue(property_exists($result, 'coursemoduleid')); $d2->completionstate = COMPLETION_COMPLETE; > $this->assertTrue(property_exists($result, 'userid')); $d2->timemodified = time(); > $this->assertTrue(property_exists($result, 'completionstate')); $d2->viewed = COMPLETION_NOT_VIEWED; > $this->assertTrue(property_exists($result, 'viewed')); $d2->overrideby = null; > $this->assertTrue(property_exists($result, 'overrideby')); $c->internal_set_data($cm2, $d2); > $this->assertTrue(property_exists($result, 'timemodified')); // Cache for current user returns the data. > $this->assertFalse(property_exists($result, 'other_cm_completion_data_fetched')); $cachevalue = $cache->get($data->userid . '_' . $cm->course); > $this->assertEquals($data, $cachevalue[$cm->id]); > $this->assertEquals($testcm->id, $result->coursemoduleid); // Cache for another user is not filled. > $this->assertEquals($this->user->id, $result->userid); $this->assertEquals(false, $cache->get($d2->userid . '_' . $cm2->course)); > $this->assertEquals(0, $result->viewed); > // 3) Test where it THINKS the data is new (from cache) but actually > $results[$testcm->id] = $result; // in the database it has been set since. > } // 1) Test with new data. > $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); > $result = $completioninfo->get_data($cm); $cm3 = get_coursemodule_from_instance('forum', $forum3->id); > $this->assertTrue(property_exists($result, 'customcompletion')); $newuser2 = $this->getDataGenerator()->create_user(); > $d3 = new stdClass(); > // The data should match when fetching modules individually. $d3->id = 13; > (cache::make('core', 'completion'))->purge(); $d3->userid = $newuser2->id; > foreach ($modinfo->cms as $testcm) { $d3->coursemoduleid = $cm3->id; > $result = $completioninfo->get_data($testcm, false); $d3->completionstate = COMPLETION_COMPLETE; > $this->assertEquals($result, $results[$testcm->id]);
$d3->timemodified = time();
> } $d3->viewed = COMPLETION_NOT_VIEWED; > $d3->overrideby = null; > /** $DB->insert_record('course_modules_completion', $d3); > * Tests for completion_info::get_other_cm_completion_data(). $c->internal_set_data($cm, $data); > * } > * @covers ::get_other_cm_completion_data > */ public function test_get_progress_all() { > public function test_get_other_cm_completion_data() { global $DB; > global $DB; $this->mock_setup(); > > $this->setup_data(); $mockbuilder = $this->getMockBuilder('completion_info'); > $user = $this->user; $mockbuilder->setMethods(array('get_tracked_users')); > $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); > $this->setAdminUser(); $c = $mockbuilder->getMock(); > > $choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); // 1) Basic usage. > $choice = $choicegenerator->create_instance([ $c->expects($this->at(0)) > 'course' => $this->course->id, ->method('get_tracked_users') > 'completion' => COMPLETION_TRACKING_AUTOMATIC, ->with(false, array(), 0, '', '', '', null) > 'completionsubmit' => true, ->will($this->returnValue(array( > ]); (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh'), > (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy')))); > $cmchoice = cm_info::create(get_coursemodule_from_instance('choice', $choice->id)); $DB->expects($this->at(0)) > ->method('get_in_or_equal') > $choice2 = $choicegenerator->create_instance([ ->with(array(100, 201)) > 'course' => $this->course->id, ->will($this->returnValue(array(' IN (100, 201)', array()))); > 'completion' => COMPLETION_TRACKING_AUTOMATIC, $progress1 = (object)array('userid'=>100, 'coursemoduleid'=>13); > ]); $progress2 = (object)array('userid'=>201, 'coursemoduleid'=>14); > $DB->expects($this->at(1)) > $cmchoice2 = cm_info::create(get_coursemodule_from_instance('choice', $choice2->id)); ->method('get_recordset_sql') > ->will($this->returnValue(new core_completionlib_fake_recordset(array($progress1, $progress2)))); > $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); > $workshop = $workshopgenerator->create_instance([ $this->assertEquals(array( > 'course' => $this->course->id, 100 => (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh', > 'completion' => COMPLETION_TRACKING_AUTOMATIC, 'progress'=>array(13=>$progress1)), > // Submission grade required. 201 => (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy', > 'completiongradeitemnumber' => 0, 'progress'=>array(14=>$progress2)), > ]); ), $c->get_progress_all(false)); > > $cmworkshop = cm_info::create(get_coursemodule_from_instance('workshop', $workshop->id)); // 2) With more than 1, 000 results. > $tracked = array(); > $completioninfo = new completion_info($this->course); $ids = array(); > $progress = array(); > $method = new ReflectionMethod("completion_info", "get_other_cm_completion_data"); for ($i = 100; $i<2000; $i++) { > $method->setAccessible(true);
$tracked[] = (object)array('id'=>$i, 'firstname'=>'frog', 'lastname'=>$i);
> // Check that fetching data for a module with custom completion provides its info. $ids[] = $i; > $choicecompletiondata = $method->invoke($completioninfo, $cmchoice, $user->id); $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>13); > $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>14); > $this->assertArrayHasKey('customcompletion', $choicecompletiondata); } > $this->assertArrayHasKey('completionsubmit', $choicecompletiondata['customcompletion']); $c->expects($this->at(0)) > $this->assertEquals(COMPLETION_INCOMPLETE, $choicecompletiondata['customcompletion']['completionsubmit']); ->method('get_tracked_users') > ->with(true, 3, 0, '', '', '', null) > // Mock a choice answer so user has completed the requirement. ->will($this->returnValue($tracked)); > $choicemockinfo = [ $DB->expects($this->at(0)) > 'choiceid' => $cmchoice->instance, ->method('get_in_or_equal') > 'userid' => $this->user->id ->with(array_slice($ids, 0, 1000)) > ]; ->will($this->returnValue(array(' IN whatever', array()))); > $DB->insert_record('choice_answers', $choicemockinfo, false); $DB->expects($this->at(1)) > ->method('get_recordset_sql') > // Confirm fetching again reflects the completion. ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 0, 1000)))); > $choicecompletiondata = $method->invoke($completioninfo, $cmchoice, $user->id); > $this->assertEquals(COMPLETION_COMPLETE, $choicecompletiondata['customcompletion']['completionsubmit']); $DB->expects($this->at(2)) > ->method('get_in_or_equal') > // Check that fetching data for a module with no custom completion still provides its grade completion status. ->with(array_slice($ids, 1000)) > $workshopcompletiondata = $method->invoke($completioninfo, $cmworkshop, $user->id); ->will($this->returnValue(array(' IN whatever2', array()))); > $DB->expects($this->at(3)) > $this->assertArrayHasKey('completiongrade', $workshopcompletiondata); ->method('get_recordset_sql') > $this->assertArrayNotHasKey('customcompletion', $workshopcompletiondata); ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 1000)))); > $this->assertEquals(COMPLETION_INCOMPLETE, $workshopcompletiondata['completiongrade']); > $result = $c->get_progress_all(true, 3); > // Check that fetching data for a module with no completion conditions does not provide any data. $resultok = true; > $choice2completiondata = $method->invoke($completioninfo, $cmchoice2, $user->id); $resultok = $resultok && ($ids == array_keys($result)); > $this->assertEmpty($choice2completiondata); > } foreach ($result as $userid => $data) { > $resultok = $resultok && $data->firstname == 'frog'; > /** $resultok = $resultok && $data->lastname == $userid; > * @covers ::internal_set_data $resultok = $resultok && $data->id == $userid; > */
< $this->assertEquals(array('cacherev' => $this->course->cacherev, $cm->id => $data), < $cache->get($data->userid . '_' . $cm->course));
> $cachevalue = $cache->get("{$data->userid}_{$cm->course}"); > $this->assertEquals([ > 'cacherev' => $this->course->cacherev, > $cm->id => array_merge( > (array) $data, > ['other_cm_completion_data_fetched' => true] > ), > ], > $cachevalue);
< $this->assertEquals($data, $cachevalue[$cm->id]);
> $this->assertEquals(array_merge( > (array) $data, > ['other_cm_completion_data_fetched' => true] > ), $cachevalue[$cm->id]); >
< // 3) Test where it THINKS the data is new (from cache) but actually < // in the database it has been set since. < // 1) Test with new data.
> // 3) Test where it THINKS the data is new (from cache) but actually in the database it has been set since.
< public function test_get_progress_all() {
> /** > * @covers ::get_progress_all > */ > public function test_get_progress_all_few() {
< $mockbuilder->setMethods(array('get_tracked_users'));
> $mockbuilder->onlyMethods(array('get_tracked_users'));
< // 1) Basic usage. < $c->expects($this->at(0))
> // With few results. > $c->expects($this->once())
< $DB->expects($this->at(0))
> $DB->expects($this->once())
< $DB->expects($this->at(1))
> $DB->expects($this->once())
$mockbuilder->setMethods(array('is_enabled', 'update_state'));
> } $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); > $c = $mockbuilder->getMock(); > /** > * @covers ::get_progress_all $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>null); > */ $item = (object)array('itemnumber'=>3, 'gradepass'=>1, 'hidden'=>0); > public function test_get_progress_all_lots() { $grade = (object)array('userid'=>31337, 'finalgrade'=>0, 'rawgrade'=>0); > global $DB; > $this->mock_setup(); // Not enabled (should do nothing). > $c->expects($this->at(0)) > $mockbuilder = $this->getMockBuilder('completion_info'); ->method('is_enabled') > $mockbuilder->onlyMethods(array('get_tracked_users')); ->with($cm) > $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); ->will($this->returnValue(false)); > $c = $mockbuilder->getMock();
< // 2) With more than 1, 000 results.
> // With more than 1000 results.
< $c->expects($this->at(0))
> $c->expects($this->once())
< $DB->expects($this->at(0))
> $DB->expects($this->exactly(2))
< ->with(array_slice($ids, 0, 1000)) < ->will($this->returnValue(array(' IN whatever', array()))); < $DB->expects($this->at(1)) < ->method('get_recordset_sql') < ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 0, 1000)))); < < $DB->expects($this->at(2)) < ->method('get_in_or_equal') < ->with(array_slice($ids, 1000)) < ->will($this->returnValue(array(' IN whatever2', array()))); < $DB->expects($this->at(3))
> ->withConsecutive( > array(array_slice($ids, 0, 1000)), > array(array_slice($ids, 1000)) > ) > ->willReturnOnConsecutiveCalls( > array(' IN whatever', array()), > array(' IN whatever2', array())); > $DB->expects($this->exactly(2))
< ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 1000))));
> ->willReturnOnConsecutiveCalls( > new core_completionlib_fake_recordset(array_slice($progress, 0, 1000)), > new core_completionlib_fake_recordset(array_slice($progress, 1000)));
> $this->assertCount(count($tracked), $result);
// Enabled and completion required and item number right. It is supposed
> /** // to call update_state with the new potential state being obtained from > * @covers ::inform_grade_changed // internal_get_grade_state. > */
< $mockbuilder->setMethods(array('is_enabled', 'update_state'));
> $mockbuilder->onlyMethods(array('is_enabled', 'update_state'));
< $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c = $mockbuilder->getMock(); > $c->expects($this->once())
< $c->expects($this->at(0))
> $c = $mockbuilder->getMock(); > $c->expects($this->once())
->with($cm)
> $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c->expects($this->once())
$c->expects($this->at(1))
> $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c->expects($this->once())
< $c->expects($this->at(1))
> $c->expects($this->once())
->will($this->returnValue(true)); $c->inform_grade_changed($cm, $item, $grade, false); // Same as above but marked deleted. It is supposed to call update_state // with new potential state being COMPLETION_INCOMPLETE.
> $c = $mockbuilder->getMock();
$cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>3); $grade = (object)array('userid'=>31337, 'finalgrade'=>1, 'rawgrade'=>0);
< $c->expects($this->at(0))
> $c->expects($this->once())
->method('is_enabled') ->with($cm) ->will($this->returnValue(true));
< $c->expects($this->at(1))
> $c->expects($this->once())
->method('update_state') ->with($cm, COMPLETION_INCOMPLETE, 31337) ->will($this->returnValue(true)); $c->inform_grade_changed($cm, $item, $grade, true); }
> /** public function test_internal_get_grade_state() { > * @covers ::internal_get_grade_state $this->mock_setup(); > */
$item = new stdClass; $grade = new stdClass; $item->gradepass = 4; $item->hidden = 0; $grade->rawgrade = 4.0; $grade->finalgrade = null; // Grade has pass mark and is not hidden, user passes. $this->assertEquals( COMPLETION_COMPLETE_PASS, completion_info::internal_get_grade_state($item, $grade)); // Same but user fails. $grade->rawgrade = 3.9; $this->assertEquals( COMPLETION_COMPLETE_FAIL, completion_info::internal_get_grade_state($item, $grade)); // User fails on raw grade but passes on final. $grade->finalgrade = 4.0; $this->assertEquals( COMPLETION_COMPLETE_PASS, completion_info::internal_get_grade_state($item, $grade)); // Item is hidden. $item->hidden = 1; $this->assertEquals( COMPLETION_COMPLETE, completion_info::internal_get_grade_state($item, $grade)); // Item isn't hidden but has no pass mark. $item->hidden = 0; $item->gradepass = 0; $this->assertEquals( COMPLETION_COMPLETE, completion_info::internal_get_grade_state($item, $grade)); }
> /** public function test_get_activities() { > * @test ::get_activities global $CFG; > */
$this->resetAfterTest(); // Enable completion before creating modules, otherwise the completion data is not written in DB. $CFG->enablecompletion = true; // Create a course with mixed auto completion data. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); $completionmanual = array('completion' => COMPLETION_TRACKING_MANUAL); $completionnone = array('completion' => COMPLETION_TRACKING_NONE); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionauto); $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionauto); $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionmanual); $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionnone); $page2 = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionnone); $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionnone); // Create data in another course to make sure it's not considered. $course2 = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); $c2forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id), $completionauto); $c2page = $this->getDataGenerator()->create_module('page', array('course' => $course2->id), $completionmanual); $c2data = $this->getDataGenerator()->create_module('data', array('course' => $course2->id), $completionnone); $c = new completion_info($course); $activities = $c->get_activities(); $this->assertCount(3, $activities); $this->assertTrue(isset($activities[$forum->cmid])); $this->assertSame($forum->name, $activities[$forum->cmid]->name); $this->assertTrue(isset($activities[$page->cmid])); $this->assertSame($page->name, $activities[$page->cmid]->name); $this->assertTrue(isset($activities[$data->cmid])); $this->assertSame($data->name, $activities[$data->cmid]->name); $this->assertFalse(isset($activities[$forum2->cmid])); $this->assertFalse(isset($activities[$page2->cmid])); $this->assertFalse(isset($activities[$data2->cmid])); }
> /** public function test_has_activities() { > * @test ::has_activities global $CFG; > */
$this->resetAfterTest(); // Enable completion before creating modules, otherwise the completion data is not written in DB. $CFG->enablecompletion = true; // Create a course with mixed auto completion data. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); $course2 = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); $completionnone = array('completion' => COMPLETION_TRACKING_NONE); $c1forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionauto); $c2forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id), $completionnone); $c1 = new completion_info($course); $c2 = new completion_info($course2); $this->assertTrue($c1->has_activities()); $this->assertFalse($c2->has_activities()); } /** * Test that data is cleaned up when we delete courses that are set as completion criteria for other courses *
< * @return void
> * @covers ::delete_course_completion_data > * @covers ::delete_all_completion_data
*/ public function test_course_delete_prerequisite() { global $DB; $this->setup_data(); $courseprerequisite = $this->getDataGenerator()->create_course(['enablecompletion' => true]); $criteriadata = (object) [ 'id' => $this->course->id, 'criteria_course' => [$courseprerequisite->id], ]; /** @var completion_criteria_course $criteria */ $criteria = completion_criteria::factory(['criteriatype' => COMPLETION_CRITERIA_TYPE_COURSE]); $criteria->update_config($criteriadata); // Sanity test. $this->assertTrue($DB->record_exists('course_completion_criteria', [ 'course' => $this->course->id, 'criteriatype' => COMPLETION_CRITERIA_TYPE_COURSE, 'courseinstance' => $courseprerequisite->id, ])); // Deleting the prerequisite course should remove the completion criteria. delete_course($courseprerequisite, false); $this->assertFalse($DB->record_exists('course_completion_criteria', [ 'course' => $this->course->id, 'criteriatype' => COMPLETION_CRITERIA_TYPE_COURSE, 'courseinstance' => $courseprerequisite->id, ])); } /** * Test course module completion update event.
> * */ > * @covers \core\event\course_module_completion_updated
public function test_course_module_completion_updated_event() { global $USER, $CFG; $this->setup_data(); $this->setAdminUser(); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); $c = new completion_info($this->course); $activities = $c->get_activities(); $this->assertEquals(1, count($activities)); $this->assertTrue(isset($activities[$forum->cmid])); $this->assertEquals($activities[$forum->cmid]->name, $forum->name); $current = $c->get_data($activities[$forum->cmid], false, $this->user->id); $current->completionstate = COMPLETION_COMPLETE; $current->timemodified = time(); $sink = $this->redirectEvents(); $c->internal_set_data($activities[$forum->cmid], $current); $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\course_module_completion_updated', $event);
< $this->assertEquals($forum->cmid, $event->get_record_snapshot('course_modules_completion', $event->objectid)->coursemoduleid);
> $this->assertEquals($forum->cmid, > $event->get_record_snapshot('course_modules_completion', $event->objectid)->coursemoduleid);
$this->assertEquals($current, $event->get_record_snapshot('course_modules_completion', $event->objectid)); $this->assertEquals(context_module::instance($forum->cmid), $event->get_context()); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->user->id, $event->relateduserid); $this->assertInstanceOf('moodle_url', $event->get_url()); $this->assertEventLegacyData($current, $event); } /** * Test course completed event.
> * */ > * @covers \core\event\course_completed
public function test_course_completed_event() { global $USER; $this->setup_data(); $this->setAdminUser(); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); $ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id)); // Mark course as complete and get triggered event. $sink = $this->redirectEvents(); $ccompletion->mark_complete(); $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\course_completed', $event); $this->assertEquals($this->course->id, $event->get_record_snapshot('course_completions', $event->objectid)->course); $this->assertEquals($this->course->id, $event->courseid); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->user->id, $event->relateduserid); $this->assertEquals(context_course::instance($this->course->id), $event->get_context()); $this->assertInstanceOf('moodle_url', $event->get_url()); $data = $ccompletion->get_record_data(); $this->assertEventLegacyData($data, $event); } /**
> * Test course completed message. * Test course completed event. > * */ > * @covers \core\event\course_completed public function test_course_completion_updated_event() { > */ $this->setup_data(); > public function test_course_completed_message() { $coursecontext = context_course::instance($this->course->id); > $this->setup_data(); $coursecompletionevent = \core\event\course_completion_updated::create( > $this->setAdminUser(); array( > 'courseid' => $this->course->id, > $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); 'context' => $coursecontext > $ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id)); ) > ); > // Mark course as complete and get the message. > $sink = $this->redirectMessages(); // Mark course as complete and get triggered event. > $ccompletion->mark_complete(); $sink = $this->redirectEvents(); > $messages = $sink->get_messages(); $coursecompletionevent->trigger(); > $sink->close(); $events = $sink->get_events(); > $event = array_pop($events); > $this->assertCount(1, $messages); $sink->close(); > $message = array_pop($messages); > $this->assertInstanceOf('\core\event\course_completion_updated', $event); > $this->assertEquals(core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($this->course->id, $event->courseid); > $this->assertEquals($this->user->id, $message->useridto); $this->assertEquals($coursecontext, $event->get_context()); > $this->assertEquals('coursecompleted', $message->eventtype); $this->assertInstanceOf('moodle_url', $event->get_url()); > $this->assertEquals(get_string('coursecompleted', 'completion'), $message->subject); $expectedlegacylog = array($this->course->id, 'course', 'completion updated', 'completion.php?id='.$this->course->id); > $this->assertStringContainsString($this->course->fullname, $message->fullmessage); $this->assertEventLegacyLogData($expectedlegacylog, $event); > } } > > /**
public function test_completion_can_view_data() {
> * $this->setup_data(); > * @covers \core\event\course_completion_updated
> /** $student = $this->getDataGenerator()->create_user(); > * @covers \completion_can_view_data $this->getDataGenerator()->enrol_user($student->id, $this->course->id); > */
$this->setUser($student); $this->assertTrue(completion_can_view_data($student->id, $this->course->id)); $this->assertFalse(completion_can_view_data($this->user->id, $this->course->id));
> } } > } > /** > * Data provider for test_get_grade_completion(). class core_completionlib_fake_recordset implements Iterator { > * protected $closed; > * @return array[] protected $values, $index; > */ > public function get_grade_completion_provider() { public function __construct($values) { > return [ $this->values = $values; > 'Grade not required' => [false, false, null, null, null], $this->index = 0; > 'Grade required, but has no grade yet' => [true, false, null, null, COMPLETION_INCOMPLETE], } > 'Grade required, grade received' => [true, true, null, null, COMPLETION_COMPLETE], > 'Grade required, passing grade received' => [true, true, 70, null, COMPLETION_COMPLETE_PASS], public function current() { > 'Grade required, failing grade received' => [true, true, 80, null, COMPLETION_COMPLETE_FAIL], return $this->values[$this->index]; > ]; } > } > public function key() { > /** return $this->values[$this->index]; > * Test for \completion_info::get_grade_completion(). } > * > * @dataProvider get_grade_completion_provider public function next() { > * @param bool $completionusegrade Whether the test activity has grade completion requirement. $this->index++; > * @param bool $hasgrade Whether to set grade for the user in this activity. } > * @param int|null $passinggrade Passing grade to set for the test activity. > * @param string|null $expectedexception Expected exception. public function rewind() { > * @param int|null $expectedresult The expected completion status. $this->index = 0; > * @covers ::get_grade_completion } > */ > public function test_get_grade_completion(bool $completionusegrade, bool $hasgrade, ?int $passinggrade, public function valid() { > ?string $expectedexception, ?int $expectedresult) { return count($this->values) > $this->index; > $this->setup_data(); } > > /** @var \mod_assign_generator $assigngenerator */ public function close() { > $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); $this->closed = true; > $assign = $assigngenerator->create_instance([ } > 'course' => $this->course->id, > 'completion' => COMPLETION_ENABLED, public function was_closed() { > 'completionusegrade' => $completionusegrade, return $this->closed; > 'gradepass' => $passinggrade, } > ]); } > > $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign->id)); > if ($completionusegrade && $hasgrade) { > $assigninstance = new assign($cm->context, $cm, $this->course); > $grade = $assigninstance->get_user_grade($this->user->id, true); > $grade->grade = 75; > $assigninstance->update_grade($grade); > } > > $completioninfo = new completion_info($this->course); > if ($expectedexception) { > $this->expectException($expectedexception); > } > $gradecompletion = $completioninfo->get_grade_completion($cm, $this->user->id); > $this->assertEquals($expectedresult, $gradecompletion); > } > > /** > * Test the return value for cases when the activity module does not have associated grade_item. > * > * @covers ::get_grade_completion > */ > public function test_get_grade_completion_without_grade_item() { > global $DB; > > $this->setup_data(); > > $assign = $this->getDataGenerator()->get_plugin_generator('mod_assign')->create_instance([ > 'course' => $this->course->id, > 'completion' => COMPLETION_ENABLED, > 'completionusegrade' => true, > 'gradepass' => 42, > ]); > > $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign->id)); > > $DB->delete_records('grade_items', [ > 'courseid' => $this->course->id, > 'itemtype' => 'mod', > 'itemmodule' => 'assign', > 'iteminstance' => $assign->id, > ]); > > // Without the grade_item, the activity is considered incomplete. > $completioninfo = new completion_info($this->course); > $this->assertEquals(COMPLETION_INCOMPLETE, $completioninfo->get_grade_completion($cm, $this->user->id)); > > // Once the activity is graded, the grade_item is automatically created. > $assigninstance = new assign($cm->context, $cm, $this->course); > $grade = $assigninstance->get_user_grade($this->user->id, true); > $grade->grade = 40; > $assigninstance->update_grade($grade); > > // The implicitly created grade_item does not have grade to pass defined so it is not distinguished. > $this->assertEquals(COMPLETION_COMPLETE, $completioninfo->get_grade_completion($cm, $this->user->id));