Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.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/>.

> defined('MOODLE_INTERNAL') || die(); /** > * Completion tests. > global $CFG; * > require_once($CFG->libdir.'/completionlib.php'); * @package core_completion >
< * @category phpunit
> * @category test
* @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
*/
< < defined('MOODLE_INTERNAL') || die(); < < global $CFG; < require_once($CFG->libdir.'/completionlib.php'); < < class core_completionlib_testcase extends advanced_testcase {
> 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.
> /** > * Provider for the test_internal_get_state_with_grade_criteria. > * > * @return array > */ > public function internal_get_state_with_grade_criteria_provider() { > return [ > "Passing grade enabled and achieve. State should be COMPLETION_COMPLETE_PASS" => [ > [ > 'completionusegrade' => 1, > 'completionpassgrade' => 1, > 'gradepass' => 50, > ], > 50, > COMPLETION_COMPLETE_PASS > ], > "Passing grade enabled and not achieve. State should be COMPLETION_COMPLETE_FAIL" => [ > [ > 'completionusegrade' => 1, > 'completionpassgrade' => 1, > 'gradepass' => 50, > ], > 40, > COMPLETION_COMPLETE_FAIL > ], > "Passing grade not enabled with passing grade set." => [ > [ > 'completionusegrade' => 1, > 'gradepass' => 50, > ], > 50, > COMPLETION_COMPLETE_PASS > ], > "Passing grade not enabled with passing grade not set." => [ > [ > 'completionusegrade' => 1, > ], > 90, > COMPLETION_COMPLETE > ], > "Passing grade not enabled with passing grade not set. No submission made." => [ > [ > 'completionusegrade' => 1, > ], > null, > COMPLETION_INCOMPLETE > ], > ]; > } > > /** > * Tests that the right completion state is being set based on the grade criteria. > * > * @dataProvider internal_get_state_with_grade_criteria_provider > * @param array $completioncriteria The completion criteria to use > * @param int|null $studentgrade Grade to assign to student > * @param int $expectedstate Expected completion state > * @covers ::internal_get_state > */ > public function test_internal_get_state_with_grade_criteria(array $completioncriteria, ?int $studentgrade, int $expectedstate) { > $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, > ] + $completioncriteria); > > $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); > if ($studentgrade) { > $data = (object)[ > 'sendstudentnotifications' => false, > 'attemptnumber' => 1, > 'grade' => $studentgrade, > ]; > $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($expectedstate, $completioninfo->internal_get_state($cm, $userid, null));
}
> /** public function test_set_module_viewed() { > * Covers the case where internal_get_state() is being called for a user different from the logged in user. $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_different_user() { $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); > $this->setup_data(); $c = $mockbuilder->getMock(); > $cm = (object)array('id'=>13, 'course'=>42); > /** @var \mod_assign_generator $assigngenerator */ > $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); // Not tracking completion, should do nothing. > $assign = $assigngenerator->create_instance([ $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED; > 'course' => $this->course->id, $c->set_module_viewed($cm); > 'completion' => COMPLETION_ENABLED, > 'completionusegrade' => 1, // Tracking completion but completion is disabled, should do nothing. > ]); $cm->completionview = COMPLETION_VIEW_REQUIRED; > $c->expects($this->at(0)) > $userid = $this->user->id; ->method('is_enabled') > ->with($cm) > $cm = get_coursemodule_from_instance('assign', $assign->id); ->will($this->returnValue(false)); > $usercm = cm_info::create($cm, $userid); $c->set_module_viewed($cm); > > // Create a teacher account. // Now it's enabled, we expect it to get data. If data already has > $teacher = $this->getDataGenerator()->create_user(); // viewed, still do nothing. > $this->getDataGenerator()->enrol_user($teacher->id, $this->course->id, 'editingteacher'); $c->expects($this->at(0)) > // Log in as the teacher. ->method('is_enabled') > $this->setUser($teacher); ->with($cm) > ->will($this->returnValue(true)); > // Grade the student for this assignment. $c->expects($this->at(1)) > $assign = new assign($usercm->context, $cm, $cm->course); ->method('get_data') > $data = (object)[ ->with($cm, 0) > 'sendstudentnotifications' => false, ->will($this->returnValue((object)array('viewed'=>COMPLETION_VIEWED))); > 'attemptnumber' => 1, $c->set_module_viewed($cm); > 'grade' => 90, > ]; // OK finally one that hasn't been viewed, now it should set it viewed > $assign->save_grade($userid, $data); // and update state. > $c->expects($this->at(0)) > // The target user already received a grade, so internal_get_state should be already complete. ->method('is_enabled') > $completioninfo = new completion_info($this->course); ->with($cm) > $this->assertEquals(COMPLETION_COMPLETE, $completioninfo->internal_get_state($cm, $userid, null)); ->will($this->returnValue(true)); > $c->expects($this->at(1)) > // As the teacher which does not have a grade in this cm, internal_get_state should return incomplete. ->method('get_data') > $this->assertEquals(COMPLETION_INCOMPLETE, $completioninfo->internal_get_state($cm, $teacher->id, null)); ->with($cm, false, 1337) > } ->will($this->returnValue((object)array('viewed'=>COMPLETION_NOT_VIEWED))); > $c->expects($this->at(2)) > /** ->method('internal_set_data') > * Test for internal_get_state() for an activity that supports custom completion. ->with($cm, (object)array('viewed'=>COMPLETION_VIEWED)); > * $c->expects($this->at(3)) > * @covers ::internal_get_state ->method('update_state') > */ ->with($cm, COMPLETION_COMPLETE, 1337); > public function test_internal_get_state_with_custom_completion() { $c->set_module_viewed($cm, 1337); > $this->setup_data(); } > > $choicerecord = [ public function test_count_user_data() { > 'course' => $this->course, global $DB; > 'completion' => COMPLETION_TRACKING_AUTOMATIC, $this->mock_setup(); > 'completionsubmit' => COMPLETION_ENABLED, > ]; $course = (object)array('id'=>13); > $choice = $this->getDataGenerator()->create_module('choice', $choicerecord); $cm = (object)array('id'=>42); > $cminfo = cm_info::create(get_coursemodule_from_instance('choice', $choice->id)); > /** @var $DB PHPUnit_Framework_MockObject_MockObject */ > $completioninfo = new completion_info($this->course); $DB->expects($this->at(0)) > ->method('get_field_sql') > // Fetch completion for the user who hasn't made a choice yet. ->will($this->returnValue(666)); > $completion = $completioninfo->internal_get_state($cminfo, $this->user->id, COMPLETION_INCOMPLETE); > $this->assertEquals(COMPLETION_INCOMPLETE, $completion); $c = new completion_info($course); > $this->assertEquals(666, $c->count_user_data($cm)); > // Have the user make a choice. } > $choicewithoptions = choice_get_choice($choice->id); > $optionids = array_keys($choicewithoptions->option); public function test_delete_all_state() { > choice_user_submit_response($optionids[0], $choice, $this->user->id, $this->course, $cminfo); global $DB; > $completion = $completioninfo->internal_get_state($cminfo, $this->user->id, COMPLETION_INCOMPLETE); $this->mock_setup(); > $this->assertEquals(COMPLETION_COMPLETE, $completion); > } $course = (object)array('id'=>13); > $cm = (object)array('id'=>42, 'course'=>13); > /** $c = new completion_info($course); > * @covers ::set_module_viewed > */
< $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();
$DB->expects($this->at(0))
> $c = $mockbuilder->getMock();
->method('delete_records')
> $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())
< $c->expects($this->at(2))
> $c->expects($this->once())
< $c->expects($this->at(3))
> $c->expects($this->once())
$this->mock_setup();
> /** > * @covers ::count_user_data $mockbuilder = $this->getMockBuilder('completion_info'); > */
< $DB->expects($this->at(0))
> $DB->expects($this->once())
$mockbuilder->setConstructorArgs(array((object)array('id' => 42)));
> /** $c = $mockbuilder->getMock(); > * @covers ::delete_all_state > */
< $DB->expects($this->at(0))
> $DB->expects($this->once())
> /** /** @var $DB PHPUnit_Framework_MockObject_MockObject */ > * @covers ::reset_all_state $DB->expects($this->at(0)) > */
< $mockbuilder->setMethods(array('delete_all_state', 'get_tracked_users', 'update_state'));
> $mockbuilder->onlyMethods(array('delete_all_state', 'get_tracked_users', 'update_state'));
< $DB->expects($this->at(0))
> $DB->expects($this->once())
< 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))
> $c->expects($this->exactly(3))
->method('update_state')
< ->with($cm, COMPLETION_UNKNOWN, 101); < $c->expects($this->at(4)) < ->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, ];
> $cmcompletionviewrecord = (object)[ $DB->insert_record('course_modules_completion', $cmcompletionrecord); > 'coursemoduleid' => $cm->id, } > 'userid' => $user->id, > 'timecreated' => 0, // Whether we expect for the returned completion data to be stored in the cache. > ];
$iscached = true;
> $DB->insert_record('course_modules_viewed', $cmcompletionviewrecord);
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')); > } > } > > /** > * @covers ::get_data > */ > public function test_get_data_successive_calls(): void { > global $DB; > > $this->setup_data(); > $this->setUser($this->user); > > $choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); > $choice = $choicegenerator->create_instance([ > 'course' => $this->course->id, > '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 through the hoops to complete an activity. > $cmcompletionrecord = (object) [ > 'coursemoduleid' => $cm->id, > 'userid' => $this->user->id, > 'completionstate' => COMPLETION_NOT_VIEWED, > 'overrideby' => null, > 'timemodified' => 0, > ]; > $cmcompletionviewrecord = (object)[ > 'coursemoduleid' => $cm->id, > 'userid' => $this->user->id, > 'timecreated' => 0, > ]; > $DB->insert_record('course_modules_completion', $cmcompletionrecord); > $DB->insert_record('course_modules_viewed', $cmcompletionviewrecord); > > // Mock other completion data. > $completioninfo = new completion_info($this->course); > > $modinfo = get_fast_modinfo($this->course); > $results = []; > foreach ($modinfo->cms as $testcm) { > $result = $completioninfo->get_data($testcm, true); > $this->assertTrue(property_exists($result, 'id')); > $this->assertTrue(property_exists($result, 'coursemoduleid')); > $this->assertTrue(property_exists($result, 'userid')); > $this->assertTrue(property_exists($result, 'completionstate')); > $this->assertTrue(property_exists($result, 'viewed')); > $this->assertTrue(property_exists($result, 'overrideby')); > $this->assertTrue(property_exists($result, 'timemodified')); > $this->assertFalse(property_exists($result, 'other_cm_completion_data_fetched')); > > $this->assertEquals($testcm->id, $result->coursemoduleid); > $this->assertEquals($this->user->id, $result->userid); > > $results[$testcm->id] = $result; > } > > $result = $completioninfo->get_data($cm); > $this->assertTrue(property_exists($result, 'customcompletion')); > > // The data should match when fetching modules individually. > (cache::make('core', 'completion'))->purge(); > foreach ($modinfo->cms as $testcm) { > $result = $completioninfo->get_data($testcm, false); > $this->assertEquals($result, $results[$testcm->id]);
}
< } else { < // Otherwise, this should not be cached. < $this->assertFalse($cache->get($key));
}
> } > /** > * Tests for get_completion_data(). public function test_internal_set_data() { > * global $DB; > * @covers ::get_completion_data $this->setup_data(); > */ > public function test_get_completion_data() { $this->setUser($this->user); > $this->setup_data(); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); > $choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); > $choice = $choicegenerator->create_instance([ $cm = get_coursemodule_from_instance('forum', $forum->id); > 'course' => $this->course->id, $c = new completion_info($this->course); > 'completion' => COMPLETION_TRACKING_AUTOMATIC, > 'completionview' => true, // 1) Test with new data. > 'completionsubmit' => true, $data = new stdClass(); > ]); $data->id = 0; > $cm = get_coursemodule_from_instance('choice', $choice->id); $data->userid = $this->user->id; > $data->coursemoduleid = $cm->id; > // Mock other completion data. $data->completionstate = COMPLETION_COMPLETE; > $completioninfo = new completion_info($this->course); $data->timemodified = time(); > // Default data to return when no completion data is found. $data->viewed = COMPLETION_NOT_VIEWED; > $defaultdata = [ $data->overrideby = null; > 'id' => 0, > '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' => 0, $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)); > > $completiondatabeforeview = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata); // 2) Test with existing data and for different user. > $this->assertTrue(array_key_exists('viewed', $completiondatabeforeview)); $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); > $this->assertTrue(array_key_exists('coursemoduleid', $completiondatabeforeview)); $cm2 = get_coursemodule_from_instance('forum', $forum2->id); > $this->assertEquals(0, $completiondatabeforeview['viewed']); $newuser = $this->getDataGenerator()->create_user(); > $this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']); > $d2 = new stdClass(); > // Set viewed. $d2->id = 7; > $completioninfo->set_module_viewed($cm, $this->user->id); $d2->userid = $newuser->id; > $d2->coursemoduleid = $cm2->id; > $completiondata = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata); $d2->completionstate = COMPLETION_COMPLETE; > $this->assertTrue(array_key_exists('viewed', $completiondata)); $d2->timemodified = time(); > $this->assertTrue(array_key_exists('coursemoduleid', $completiondata)); $d2->viewed = COMPLETION_NOT_VIEWED; > $this->assertEquals(1, $completiondata['viewed']); $d2->overrideby = null; > $this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']); $c->internal_set_data($cm2, $d2); > // Cache for current user returns the data. > $completioninfo->reset_all_state($cm); $cachevalue = $cache->get($data->userid . '_' . $cm->course); > $this->assertEquals($data, $cachevalue[$cm->id]); > $completiondataafterreset = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata); // Cache for another user is not filled. > $this->assertTrue(array_key_exists('viewed', $completiondataafterreset)); $this->assertEquals(false, $cache->get($d2->userid . '_' . $cm2->course)); > $this->assertTrue(array_key_exists('coursemoduleid', $completiondataafterreset)); > $this->assertEquals(1, $completiondataafterreset['viewed']); // 3) Test where it THINKS the data is new (from cache) but actually > $this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']); // 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); > /** $cm3 = get_coursemodule_from_instance('forum', $forum3->id); > * Tests for completion_info::get_other_cm_completion_data(). $newuser2 = $this->getDataGenerator()->create_user(); > * $d3 = new stdClass(); > * @covers ::get_other_cm_completion_data $d3->id = 13; > */ $d3->userid = $newuser2->id; > public function test_get_other_cm_completion_data() { $d3->coursemoduleid = $cm3->id; > global $DB; $d3->completionstate = COMPLETION_COMPLETE; > $d3->timemodified = time(); > $this->setup_data(); $d3->viewed = COMPLETION_NOT_VIEWED; > $user = $this->user; $d3->overrideby = null; > $DB->insert_record('course_modules_completion', $d3); > $this->setAdminUser(); $c->internal_set_data($cm, $data); > } > $choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); > $choice = $choicegenerator->create_instance([ public function test_get_progress_all() { > 'course' => $this->course->id, global $DB; > 'completion' => COMPLETION_TRACKING_AUTOMATIC, $this->mock_setup(); > 'completionsubmit' => true, > ]); $mockbuilder = $this->getMockBuilder('completion_info'); > $mockbuilder->setMethods(array('get_tracked_users')); > $cmchoice = cm_info::create(get_coursemodule_from_instance('choice', $choice->id)); $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); > $c = $mockbuilder->getMock(); > $choice2 = $choicegenerator->create_instance([ > 'course' => $this->course->id, // 1) Basic usage. > 'completion' => COMPLETION_TRACKING_AUTOMATIC, $c->expects($this->at(0)) > ]); ->method('get_tracked_users') > ->with(false, array(), 0, '', '', '', null) > $cmchoice2 = cm_info::create(get_coursemodule_from_instance('choice', $choice2->id)); ->will($this->returnValue(array( > (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh'), > $workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop'); (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy')))); > $workshop = $workshopgenerator->create_instance([ $DB->expects($this->at(0)) > 'course' => $this->course->id, ->method('get_in_or_equal') > 'completion' => COMPLETION_TRACKING_AUTOMATIC, ->with(array(100, 201)) > // Submission grade required. ->will($this->returnValue(array(' IN (100, 201)', array()))); > 'completiongradeitemnumber' => 0, $progress1 = (object)array('userid'=>100, 'coursemoduleid'=>13); > 'completionpassgrade' => 1, $progress2 = (object)array('userid'=>201, 'coursemoduleid'=>14); > ]); $DB->expects($this->at(1)) > ->method('get_recordset_sql') > $cmworkshop = cm_info::create(get_coursemodule_from_instance('workshop', $workshop->id)); ->will($this->returnValue(new core_completionlib_fake_recordset(array($progress1, $progress2)))); > > $completioninfo = new completion_info($this->course); $this->assertEquals(array( > 100 => (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh', > $method = new ReflectionMethod("completion_info", "get_other_cm_completion_data"); 'progress'=>array(13=>$progress1)), > $method->setAccessible(true); 201 => (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy', > 'progress'=>array(14=>$progress2)), > // Check that fetching data for a module with custom completion provides its info. ), $c->get_progress_all(false)); > $choicecompletiondata = $method->invoke($completioninfo, $cmchoice, $user->id); > // 2) With more than 1, 000 results. > $this->assertArrayHasKey('customcompletion', $choicecompletiondata); $tracked = array(); > $this->assertArrayHasKey('completionsubmit', $choicecompletiondata['customcompletion']); $ids = array(); > $this->assertEquals(COMPLETION_INCOMPLETE, $choicecompletiondata['customcompletion']['completionsubmit']); $progress = array(); > for ($i = 100; $i<2000; $i++) { > // Mock a choice answer so user has completed the requirement. $tracked[] = (object)array('id'=>$i, 'firstname'=>'frog', 'lastname'=>$i); > $choicemockinfo = [ $ids[] = $i; > 'choiceid' => $cmchoice->instance, $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>13); > 'userid' => $this->user->id $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>14); > ]; } > $DB->insert_record('choice_answers', $choicemockinfo, false); $c->expects($this->at(0)) > ->method('get_tracked_users') > // Confirm fetching again reflects the completion. ->with(true, 3, 0, '', '', '', null) > $choicecompletiondata = $method->invoke($completioninfo, $cmchoice, $user->id); ->will($this->returnValue($tracked)); > $this->assertEquals(COMPLETION_COMPLETE, $choicecompletiondata['customcompletion']['completionsubmit']); $DB->expects($this->at(0)) > ->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, 0, 1000)) > $workshopcompletiondata = $method->invoke($completioninfo, $cmworkshop, $user->id); ->will($this->returnValue(array(' IN whatever', array()))); > $DB->expects($this->at(1)) > $this->assertArrayHasKey('completiongrade', $workshopcompletiondata); ->method('get_recordset_sql') > $this->assertArrayHasKey('passgrade', $workshopcompletiondata); ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 0, 1000)))); > $this->assertArrayNotHasKey('customcompletion', $workshopcompletiondata); > $this->assertEquals(COMPLETION_INCOMPLETE, $workshopcompletiondata['completiongrade']); $DB->expects($this->at(2)) > $this->assertEquals(COMPLETION_INCOMPLETE, $workshopcompletiondata['passgrade']); ->method('get_in_or_equal') > ->with(array_slice($ids, 1000)) > // Check that fetching data for a module with no completion conditions does not provide any data. ->will($this->returnValue(array(' IN whatever2', array()))); > $choice2completiondata = $method->invoke($completioninfo, $cmchoice2, $user->id); $DB->expects($this->at(3)) > $this->assertEmpty($choice2completiondata);
->method('get_recordset_sql')
> /** ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 1000)))); > * @covers ::internal_set_data > */
< $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.
$resultok = $resultok && $data->lastname == $userid;
> $resultok = $resultok && $data->id == $userid; > // 4) Test instant course completions. $cms = $data->progress; > $dataactivity = $this->getDataGenerator()->create_module('data', array('course' => $this->course->id), $resultok = $resultok && (array(13, 14) == array_keys($cms)); > array('completion' => 1)); $resultok = $resultok && ((object)array('userid'=>$userid, 'coursemoduleid'=>13) == $cms[13]); > $cm = get_coursemodule_from_instance('data', $dataactivity->id); $resultok = $resultok && ((object)array('userid'=>$userid, 'coursemoduleid'=>14) == $cms[14]); > $c = new completion_info($this->course); } > $cmdata = get_coursemodule_from_id('data', $dataactivity->cmid); $this->assertTrue($resultok); > } > // Add activity completion criteria. > $criteriadata = new stdClass(); public function test_inform_grade_changed() { > $criteriadata->id = $this->course->id; $this->mock_setup(); > $criteriadata->criteria_activity = array(); > // Some activities. $mockbuilder = $this->getMockBuilder('completion_info'); > $criteriadata->criteria_activity[$cmdata->id] = 1; $mockbuilder->setMethods(array('is_enabled', 'update_state')); > $class = 'completion_criteria_activity'; $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); > $criterion = new $class(); $c = $mockbuilder->getMock(); > $criterion->update_config($criteriadata); > $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>null); > $actual = $DB->get_records('course_completions'); $item = (object)array('itemnumber'=>3, 'gradepass'=>1, 'hidden'=>0); > $this->assertEmpty($actual); $grade = (object)array('userid'=>31337, 'finalgrade'=>0, 'rawgrade'=>0); > > $data->coursemoduleid = $cm->id; // Not enabled (should do nothing). > $c->internal_set_data($cm, $data); $c->expects($this->at(0)) > $actual = $DB->get_records('course_completions'); ->method('is_enabled') > $this->assertEquals(1, count($actual)); ->with($cm) > $this->assertEquals($this->user->id, reset($actual)->userid); ->will($this->returnValue(false)); > $c->inform_grade_changed($cm, $item, $grade, false); > $data->userid = $newuser2->id; > $c->internal_set_data($cm, $data, true); // Enabled but still no grade completion required, should still do nothing. > $actual = $DB->get_records('course_completions'); $c->expects($this->at(0)) > $this->assertEquals(1, count($actual)); ->method('is_enabled') > $this->assertEquals($this->user->id, reset($actual)->userid);
< 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())
$c->expects($this->at(0))
> } ->method('is_enabled') > ->with($cm) > /** ->will($this->returnValue(true)); > * @covers ::get_progress_all $c->inform_grade_changed($cm, $item, $grade, false); > */ > public function test_get_progress_all_lots() { // Enabled and completion required and item number right. It is supposed > global $DB; // to call update_state with the new potential state being obtained from > $this->mock_setup(); // internal_get_grade_state. > $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>3); > $mockbuilder = $this->getMockBuilder('completion_info'); $grade = (object)array('userid'=>31337, 'finalgrade'=>1, 'rawgrade'=>0); > $mockbuilder->onlyMethods(array('get_tracked_users')); $c->expects($this->at(0)) > $mockbuilder->setConstructorArgs(array((object)array('id' => 42))); ->method('is_enabled') > $c = $mockbuilder->getMock();
< // 2) With more than 1, 000 results.
->will($this->returnValue(true));
> // 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)));
$c->expects($this->at(1))
> $this->assertCount(count($tracked), $result);
->method('update_state')
> /** ->with($cm, COMPLETION_INCOMPLETE, 31337) > * @covers ::inform_grade_changed ->will($this->returnValue(true)); > */
< $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())
$this->mock_setup();
> $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c->expects($this->once())
$item = new stdClass;
> $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c->expects($this->once())
< $c->expects($this->at(1))
> $c->expects($this->once())
$item->gradepass = 4;
> $c = $mockbuilder->getMock();
< $c->expects($this->at(0))
> $c->expects($this->once())
< $c->expects($this->at(1))
> $c->expects($this->once())
$grade->finalgrade = null;
> /** > * @covers ::internal_get_grade_state // 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));
> } > // Item is hidden, but returnpassfail is true and the grade is passing. > $item->hidden = 1; public function test_get_activities() { > $item->gradepass = 4; global $CFG; > $grade->finalgrade = 5.0; $this->resetAfterTest(); > $this->assertEquals( > COMPLETION_COMPLETE_PASS, // Enable completion before creating modules, otherwise the completion data is not written in DB. > completion_info::internal_get_grade_state($item, $grade, true)); $CFG->enablecompletion = true; > > // Item is hidden, but returnpassfail is true and the grade is failing. // Create a course with mixed auto completion data. > $item->hidden = 1; $course = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); > $item->gradepass = 4; $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); > $grade->finalgrade = 3.0; $completionmanual = array('completion' => COMPLETION_TRACKING_MANUAL); > $this->assertEquals( $completionnone = array('completion' => COMPLETION_TRACKING_NONE); > COMPLETION_COMPLETE_FAIL_HIDDEN, $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionauto); > completion_info::internal_get_grade_state($item, $grade, true)); $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionauto); > $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionmanual); > // Item is not hidden, but returnpassfail is true and the grade is failing. > $item->hidden = 0; $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionnone); > $item->gradepass = 4; $page2 = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionnone); > $grade->finalgrade = 3.0; $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionnone); > $this->assertEquals( > COMPLETION_COMPLETE_FAIL, // Create data in another course to make sure it's not considered. > completion_info::internal_get_grade_state($item, $grade, true));
$course2 = $this->getDataGenerator()->create_course(array('enablecompletion' => true));
> /** $c2forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id), $completionauto); > * @test ::get_activities $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.
> * */ > * @covers \core\event\course_completed
public function test_course_completed_message() { $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 the message. $sink = $this->redirectMessages(); $ccompletion->mark_complete(); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $this->assertEquals(core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($this->user->id, $message->useridto); $this->assertEquals('coursecompleted', $message->eventtype); $this->assertEquals(get_string('coursecompleted', 'completion'), $message->subject); $this->assertStringContainsString($this->course->fullname, $message->fullmessage); } /** * Test course completed event.
> * */ > * @covers \core\event\course_completion_updated
public function test_course_completion_updated_event() { $this->setup_data(); $coursecontext = context_course::instance($this->course->id); $coursecompletionevent = \core\event\course_completion_updated::create( array( 'courseid' => $this->course->id, 'context' => $coursecontext ) ); // Mark course as complete and get triggered event. $sink = $this->redirectEvents(); $coursecompletionevent->trigger(); $events = $sink->get_events(); $event = array_pop($events); $sink->close(); $this->assertInstanceOf('\core\event\course_completion_updated', $event); $this->assertEquals($this->course->id, $event->courseid); $this->assertEquals($coursecontext, $event->get_context()); $this->assertInstanceOf('moodle_url', $event->get_url()); $expectedlegacylog = array($this->course->id, 'course', 'completion updated', 'completion.php?id='.$this->course->id); $this->assertEventLegacyLogData($expectedlegacylog, $event); }
> /** public function test_completion_can_view_data() { > * @covers \completion_can_view_data $this->setup_data(); > */
$student = $this->getDataGenerator()->create_user(); $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)); > } > > /** > * Test for aggregate_completions(). > * > * @covers \aggregate_completions > */ > public function test_aggregate_completions() { > global $DB; > $this->resetAfterTest(true); > $time = time(); > > $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); > > for ($i = 0; $i < 4; $i++) { > $students[] = $this->getDataGenerator()->create_user(); > } > > $teacher = $this->getDataGenerator()->create_user(); > $studentrole = $DB->get_record('role', array('shortname' => 'student')); > $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); > > $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); > foreach ($students as $student) { > $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); > } > > $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id), > array('completion' => 1)); > $cmdata = get_coursemodule_from_id('data', $data->cmid); > > // Add activity completion criteria. > $criteriadata = new stdClass(); > $criteriadata->id = $course->id; > $criteriadata->criteria_activity = array(); > // Some activities. > $criteriadata->criteria_activity[$cmdata->id] = 1; > $class = 'completion_criteria_activity'; > $criterion = new $class(); > $criterion->update_config($criteriadata); > > $this->setUser($teacher); > > // Mark activity complete for both students. > $cm = get_coursemodule_from_instance('data', $data->id); > $completioncriteria = $DB->get_record('course_completion_criteria', []); > foreach ($students as $student) { > $cmcompletionrecords[] = (object)[ > 'coursemoduleid' => $cm->id, > 'userid' => $student->id, > 'completionstate' => 1, > 'viewed' => 0, > 'overrideby' => null, > 'timemodified' => 0, > ]; > > $usercompletions[] = (object)[ > 'criteriaid' => $completioncriteria->id, > 'userid' => $student->id, > 'timecompleted' => $time, > ]; > > $cc = array( > 'course' => $course->id, > 'userid' => $student->id > ); > $ccompletion = new completion_completion($cc); > $completion[] = $ccompletion->mark_inprogress($time); > } > $DB->insert_records('course_modules_completion', $cmcompletionrecords); > $DB->insert_records('course_completion_crit_compl', $usercompletions); > > // MDL-33320: for instant completions we need aggregate to work in a single run. > $DB->set_field('course_completions', 'reaggregate', $time - 2); > > foreach ($students as $student) { > $result = $DB->get_record('course_completions', ['userid' => $student->id, 'reaggregate' => 0]); > $this->assertFalse($result); > } > > aggregate_completions($completion[0]); > > $result1 = $DB->get_record('course_completions', ['userid' => $students[0]->id, 'reaggregate' => 0]); > $result2 = $DB->get_record('course_completions', ['userid' => $students[1]->id, 'reaggregate' => 0]); > $result3 = $DB->get_record('course_completions', ['userid' => $students[2]->id, 'reaggregate' => 0]); > > $this->assertIsObject($result1); > $this->assertFalse($result2); > $this->assertFalse($result3); > > aggregate_completions(0); > > foreach ($students as $student) { > $result = $DB->get_record('course_completions', ['userid' => $student->id, 'reaggregate' => 0]); > $this->assertIsObject($result); > } > } > > /** > * Test for completion_completion::_save(). > * > * @covers \completion_completion::_save > */ > public function test_save() { > global $DB; > $this->resetAfterTest(true); > > $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); > > $student = $this->getDataGenerator()->create_user(); > $teacher = $this->getDataGenerator()->create_user(); > $studentrole = $DB->get_record('role', array('shortname' => 'student')); > $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); > > $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); > $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); > > $this->setUser($teacher); > > $cc = array( > 'course' => $course->id, > 'userid' => $student->id > ); > $ccompletion = new completion_completion($cc); > > $completions = $DB->get_records('course_completions'); > $this->assertEmpty($completions); > > // We're testing a private method, so we need to setup reflector magic. > $method = new ReflectionMethod($ccompletion, '_save'); > $method->setAccessible(true); // Allow accessing of private method. > $completionid = $method->invoke($ccompletion); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(count($completions), 1); > $this->assertEquals(reset($completions)->id, $completionid); > > $ccompletion->id = 0; > $method = new ReflectionMethod($ccompletion, '_save'); > $method->setAccessible(true); // Allow accessing of private method. > $completionid = $method->invoke($ccompletion); > $this->assertDebuggingCalled('Can not update data object, no id!'); > $this->assertNull($completionid); > } > > /** > * Test for completion_completion::mark_enrolled(). > * > * @covers \completion_completion::mark_enrolled > */ > public function test_mark_enrolled() { > global $DB; > $this->resetAfterTest(true); > > $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); > > $student = $this->getDataGenerator()->create_user(); > $teacher = $this->getDataGenerator()->create_user(); > $studentrole = $DB->get_record('role', array('shortname' => 'student')); > $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); > > $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); > $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); > > $this->setUser($teacher); > > $cc = array( > 'course' => $course->id, > 'userid' => $student->id > ); > $ccompletion = new completion_completion($cc); > > $completions = $DB->get_records('course_completions'); > $this->assertEmpty($completions); > > $completionid = $ccompletion->mark_enrolled(); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(count($completions), 1); > $this->assertEquals(reset($completions)->id, $completionid); > > $ccompletion->id = 0; > $completionid = $ccompletion->mark_enrolled(); > $this->assertDebuggingCalled('Can not update data object, no id!'); > $this->assertNull($completionid); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(1, count($completions)); > } > > /** > * Test for completion_completion::mark_inprogress(). > * > * @covers \completion_completion::mark_inprogress > */ > public function test_mark_inprogress() { > global $DB; > $this->resetAfterTest(true); > > $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); > > $student = $this->getDataGenerator()->create_user(); > $teacher = $this->getDataGenerator()->create_user(); > $studentrole = $DB->get_record('role', array('shortname' => 'student')); > $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); > > $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); > $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); > > $this->setUser($teacher); > > $cc = array( > 'course' => $course->id, > 'userid' => $student->id > ); > $ccompletion = new completion_completion($cc); > > $completions = $DB->get_records('course_completions'); > $this->assertEmpty($completions); > > $completionid = $ccompletion->mark_inprogress(); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(1, count($completions)); > $this->assertEquals(reset($completions)->id, $completionid); > > $ccompletion->id = 0; > $completionid = $ccompletion->mark_inprogress(); > $this->assertDebuggingCalled('Can not update data object, no id!'); > $this->assertNull($completionid); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(1, count($completions)); > } > > /** > * Test for completion_completion::mark_complete(). > * > * @covers \completion_completion::mark_complete > */ > public function test_mark_complete() { > global $DB; > $this->resetAfterTest(true); > > $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); > > $student = $this->getDataGenerator()->create_user(); > $teacher = $this->getDataGenerator()->create_user(); > $studentrole = $DB->get_record('role', array('shortname' => 'student')); > $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); > > $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); > $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); > > $this->setUser($teacher); > > $cc = array( > 'course' => $course->id, > 'userid' => $student->id > ); > $ccompletion = new completion_completion($cc); > > $completions = $DB->get_records('course_completions'); > $this->assertEmpty($completions); > > $completionid = $ccompletion->mark_complete(); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(1, count($completions)); > $this->assertEquals(reset($completions)->id, $completionid); > > $ccompletion->id = 0; > $completionid = $ccompletion->mark_complete(); > $this->assertNull($completionid); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(1, count($completions)); > } > > /** > * Test for completion_criteria_completion::mark_complete(). > * > * @covers \completion_criteria_completion::mark_complete > */ > public function test_criteria_mark_complete() { > global $DB; > $this->resetAfterTest(true); > > $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); > > $student = $this->getDataGenerator()->create_user(); > $teacher = $this->getDataGenerator()->create_user(); > $studentrole = $DB->get_record('role', array('shortname' => 'student')); > $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); > > $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); > $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); > > $this->setUser($teacher); > > $record = [ > 'course' => $course->id, > 'criteriaid' => 1, > 'userid' => $student->id, > 'timecompleted' => time() > ]; > $completion = new completion_criteria_completion($record, DATA_OBJECT_FETCH_BY_KEY); > > $completions = $DB->get_records('course_completions'); > $this->assertEmpty($completions); > > $completionid = $completion->mark_complete($record['timecompleted']); > $completions = $DB->get_records('course_completions'); > $this->assertEquals(1, count($completions)); > $this->assertEquals(reset($completions)->id, $completionid); > } > > /** > * Test that data is cleaned when we reset a course completion data > * > * @covers ::delete_all_completion_data > */ > public function test_course_reset_completion() { > global $DB; > > $this->setup_data(); > > $page = $this->getDataGenerator()->create_module('page', [ > 'course' => $this->course->id, > 'completion' => COMPLETION_ENABLED, > 'completionview' => COMPLETION_VIEW_REQUIRED, > ]); > $cm = cm_info::create(get_coursemodule_from_instance('page', $page->id)); > $completion = new completion_info($this->course); > $completion->set_module_viewed($cm, $this->user->id); > // Sanity test. > $this->assertTrue($DB->record_exists_select('course_modules_completion', > 'coursemoduleid IN (SELECT id FROM {course_modules} WHERE course=:course)', > ['course' => $this->course->id] > )); > $this->assertTrue($DB->record_exists_select('course_modules_viewed', > 'coursemoduleid IN (SELECT id FROM {course_modules} WHERE course=:course)', > ['course' => $this->course->id] > )); > // Deleting the prerequisite course should remove the completion criteria. > $resetdata = new \stdClass(); > $resetdata->id = $this->course->id; > $resetdata->reset_completion = true; > reset_course_userdata($resetdata); > > $this->assertFalse($DB->record_exists_select('course_modules_completion', > 'coursemoduleid IN (SELECT id FROM {course_modules} WHERE course=:course)', > ['course' => $this->course->id] > )); > $this->assertFalse($DB->record_exists_select('course_modules_viewed', > 'coursemoduleid IN (SELECT id FROM {course_modules} WHERE course=:course)', > ['course' => $this->course->id] > )); > }
> #[\ReturnTypeWillChange]
> #[\ReturnTypeWillChange]
< public function next() {
> public function next(): void {
< public function rewind() {
> public function rewind(): void {
< public function valid() {
> public function valid(): bool {