AutorÃa | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_quiz\task;
use mod_quiz\quiz_attempt;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php');
/**
* Unit tests for precreate_attempts
*
* @package mod_quiz
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \mod_quiz\task\precreate_attempts
*/
final class precreate_attempts_test extends \advanced_testcase {
use \quiz_question_helper_test_trait;
/**
* Generate the various possible combinations precreation settings and the corresponding task output.
*
* @return array
*/
public static function precreate_settings_provider(): array {
return [
[
'period' => 0,
'output' => 'Pre-creation of quiz attempts is disabled. Nothing to do.',
],
[
'period' => 1,
'output' => 'Found 0 quizzes to create attempts for.',
],
];
}
/**
* The scheduled task only looks for quizzes to generate if pre-creation is configured.
*
* Only look for quizzes to generate attempts for if precreateperiod is not 0, and precreateattempts is 1 or is unlocked.
*
* @param int $period
* @param string $output
* @dataProvider precreate_settings_provider
*/
public function test_execute_disabled(int $period, string $output): void {
$this->resetAfterTest();
set_config('precreateperiod', $period, 'quiz');
$task = new precreate_attempts();
ob_start();
$task->execute();
$log = ob_get_clean();
$this->assertMatchesRegularExpression("/{$output}/", $log);
}
/**
* Test precreate attempts task.
*
* Generate quizzes with a variety of timeopen and precreateperiod settings, and ensure those that match the criteria
* for attempt pre-generation are picked up by the scheduled task.
*/
public function test_execute(): void {
$this->resetAfterTest();
// Generate a course.
$course = $this->getDataGenerator()->create_course();
// Generate 3 users.
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
// Enrol users on the course with the appropriate roles.
$this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
set_config('precreateperiod', 12 * HOURSECS, 'quiz');
set_config('precreateattempts', 1, 'quiz');
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
// Generate a quiz with timeopen 1 day in the future.
$quizinfuture = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() + 86400,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Generate a quiz with timeopen 0.
$quiznotimeopen = $quizgenerator->create_instance([
'course' => $course->id,
'precreateperiod' => 43200,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Generate a quiz with timeopen in the past.
$quizinpast = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() - 86400,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Generate a quiz with timeopen 11 hours in the future.
$quizwithattempts = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Generate second quiz with timeopen 11 hours in the future.
$quizinprecreateperiod = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Generate second quiz with timeopen 11 hours in the future,
// but do not give it any questions.
$quizwithoutquestions = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Add questions to the quizzes.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$this->add_two_regular_questions($questiongenerator, $quizinfuture);
$this->add_two_regular_questions($questiongenerator, $quiznotimeopen);
$this->add_two_regular_questions($questiongenerator, $quizinpast);
$this->add_two_regular_questions($questiongenerator, $quizwithattempts);
$this->add_two_regular_questions($questiongenerator, $quizinprecreateperiod);
// Create attempts for one student on quiz 5.
$quiz5settings = quiz_settings::create($quizwithattempts->id);
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quiz5settings->get_context());
$quba->set_preferred_behaviour($quiz5settings->get_quiz()->preferredbehaviour);
$attempt = quiz_create_attempt($quiz5settings, 1, false, time(), false, $student1->id);
quiz_start_new_attempt($quiz5settings, $quba, $attempt, 1, time());
quiz_attempt_save_started($quiz5settings, $quba, $attempt);
$this->assertEmpty(
quiz_get_user_attempts(
[
$quizinfuture->id,
$quiznotimeopen->id,
$quizinpast->id,
$quizwithoutquestions->id,
],
$student1->id,
'all'
)
);
$this->assertEmpty(
quiz_get_user_attempts(
[
$quizinfuture->id,
$quiznotimeopen->id,
$quizinpast->id,
$quizwithattempts->id,
$quizwithoutquestions->id,
],
$student2->id,
'all',
),
);
$this->assertEmpty(
quiz_get_user_attempts(
[
$quizinfuture->id,
$quiznotimeopen->id,
$quizinpast->id,
$quizwithattempts->id,
$quizinprecreateperiod->id,
$quizwithoutquestions->id,
],
$teacher->id,
'all',
),
);
$student1existingattempts = quiz_get_user_attempts($quizwithattempts->id, $student1->id, 'all');
$this->assertCount(1, $student1existingattempts);
$this->assertEquals(reset($student1existingattempts)->state, quiz_attempt::IN_PROGRESS);
$student1precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student1->id, 'all');
$this->assertEmpty($student1precreatedattempts);
$student2precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student2->id, 'all');
$this->assertEmpty($student2precreatedattempts);
$task = new precreate_attempts();
ob_start();
$task->execute();
$log = ob_get_clean();
$this->assertMatchesRegularExpression('/Found 1 quizzes to create attempts for/', $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizinfuture->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiznotimeopen->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizinpast->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizwithattempts->name}/", $log);
$this->assertMatchesRegularExpression("/Creating attempts for {$quizinprecreateperiod->name}/", $log);
$this->assertMatchesRegularExpression("/Created 2 attempts for {$quizinprecreateperiod->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizwithoutquestions->name}/", $log);
$this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
// Students should have no attempts on quizzes that didn't meet criteria for pre-creation.
$this->assertEmpty(
quiz_get_user_attempts(
[
$quizinfuture->id,
$quiznotimeopen->id,
$quizinpast->id,
$quizwithoutquestions->id,
],
$student1->id,
'all'
)
);
$this->assertEmpty(
quiz_get_user_attempts(
[
$quizinfuture->id,
$quiznotimeopen->id,
$quizinpast->id,
$quizwithattempts->id,
$quizwithoutquestions->id,
],
$student2->id,
'all',
),
);
// Teacher should not have any attempts on any quizzes.
$this->assertEmpty(
quiz_get_user_attempts(
[
$quizinfuture->id,
$quiznotimeopen->id,
$quizinpast->id,
$quizwithattempts->id,
$quizinprecreateperiod->id,
$quizwithoutquestions->id,
],
$teacher->id,
'all',
),
);
// Students existing attempts should remain.
$student1existingattempts = quiz_get_user_attempts($quizwithattempts->id, $student1->id, 'all');
$this->assertCount(1, $student1existingattempts);
$this->assertEquals(reset($student1existingattempts)->state, quiz_attempt::IN_PROGRESS);
// They should have NOT_STARTED attempts on quizzes that meet the criteria for pre-creation.
$student1precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student1->id, 'all');
$this->assertCount(1, $student1precreatedattempts);
$this->assertEquals(reset($student1precreatedattempts)->state, quiz_attempt::NOT_STARTED);
$student2precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student2->id, 'all');
$this->assertCount(1, $student2precreatedattempts);
$this->assertEquals(reset($student1precreatedattempts)->state, quiz_attempt::NOT_STARTED);
}
/**
* Processing should stop at the end of a quiz once maxruntime has been reached.
*
* @return void
*/
public function test_execute_maxruntime(): void {
$this->resetAfterTest();
// Generate a course.
$course = $this->getDataGenerator()->create_course();
// Generate 3 users.
$student1 = $this->getDataGenerator()->create_user();
// Enrol users on the course with the appropriate roles.
$this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
set_config('precreateperiod', 12 * HOURSECS, 'quiz');
set_config('precreateattempts', 1, 'quiz');
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
// Generate 3 quizzes within the pre-creation window.
$timenow = time();
$quiz1 = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => $timenow + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// This quiz opens first, so should be processed first.
$quiz2 = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => $timenow + 39599,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
$quiz3 = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => $timenow + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Add questions to the quizzes.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$this->add_two_regular_questions($questiongenerator, $quiz1);
$this->add_two_regular_questions($questiongenerator, $quiz2);
$this->add_two_regular_questions($questiongenerator, $quiz3);
// Run the task with a maxruntime of 0, so that it should stop after processing the first quiz.
$task = new precreate_attempts(0);
ob_start();
$task->execute();
$log = ob_get_clean();
// Verify that the task stopped after the quiz opening soonest.
$this->assertMatchesRegularExpression('/Found 3 quizzes to create attempts for/', $log);
$this->assertMatchesRegularExpression("/Creating attempts for {$quiz2->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiz1->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiz3->name}/", $log);
$this->assertMatchesRegularExpression("/Created 1 attempts for {$quiz2->name}/", $log);
$this->assertMatchesRegularExpression('/Time limit reached./', $log);
$this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
// Run the task again with the default maxruntime.
ob_start();
$task = new precreate_attempts();
$task->execute();
$log = ob_get_clean();
// Verify that it picks up the remaining quiz for processing.
$this->assertMatchesRegularExpression('/Found 2 quizzes to create attempts for/', $log);
$this->assertMatchesRegularExpression("/Creating attempts for {$quiz1->name}/", $log);
$this->assertMatchesRegularExpression("/Creating attempts for {$quiz3->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiz2->name}/", $log);
$this->assertMatchesRegularExpression("/Created 1 attempts for {$quiz1->name}/", $log);
$this->assertMatchesRegularExpression("/Created 1 attempts for {$quiz3->name}/", $log);
$this->assertDoesNotMatchRegularExpression('/Time limit reached./', $log);
$this->assertMatchesRegularExpression('/Created attempts for 2 quizzes./', $log);
}
/**
* Pre-creation is opt in based on quiz setting.
*
* @return void
*/
public function test_execute_optin(): void {
$this->resetAfterTest();
// Generate a course.
$course = $this->getDataGenerator()->create_course();
// Generate 3 users.
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
// Enrol users on the course with the appropriate roles.
$this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
// Precreation is disabled by default.
set_config('precreateperiod', 12 * HOURSECS, 'quiz');
set_config('precreateattempts', 0, 'quiz');
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
// Generate a quiz with timeopen 11 hours in the future, and precreateattempts set to 1.
$quizprecreate = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
'precreateattempts' => 1,
]);
// Generate a quiz with timeopen 11 hours in the future, and precreateattempts set to 0.
$quiznoprecreate = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
'precreateattempts' => 0,
]);
// Generate a quiz with timeopen 11 hours in the future, and precreateattempts set to null.
$quizprecreatenull = $quizgenerator->create_instance([
'course' => $course->id,
'timeopen' => time() + 39600,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$this->add_two_regular_questions($questiongenerator, $quizprecreate);
$this->add_two_regular_questions($questiongenerator, $quiznoprecreate);
$this->add_two_regular_questions($questiongenerator, $quizprecreatenull);
// Run the task.
ob_start();
$task = new precreate_attempts();
$task->execute();
$log = ob_get_clean();
// Attempts were now created for the opted-in quiz.
$this->assertMatchesRegularExpression('/Found 1 quizzes to create attempts for/', $log);
$this->assertMatchesRegularExpression("/Creating attempts for {$quizprecreate->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiznoprecreate->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizprecreatenull->name}/", $log);
$this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
// Now enabled by default.
set_config('precreateattempts', 1, 'quiz');
// Run the task again.
ob_start();
$task = new precreate_attempts();
$task->execute();
$log = ob_get_clean();
// The quiz with null now has attempts generated.
$this->assertMatchesRegularExpression('/Found 1 quizzes to create attempts for/', $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizprecreate->name}/", $log);
$this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiznoprecreate->name}/", $log);
$this->assertMatchesRegularExpression("/Creating attempts for {$quizprecreatenull->name}/", $log);
$this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
}
}