From c5aac1bb0a30d459d2ef3c374cf43b294db981a8 Mon Sep 17 00:00:00 2001 From: Conn Warwicker Date: Thu, 2 Jan 2025 13:02:21 +0000 Subject: [PATCH] MDL-82772 mod_quiz: Update context when random question is updated. --- .../external/update_filter_condition.php | 26 ++- .../external/update_filter_condition_test.php | 173 ++++++++++++++++++ 2 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 mod/quiz/tests/external/update_filter_condition_test.php diff --git a/mod/quiz/classes/external/update_filter_condition.php b/mod/quiz/classes/external/update_filter_condition.php index 70d2c5c94bf..44df1873c06 100644 --- a/mod/quiz/classes/external/update_filter_condition.php +++ b/mod/quiz/classes/external/update_filter_condition.php @@ -26,6 +26,7 @@ use external_function_parameters; use external_single_structure; use external_value; use external_api; +use stdClass; /** * Update the filter condition for a random question. @@ -80,13 +81,30 @@ class update_filter_condition extends external_api { self::validate_context($thiscontext); require_capability('mod/quiz:manage', $thiscontext); - // Update filter condition. - $setparams = [ + // Validate question category is supplied in filter. + $decodedfiltercondition = json_decode($filtercondition, true); + $categoryid = (isset($decodedfiltercondition['filter']['category']['values'][0])) ? + $decodedfiltercondition['filter']['category']['values'][0] : false; + if (!$categoryid) { + throw new \moodle_exception('invalidcategoryid'); + } + + // Validate question category exists. + $categorycontextid = $DB->get_field('question_categories', 'contextid', ['id' => $categoryid], MUST_EXIST); + + // Validate set_reference record exists for this quiz and slot. + $setreferenceid = $DB->get_field('question_set_references', 'id', [ 'itemid' => $slotid, 'questionarea' => 'slot', 'component' => 'mod_quiz', - ]; - $DB->set_field('question_set_references', 'filtercondition', $filtercondition, $setparams); + ], MUST_EXIST); + + // Update set_reference record with new filtercondition and context. + $update = new stdClass(); + $update->id = $setreferenceid; + $update->filtercondition = $filtercondition; + $update->questionscontextid = $categorycontextid; + $DB->update_record('question_set_references', $update); return ['message' => get_string('updatefilterconditon_success', 'mod_quiz')]; } diff --git a/mod/quiz/tests/external/update_filter_condition_test.php b/mod/quiz/tests/external/update_filter_condition_test.php new file mode 100644 index 00000000000..3e83ae73517 --- /dev/null +++ b/mod/quiz/tests/external/update_filter_condition_test.php @@ -0,0 +1,173 @@ +. + +namespace mod_quiz\external; + +use advanced_testcase; +use core_question\local\bank\condition; +use mod_quiz\quiz_settings; + +/** + * Unit tests for the update_filter_condition webservice. + * + * @package mod_quiz + * @copyright 2025 onwards Catalyst IT {@link http://www.catalyst-eu.net/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Conn Warwicker + * @covers \mod_quiz\external\update_filter_condition::execute + */ +final class update_filter_condition_test extends advanced_testcase { + + /** + * Generate a course with a quiz activity with two random questions for use in tests. + * @return array + */ + private function create_quiz_with_random_questions(): array { + + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a quiz activity. + $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); + $quiz = $quizgenerator->create_instance(['course' => $course->id]); + $cm = get_coursemodule_from_instance('quiz', $quiz->id); + $quizobj = quiz_settings::create($quiz->id); + $quizcontext = \core\context\module::instance($quiz->cmid); + + // Create a question category (top level) and question. + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $questioncategory = $questiongenerator->create_question_category(); + + // Create 2 questions to choose from. + $questiongenerator->create_question('shortanswer', null, ['category' => $questioncategory->id]); + $questiongenerator->create_question('shortanswer', null, ['category' => $questioncategory->id]); + + // Add random question to the quiz. + $filtercondition = [ + 'filter' => [ + 'category' => [ + 'jointype' => condition::JOINTYPE_DEFAULT, + 'values' => [$questioncategory->id], + 'filteroptions' => ['includesubcategories' => false], + ], + ], + ]; + $quizobj->get_structure()->add_random_questions(1, 1, $filtercondition); + + return [ + $course, + $cm, + $questioncategory, + $filtercondition, + $quizcontext, + ]; + + } + + /** + * Test updating the filter conditions of a random question + * @runInSeparateProcess + * @return void + */ + public function test_update_filter_condition(): void { + + global $DB; + + [ + $course, + $cm, + $questioncategory, + $filtercondition, + $quizcontext, + ] = $this->create_quiz_with_random_questions(); + + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + + // Check what the question set reference record contains currently. + $qsetref = $DB->get_record('question_set_references', [ + 'usingcontextid' => $quizcontext->id, + ]); + + $this->assertEquals($questioncategory->contextid, $qsetref->questionscontextid); + + // Create a new question category on the course. + $coursecontext = \core\context\course::instance($course->id); + $questioncategory2 = $questiongenerator->create_question_category([ + 'contextid' => $coursecontext->id, + 'parent' => 0, + ]); + + // Call the webservice execute method. + $filtercondition['filter']['category']['values'] = [$questioncategory2->id]; + update_filter_condition::execute($cm->id, $qsetref->itemid, json_encode($filtercondition)); + + // Check that the questionscontextid value has been updated. + $qsetref = $DB->get_record('question_set_references', [ + 'usingcontextid' => $quizcontext->id, + ]); + + $this->assertEquals($questioncategory2->contextid, $qsetref->questionscontextid); + + } + + /** + * Test updating the filter conditions of a random question with an invalid array of conditions + * @runInSeparateProcess + * @return void + */ + public function test_invalid_filter_condition(): void { + + global $DB; + + [, $cm, , , $quizcontext] = $this->create_quiz_with_random_questions(); + + $qsetref = $DB->get_record('question_set_references', [ + 'usingcontextid' => $quizcontext->id, + ]); + + // Try to call the service with an invalid array of filterconditions. + $filtercondition = ['invalid' => true]; + $this->expectException(\moodle_exception::class); + update_filter_condition::execute($cm->id, $qsetref->itemid, json_encode($filtercondition)); + + } + + /** + * Test updating the filter conditions of a random question with an invalid question category + * @runInSeparateProcess + * @return void + */ + public function test_invalid_question_category(): void { + + global $DB; + + [ , $cm, , $filtercondition, $quizcontext] = $this->create_quiz_with_random_questions(); + + $qsetref = $DB->get_record('question_set_references', [ + 'usingcontextid' => $quizcontext->id, + ]); + + // Try to call the service with an invalid question category set in the $filtercondition. + $filtercondition['filter']['category']['values'] = [123]; + $this->expectException(\moodle_exception::class); + update_filter_condition::execute($cm->id, $qsetref->itemid, json_encode($filtercondition)); + + } + +}