Merge branch 'MDL-81073' of https://github.com/paulholden/moodle
[moodle.git] / mod / quiz / tests / quiz_question_restore_test.php
blob27b9a350fbb048215c2e3e10d5f1538b7bec596d
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 namespace mod_quiz;
19 use core_question\question_reference_manager;
20 use mod_quiz\question\display_options;
22 defined('MOODLE_INTERNAL') || die();
24 global $CFG;
25 require_once(__DIR__ . '/quiz_question_helper_test_trait.php');
26 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
27 require_once($CFG->dirroot . '/mod/quiz/locallib.php');
29 /**
30 * Quiz backup and restore tests.
32 * @package mod_quiz
33 * @category test
34 * @copyright 2021 Catalyst IT Australia Pty Ltd
35 * @author Safat Shahin <safatshahin@catalyst-au.net>
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 * @coversDefaultClass \mod_quiz\question\bank\qbank_helper
38 * @coversDefaultClass \backup_quiz_activity_structure_step
39 * @coversDefaultClass \restore_quiz_activity_structure_step
41 class quiz_question_restore_test extends \advanced_testcase {
42 use \quiz_question_helper_test_trait;
44 /**
45 * @var \stdClass test student user.
47 protected $student;
49 /**
50 * Called before every test.
52 public function setUp(): void {
53 global $USER;
54 parent::setUp();
55 $this->setAdminUser();
56 $this->course = $this->getDataGenerator()->create_course();
57 $this->student = $this->getDataGenerator()->create_user();
58 $this->user = $USER;
61 /**
62 * Test a quiz backup and restore in a different course without attempts for course question bank.
64 * @covers ::get_question_structure
66 public function test_quiz_restore_in_a_different_course_using_course_question_bank() {
67 $this->resetAfterTest();
69 // Create the test quiz.
70 $quiz = $this->create_test_quiz($this->course);
71 $oldquizcontext = \context_module::instance($quiz->cmid);
72 // Test for questions from a different context.
73 $coursecontext = \context_course::instance($this->course->id);
74 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
75 $this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $coursecontext->id]);
76 $this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $coursecontext->id]);
78 // Make the backup.
79 $backupid = $this->backup_quiz($quiz, $this->user);
81 // Delete the current course to make sure there is no data.
82 delete_course($this->course, false);
84 // Check if the questions and associated data are deleted properly.
85 $this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
86 $quiz->id, $oldquizcontext)));
88 // Restore the course.
89 $newcourse = $this->getDataGenerator()->create_course();
90 $this->restore_quiz($backupid, $newcourse, $this->user);
92 // Verify.
93 $modules = get_fast_modinfo($newcourse->id)->get_instances_of('quiz');
94 $module = reset($modules);
95 $questions = \mod_quiz\question\bank\qbank_helper::get_question_structure(
96 $module->instance, $module->context);
97 $this->assertCount(3, $questions);
101 * Test a quiz backup and restore in a different course without attempts for quiz question bank.
103 * @covers ::get_question_structure
105 public function test_quiz_restore_in_a_different_course_using_quiz_question_bank() {
106 $this->resetAfterTest();
108 // Create the test quiz.
109 $quiz = $this->create_test_quiz($this->course);
110 // Test for questions from a different context.
111 $quizcontext = \context_module::instance($quiz->cmid);
112 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
113 $this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
114 $this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
116 // Make the backup.
117 $backupid = $this->backup_quiz($quiz, $this->user);
119 // Delete the current course to make sure there is no data.
120 delete_course($this->course, false);
122 // Check if the questions and associated datas are deleted properly.
123 $this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
124 $quiz->id, $quizcontext)));
126 // Restore the course.
127 $newcourse = $this->getDataGenerator()->create_course();
128 $this->restore_quiz($backupid, $newcourse, $this->user);
130 // Verify.
131 $modules = get_fast_modinfo($newcourse->id)->get_instances_of('quiz');
132 $module = reset($modules);
133 $this->assertEquals(3, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
134 $module->instance, $module->context)));
138 * Count the questions for the context.
140 * @param int $contextid
141 * @param string $extracondition
142 * @return int the number of questions.
144 protected function question_count(int $contextid, string $extracondition = ''): int {
145 global $DB;
146 return $DB->count_records_sql(
147 "SELECT COUNT(q.id)
148 FROM {question} q
149 JOIN {question_versions} qv ON qv.questionid = q.id
150 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
151 JOIN {question_categories} qc on qc.id = qbe.questioncategoryid
152 WHERE qc.contextid = ?
153 $extracondition", [$contextid]);
157 * Test if a duplicate does not duplicate questions in course question bank.
159 * @covers ::duplicate_module
161 public function test_quiz_duplicate_does_not_duplicate_course_question_bank_questions() {
162 $this->resetAfterTest();
163 $quiz = $this->create_test_quiz($this->course);
164 // Test for questions from a different context.
165 $context = \context_course::instance($this->course->id);
166 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
167 $this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
168 $this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $context->id]);
169 // Count the questions in course context.
170 $this->assertEquals(7, $this->question_count($context->id));
171 $newquiz = $this->duplicate_quiz($this->course, $quiz);
172 $this->assertEquals(7, $this->question_count($context->id));
173 $context = \context_module::instance($newquiz->id);
174 // Count the questions in the quiz context.
175 $this->assertEquals(0, $this->question_count($context->id));
179 * Test quiz duplicate for quiz question bank.
181 * @covers ::duplicate_module
183 public function test_quiz_duplicate_for_quiz_question_bank_questions() {
184 $this->resetAfterTest();
185 $quiz = $this->create_test_quiz($this->course);
186 // Test for questions from a different context.
187 $context = \context_module::instance($quiz->cmid);
188 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
189 $this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
190 $this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $context->id]);
191 // Count the questions in course context.
192 $this->assertEquals(7, $this->question_count($context->id));
193 $newquiz = $this->duplicate_quiz($this->course, $quiz);
194 $this->assertEquals(7, $this->question_count($context->id));
195 $context = \context_module::instance($newquiz->id);
196 // Count the questions in the quiz context.
197 $this->assertEquals(7, $this->question_count($context->id));
201 * Test quiz restore with attempts.
203 * @covers ::get_question_structure
205 public function test_quiz_restore_with_attempts() {
206 $this->resetAfterTest();
208 // Create a quiz.
209 $quiz = $this->create_test_quiz($this->course);
210 $quizcontext = \context_module::instance($quiz->cmid);
211 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
212 $this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
213 $this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
215 // Attempt it as a student, and check.
216 /** @var \question_usage_by_activity $quba */
217 [, $quba] = $this->attempt_quiz($quiz, $this->student);
218 $this->assertEquals(3, $quba->question_count());
219 $this->assertCount(1, quiz_get_user_attempts($quiz->id, $this->student->id));
221 // Make the backup.
222 $backupid = $this->backup_quiz($quiz, $this->user);
224 // Delete the current course to make sure there is no data.
225 delete_course($this->course, false);
227 // Restore the backup.
228 $newcourse = $this->getDataGenerator()->create_course();
229 $this->restore_quiz($backupid, $newcourse, $this->user);
231 // Verify.
232 $modules = get_fast_modinfo($newcourse->id)->get_instances_of('quiz');
233 $module = reset($modules);
234 $this->assertCount(1, quiz_get_user_attempts($module->instance, $this->student->id));
235 $this->assertCount(3, \mod_quiz\question\bank\qbank_helper::get_question_structure(
236 $module->instance, $module->context));
240 * Test pre 4.0 quiz restore for regular questions.
242 * Also, for efficiency, tests restore of the review options.
244 * @covers ::process_quiz_question_legacy_instance
246 public function test_pre_4_quiz_restore_for_regular_questions() {
247 global $USER, $DB;
248 $this->resetAfterTest();
249 $backupid = 'abc';
250 $backuppath = make_backup_temp_directory($backupid);
251 get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
252 __DIR__ . "/fixtures/moodle_28_quiz.mbz", $backuppath);
254 // Do the restore to new course with default settings.
255 $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
256 $newcourseid = \restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid);
257 $rc = new \restore_controller($backupid, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
258 \backup::TARGET_NEW_COURSE);
260 $this->assertTrue($rc->execute_precheck());
261 $rc->execute_plan();
262 $rc->destroy();
264 // Get the information about the resulting course and check that it is set up correctly.
265 $modinfo = get_fast_modinfo($newcourseid);
266 $quiz = array_values($modinfo->get_instances_of('quiz'))[0];
267 $quizobj = \mod_quiz\quiz_settings::create($quiz->instance);
268 $structure = structure::create_for_quiz($quizobj);
270 // Verify the restored review options setting.
271 $this->assertEquals(display_options::DURING |
272 display_options::IMMEDIATELY_AFTER |
273 display_options::LATER_WHILE_OPEN |
274 display_options::AFTER_CLOSE, $quizobj->get_quiz()->reviewmaxmarks);
276 // Are the correct slots returned?
277 $slots = $structure->get_slots();
278 $this->assertCount(2, $slots);
280 $quizobj->preload_questions();
281 $quizobj->load_questions();
282 $questions = $quizobj->get_questions();
283 $this->assertCount(2, $questions);
285 // Count the questions in quiz qbank.
286 $this->assertEquals(2, $this->question_count($quizobj->get_context()->id));
290 * Test pre 4.0 quiz restore for random questions.
292 * @covers ::process_quiz_question_legacy_instance
294 public function test_pre_4_quiz_restore_for_random_questions() {
295 global $USER, $DB;
296 $this->resetAfterTest();
298 $backupid = 'abc';
299 $backuppath = make_backup_temp_directory($backupid);
300 get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
301 __DIR__ . "/fixtures/random_by_tag_quiz.mbz", $backuppath);
303 // Do the restore to new course with default settings.
304 $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
305 $newcourseid = \restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid);
306 $rc = new \restore_controller($backupid, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
307 \backup::TARGET_NEW_COURSE);
309 $this->assertTrue($rc->execute_precheck());
310 $rc->execute_plan();
311 $rc->destroy();
313 // Get the information about the resulting course and check that it is set up correctly.
314 $modinfo = get_fast_modinfo($newcourseid);
315 $quiz = array_values($modinfo->get_instances_of('quiz'))[0];
316 $quizobj = \mod_quiz\quiz_settings::create($quiz->instance);
317 $structure = structure::create_for_quiz($quizobj);
319 // Are the correct slots returned?
320 $slots = $structure->get_slots();
321 $this->assertCount(1, $slots);
323 $quizobj->preload_questions();
324 $quizobj->load_questions();
325 $questions = $quizobj->get_questions();
326 $this->assertCount(1, $questions);
328 // Count the questions for course question bank.
329 $this->assertEquals(6, $this->question_count(\context_course::instance($newcourseid)->id));
330 $this->assertEquals(6, $this->question_count(\context_course::instance($newcourseid)->id,
331 "AND q.qtype <> 'random'"));
333 // Count the questions in quiz qbank.
334 $this->assertEquals(0, $this->question_count($quizobj->get_context()->id));
338 * Test pre 4.0 quiz restore for random question tags.
340 * @covers ::process_quiz_question_legacy_instance
342 public function test_pre_4_quiz_restore_for_random_question_tags() {
343 global $USER, $DB;
344 $this->resetAfterTest();
345 $randomtags = [
346 '1' => ['first question', 'one', 'number one'],
347 '2' => ['first question', 'one', 'number one'],
348 '3' => ['one', 'number one', 'second question'],
350 $backupid = 'abc';
351 $backuppath = make_backup_temp_directory($backupid);
352 get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
353 __DIR__ . "/fixtures/moodle_311_quiz.mbz", $backuppath);
355 // Do the restore to new course with default settings.
356 $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
357 $newcourseid = \restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid);
358 $rc = new \restore_controller($backupid, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
359 \backup::TARGET_NEW_COURSE);
361 $this->assertTrue($rc->execute_precheck());
362 $rc->execute_plan();
363 $rc->destroy();
365 // Get the information about the resulting course and check that it is set up correctly.
366 $modinfo = get_fast_modinfo($newcourseid);
367 $quiz = array_values($modinfo->get_instances_of('quiz'))[0];
368 $quizobj = \mod_quiz\quiz_settings::create($quiz->instance);
369 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
371 // Count the questions in quiz qbank.
372 $context = \context_module::instance(get_coursemodule_from_instance("quiz", $quizobj->get_quizid(), $newcourseid)->id);
373 $this->assertEquals(2, $this->question_count($context->id));
375 // Are the correct slots returned?
376 $slots = $structure->get_slots();
377 $this->assertCount(3, $slots);
379 // Check if the tags match with the actual restored data.
380 foreach ($slots as $slot) {
381 $setreference = $DB->get_record('question_set_references',
382 ['itemid' => $slot->id, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
383 $filterconditions = json_decode($setreference->filtercondition);
384 $tags = [];
385 foreach ($filterconditions->tags as $tagstring) {
386 $tag = explode(',', $tagstring);
387 $tags[] = $tag[1];
389 $this->assertEquals([], array_diff($randomtags[$slot->slot], $tags));
395 * Test pre 4.0 quiz restore for random question used on multiple quizzes.
397 * @covers ::process_quiz_question_legacy_instance
399 public function test_pre_4_quiz_restore_shared_random_question() {
400 global $USER, $DB;
401 $this->resetAfterTest();
403 $backupid = 'abc';
404 $backuppath = make_backup_temp_directory($backupid);
405 get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
406 __DIR__ . "/fixtures/pre-40-shared-random-question.mbz", $backuppath);
408 // Do the restore to new course with default settings.
409 $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
410 $newcourseid = \restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid);
411 $rc = new \restore_controller($backupid, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
412 \backup::TARGET_NEW_COURSE);
414 $this->assertTrue($rc->execute_precheck());
415 $rc->execute_plan();
416 $rc->destroy();
418 // Get the information about the resulting course and check that it is set up correctly.
419 // Each quiz should contain an instance of the random question.
420 $modinfo = get_fast_modinfo($newcourseid);
421 $quizzes = $modinfo->get_instances_of('quiz');
422 $this->assertCount(2, $quizzes);
423 foreach ($quizzes as $quiz) {
424 $quizobj = \mod_quiz\quiz_settings::create($quiz->instance);
425 $structure = structure::create_for_quiz($quizobj);
427 // Are the correct slots returned?
428 $slots = $structure->get_slots();
429 $this->assertCount(1, $slots);
431 $quizobj->preload_questions();
432 $quizobj->load_questions();
433 $questions = $quizobj->get_questions();
434 $this->assertCount(1, $questions);
437 // Count the questions for course question bank.
438 // We should have a single question, the random question should have been deleted after the restore.
439 $this->assertEquals(1, $this->question_count(\context_course::instance($newcourseid)->id));
440 $this->assertEquals(1, $this->question_count(\context_course::instance($newcourseid)->id,
441 "AND q.qtype <> 'random'"));
443 // Count the questions in quiz qbank.
444 $this->assertEquals(0, $this->question_count($quizobj->get_context()->id));
448 * Ensure that question slots are correctly backed up and restored with all properties.
450 * @covers \backup_quiz_activity_structure_step::define_structure()
451 * @return void
453 public function test_backup_restore_question_slots(): void {
454 $this->resetAfterTest(true);
456 $course1 = $this->getDataGenerator()->create_course();
457 $course2 = $this->getDataGenerator()->create_course();
459 $user1 = $this->getDataGenerator()->create_and_enrol($course1, 'editingteacher');
460 $this->getDataGenerator()->enrol_user($user1->id, $course2->id, 'editingteacher');
462 // Make a quiz.
463 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
465 $quiz = $quizgenerator->create_instance(['course' => $course1->id, 'questionsperpage' => 0, 'grade' => 100.0,
466 'sumgrades' => 3]);
468 // Create some fixed and random questions.
469 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
471 $cat = $questiongenerator->create_question_category();
472 $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
473 $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
474 $matchq = $questiongenerator->create_question('match', null, ['category' => $cat->id]);
475 $randomcat = $questiongenerator->create_question_category();
476 $questiongenerator->create_question('shortanswer', null, ['category' => $randomcat->id]);
477 $questiongenerator->create_question('numerical', null, ['category' => $randomcat->id]);
478 $questiongenerator->create_question('match', null, ['category' => $randomcat->id]);
480 // Add them to the quiz.
481 quiz_add_quiz_question($saq->id, $quiz, 1, 3);
482 quiz_add_quiz_question($numq->id, $quiz, 2, 2);
483 quiz_add_quiz_question($matchq->id, $quiz, 3, 1);
484 $this->add_random_questions($quiz->id, 3, $randomcat->id, 2);
486 $quizobj = quiz_settings::create($quiz->id, $user1->id);
487 $originalstructure = \mod_quiz\structure::create_for_quiz($quizobj);
489 // Set one slot to a non-default display number.
490 $originalslots = $originalstructure->get_slots();
491 $firstslot = reset($originalslots);
492 $originalstructure->update_slot_display_number($firstslot->id, rand(5, 10));
494 // Set one slot to requireprevious.
495 $lastslot = end($originalslots);
496 $originalstructure->update_question_dependency($lastslot->id, true);
498 // Backup and restore the quiz.
499 $backupid = $this->backup_quiz($quiz, $user1);
500 $this->restore_quiz($backupid, $course2, $user1);
502 // Ensure the restored slots match the original slots.
503 $modinfo = get_fast_modinfo($course2);
504 $quizzes = $modinfo->get_instances_of('quiz');
505 $restoredquiz = reset($quizzes);
506 $restoredquizobj = quiz_settings::create($restoredquiz->instance, $user1->id);
507 $restoredstructure = \mod_quiz\structure::create_for_quiz($restoredquizobj);
508 $restoredslots = array_values($restoredstructure->get_slots());
509 $originalstructure = \mod_quiz\structure::create_for_quiz($quizobj);
510 $originalslots = array_values($originalstructure->get_slots());
511 foreach ($restoredslots as $key => $restoredslot) {
512 $originalslot = $originalslots[$key];
513 $this->assertEquals($originalslot->quizid, $quiz->id);
514 $this->assertEquals($restoredslot->quizid, $restoredquiz->instance);
515 $this->assertEquals($originalslot->slot, $restoredslot->slot);
516 $this->assertEquals($originalslot->page, $restoredslot->page);
517 $this->assertEquals($originalslot->displaynumber, $restoredslot->displaynumber);
518 $this->assertEquals($originalslot->requireprevious, $restoredslot->requireprevious);
519 $this->assertEquals($originalslot->maxmark, $restoredslot->maxmark);
524 * Test pre 4.3 quiz restore for random question filter conditions.
526 * @covers \restore_question_set_reference_data_trait::process_question_set_reference
528 public function test_pre_43_quiz_restore_for_random_question_filtercondition() {
529 global $USER, $DB;
530 $this->resetAfterTest();
531 $backupid = 'abc';
532 $backuppath = make_backup_temp_directory($backupid);
533 get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
534 __DIR__ . "/fixtures/moodle_42_random_question.mbz", $backuppath);
536 // Do the restore to new course with default settings.
537 $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
538 $newcourseid = \restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid);
539 $rc = new \restore_controller($backupid, $newcourseid, \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
540 \backup::TARGET_NEW_COURSE);
542 $this->assertTrue($rc->execute_precheck());
543 $rc->execute_plan();
544 $rc->destroy();
546 // Get the information about the resulting course and check that it is set up correctly.
547 $modinfo = get_fast_modinfo($newcourseid);
548 $quiz = array_values($modinfo->get_instances_of('quiz'))[0];
549 $quizobj = \mod_quiz\quiz_settings::create($quiz->instance);
550 $structure = \mod_quiz\structure::create_for_quiz($quizobj);
552 // Count the questions in quiz qbank.
553 $context = \context_module::instance(get_coursemodule_from_instance("quiz", $quizobj->get_quizid(), $newcourseid)->id);
554 $this->assertEquals(2, $this->question_count($context->id));
556 // Are the correct slots returned?
557 $slots = $structure->get_slots();
558 $this->assertCount(1, $slots);
560 // Check that the filtercondition now matches the 4.3 structure.
561 foreach ($slots as $slot) {
562 $setreference = $DB->get_record('question_set_references',
563 ['itemid' => $slot->id, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
564 $filterconditions = json_decode($setreference->filtercondition, true);
565 $this->assertArrayHasKey('cat', $filterconditions);
566 $this->assertArrayHasKey('jointype', $filterconditions);
567 $this->assertArrayHasKey('qpage', $filterconditions);
568 $this->assertArrayHasKey('qperpage', $filterconditions);
569 $this->assertArrayHasKey('filter', $filterconditions);
570 $this->assertArrayHasKey('category', $filterconditions['filter']);
571 $this->assertArrayHasKey('qtagids', $filterconditions['filter']);
572 $this->assertArrayHasKey('filteroptions', $filterconditions['filter']['category']);
573 $this->assertArrayHasKey('includesubcategories', $filterconditions['filter']['category']['filteroptions']);
575 // MDL-79708: Bad filter conversion check.
576 $this->assertArrayNotHasKey('includesubcategories', $filterconditions['filter']['category']);
578 $this->assertArrayNotHasKey('questioncategoryid', $filterconditions);
579 $this->assertArrayNotHasKey('tags', $filterconditions);
580 $expectedtags = \core_tag_tag::get_by_name_bulk(1, ['foo', 'bar']);
581 $expectedtagids = array_values(array_map(fn($expectedtag) => $expectedtag->id, $expectedtags));
582 $this->assertEquals($expectedtagids, $filterconditions['filter']['qtagids']['values']);
583 $expectedcategory = $DB->get_record('question_categories', ['idnumber' => 'RAND']);
584 $this->assertEquals($expectedcategory->id, $filterconditions['filter']['category']['values'][0]);
585 $expectedcat = implode(',', [$expectedcategory->id, $expectedcategory->contextid]);
586 $this->assertEquals($expectedcat, $filterconditions['cat']);
588 // MDL-79708: Try to convert already converted filter.
589 $filterconditionsold = $filterconditions;
590 $filterconditions = question_reference_manager::convert_legacy_set_reference_filter_condition($filterconditions);
591 // Check that the filtercondition didn't change.
592 $this->assertEquals($filterconditionsold, $filterconditions);
594 // MDL-79708: Try to convert a filter with previously bad conversion.
595 $filterconditions['filter']['category']['includesubcategories'] = 0;
596 unset($filterconditions['filter']['category']['filteroptions']);
597 $filterconditions = question_reference_manager::convert_legacy_set_reference_filter_condition($filterconditions);
598 $this->assertEquals($filterconditionsold, $filterconditions);