Differences Between: [Versions 400 and 402] [Versions 401 and 402]
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. 13 // 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/>. 16 17 namespace qbank_managecategories; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 global $CFG; 22 require_once($CFG->dirroot . '/question/editlib.php'); 23 24 use context; 25 use context_course; 26 use context_module; 27 use moodle_url; 28 use core_question\local\bank\question_edit_contexts; 29 use stdClass; 30 31 /** 32 * Unit tests for qbank_managecategories\question_category_object. 33 * 34 * @package qbank_managecategories 35 * @copyright 2019 the Open University 36 * @author 2021, Guillermo Gomez Arias <guillermogomez@catalyst-au.net> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 * @coversDefaultClass \qbank_managecategories\question_category_object 39 */ 40 class question_category_object_test extends \advanced_testcase { 41 42 /** 43 * @var question_category_object used in the tests. 44 */ 45 protected $qcobject; 46 47 /** 48 * @var context a context to use. 49 */ 50 protected $context; 51 52 /** 53 * @var stdClass top category in context. 54 */ 55 protected $topcat; 56 57 /** 58 * @var stdClass course object. 59 */ 60 protected $course; 61 62 /** 63 * @var stdClass quiz object. 64 */ 65 protected $quiz; 66 67 /** 68 * @var question_edit_contexts 69 */ 70 private $qcontexts; 71 72 /** 73 * @var false|object|stdClass|null 74 */ 75 private $defaultcategoryobj; 76 77 /** 78 * @var string 79 */ 80 private $defaultcategory; 81 82 /** 83 * @var question_category_object 84 */ 85 private $qcobjectquiz; 86 87 protected function setUp(): void { 88 parent::setUp(); 89 self::setAdminUser(); 90 $this->resetAfterTest(); 91 $this->context = context_course::instance(SITEID); 92 $contexts = new question_edit_contexts($this->context); 93 $this->topcat = question_get_top_category($this->context->id, true); 94 $this->qcobject = new question_category_object(null, 95 new moodle_url('/question/bank/managecategories/category.php', ['courseid' => SITEID]), 96 $contexts->having_one_edit_tab_cap('categories'), 0, null, 0, 97 $contexts->having_cap('moodle/question:add')); 98 99 // Set up tests in a quiz context. 100 $this->course = $this->getDataGenerator()->create_course(); 101 $this->quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $this->course->id]); 102 $this->qcontexts = new question_edit_contexts(context_module::instance($this->quiz->cmid)); 103 104 $this->defaultcategoryobj = question_make_default_categories([$this->qcontexts->lowest()]); 105 $this->defaultcategory = $this->defaultcategoryobj->id . ',' . $this->defaultcategoryobj->contextid; 106 107 $this->qcobjectquiz = new question_category_object( 108 1, 109 new moodle_url('/mod/quiz/edit.php', ['cmid' => $this->quiz->cmid]), 110 $this->qcontexts->having_one_edit_tab_cap('categories'), 111 $this->defaultcategoryobj->id, 112 $this->defaultcategory, 113 null, 114 $this->qcontexts->having_cap('moodle/question:add')); 115 116 } 117 118 /** 119 * Test creating a category. 120 * 121 * @covers ::add_category 122 */ 123 public function test_add_category_no_idnumber() { 124 global $DB; 125 126 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 127 'New category', '', true, FORMAT_HTML, ''); // No idnumber passed as '' to match form data. 128 129 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 130 $this->assertSame('New category', $newcat->name); 131 $this->assertNull($newcat->idnumber); 132 } 133 134 /** 135 * Test creating a category with a tricky idnumber. 136 * 137 * @covers ::add_category 138 */ 139 public function test_add_category_set_idnumber_0() { 140 global $DB; 141 142 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 143 'New category', '', true, FORMAT_HTML, '0'); 144 145 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 146 $this->assertSame('New category', $newcat->name); 147 $this->assertSame('0', $newcat->idnumber); 148 } 149 150 /** 151 * Trying to add a category with duplicate idnumber blanks it. 152 * (In reality, this would probably get caught by form validation.) 153 * 154 * @covers ::add_category 155 */ 156 public function test_add_category_try_to_set_duplicate_idnumber() { 157 global $DB; 158 159 $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 160 'Existing category', '', true, FORMAT_HTML, 'frog'); 161 162 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 163 'New category', '', true, FORMAT_HTML, 'frog'); 164 165 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 166 $this->assertSame('New category', $newcat->name); 167 $this->assertNull($newcat->idnumber); 168 } 169 170 /** 171 * Test updating a category. 172 * 173 * @covers ::update_category 174 */ 175 public function test_update_category() { 176 global $DB; 177 178 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 179 'Old name', 'Description', true, FORMAT_HTML, 'frog'); 180 181 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 182 'New name', 'New description', FORMAT_HTML, '0', false); 183 184 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 185 $this->assertSame('New name', $newcat->name); 186 $this->assertSame('0', $newcat->idnumber); 187 } 188 189 /** 190 * Test updating a category to remove the idnumber. 191 * 192 * @covers ::update_category 193 */ 194 public function test_update_category_removing_idnumber() { 195 global $DB; 196 197 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 198 'Old name', 'Description', true, FORMAT_HTML, 'frog'); 199 200 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 201 'New name', 'New description', FORMAT_HTML, '', false); 202 203 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 204 $this->assertSame('New name', $newcat->name); 205 $this->assertNull($newcat->idnumber); 206 } 207 208 /** 209 * Test updating a category without changing the idnumber. 210 * 211 * @covers ::update_category 212 */ 213 public function test_update_category_dont_change_idnumber() { 214 global $DB; 215 216 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 217 'Old name', 'Description', true, FORMAT_HTML, 'frog'); 218 219 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 220 'New name', 'New description', FORMAT_HTML, 'frog', false); 221 222 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 223 $this->assertSame('New name', $newcat->name); 224 $this->assertSame('frog', $newcat->idnumber); 225 } 226 227 /** 228 * Trying to update a category so its idnumber duplicates idnumber blanks it. 229 * (In reality, this would probably get caught by form validation.) 230 * 231 * @covers ::update_category 232 */ 233 public function test_update_category_try_to_set_duplicate_idnumber() { 234 global $DB; 235 236 $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 237 'Existing category', '', true, FORMAT_HTML, 'toad'); 238 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 239 'old name', '', true, FORMAT_HTML, 'frog'); 240 241 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 242 'New name', '', FORMAT_HTML, 'toad', false); 243 244 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 245 $this->assertSame('New name', $newcat->name); 246 $this->assertNull($newcat->idnumber); 247 } 248 249 /** 250 * Test the question category created event. 251 * 252 * @covers ::add_category 253 */ 254 public function test_question_category_created() { 255 // Trigger and capture the event. 256 $sink = $this->redirectEvents(); 257 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 258 $events = $sink->get_events(); 259 $event = reset($events); 260 261 // Check that the event data is valid. 262 $this->assertInstanceOf('\core\event\question_category_created', $event); 263 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 264 $this->assertEventContextNotUsed($event); 265 } 266 267 /** 268 * Test the question category deleted event. 269 * 270 * @covers ::delete_category 271 */ 272 public function test_question_category_deleted() { 273 // Create the category. 274 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 275 276 // Trigger and capture the event. 277 $sink = $this->redirectEvents(); 278 $this->qcobjectquiz->delete_category($categoryid); 279 $events = $sink->get_events(); 280 $event = reset($events); 281 282 // Check that the event data is valid. 283 $this->assertInstanceOf('\core\event\question_category_deleted', $event); 284 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 285 $this->assertEquals($categoryid, $event->objectid); 286 $this->assertDebuggingNotCalled(); 287 } 288 289 /** 290 * Test the question category updated event. 291 * 292 * @covers ::update_category 293 */ 294 public function test_question_category_updated() { 295 // Create the category. 296 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 297 298 // Trigger and capture the event. 299 $sink = $this->redirectEvents(); 300 $this->qcobjectquiz->update_category($categoryid, $this->defaultcategory, 'updatedcategory', '', FORMAT_HTML, '', false); 301 $events = $sink->get_events(); 302 $event = reset($events); 303 304 // Check that the event data is valid. 305 $this->assertInstanceOf('\core\event\question_category_updated', $event); 306 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 307 $this->assertEquals($categoryid, $event->objectid); 308 $this->assertDebuggingNotCalled(); 309 } 310 311 /** 312 * Test the question category viewed event. 313 * There is no external API for viewing the category, so the unit test will simply 314 * create and trigger the event and ensure data is returned as expected. 315 * 316 * @covers ::add_category 317 */ 318 public function test_question_category_viewed() { 319 // Create the category. 320 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 321 322 // Log the view of this category. 323 $category = new stdClass(); 324 $category->id = $categoryid; 325 $context = context_module::instance($this->quiz->cmid); 326 $event = \core\event\question_category_viewed::create_from_question_category_instance($category, $context); 327 328 // Trigger and capture the event. 329 $sink = $this->redirectEvents(); 330 $event->trigger(); 331 $events = $sink->get_events(); 332 $event = reset($events); 333 334 // Check that the event data is valid. 335 $this->assertInstanceOf('\core\event\question_category_viewed', $event); 336 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 337 $this->assertEquals($categoryid, $event->objectid); 338 $this->assertDebuggingNotCalled(); 339 340 } 341 342 /** 343 * Test that get_real_question_ids_in_category() returns question id 344 * of a shortanswer question in a category. 345 * 346 * @covers ::get_real_question_ids_in_category 347 */ 348 public function test_get_real_question_ids_in_category_shortanswer() { 349 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 350 $categoryid = $this->defaultcategoryobj->id; 351 352 // Short answer question is made of one question. 353 $shortanswer = $generator->create_question('shortanswer', null, ['category' => $categoryid]); 354 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 355 $this->assertCount(1, $questionids); 356 $this->assertContains($shortanswer->id, $questionids); 357 } 358 359 /** 360 * Test that get_real_question_ids_in_category() returns question id 361 * of a multianswer question in a category. 362 * 363 * @covers ::get_real_question_ids_in_category 364 */ 365 public function test_get_real_question_ids_in_category_multianswer() { 366 global $DB; 367 $countq = $DB->count_records('question'); 368 $countqbe = $DB->count_records('question_bank_entries'); 369 370 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 371 $categoryid = $this->defaultcategoryobj->id; 372 373 // Multi answer question is made of one parent and two child questions. 374 $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); 375 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 376 $this->assertCount(1, $questionids); 377 $this->assertContains($multianswer->id, $questionids); 378 $this->assertEquals(3, $DB->count_records('question') - $countq); 379 $this->assertEquals(3, $DB->count_records('question_bank_entries') - $countqbe); 380 } 381 382 /** 383 * Test that get_real_question_ids_in_category() returns question ids 384 * of two versions of a multianswer question in a category. 385 * 386 * @covers ::get_real_question_ids_in_category 387 */ 388 public function test_get_real_question_ids_in_category_multianswer_two_versions() { 389 global $DB; 390 $countq = $DB->count_records('question'); 391 $countqv = $DB->count_records('question_versions'); 392 $countqbe = $DB->count_records('question_bank_entries'); 393 394 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 395 $categoryid = $this->defaultcategoryobj->id; 396 397 // Create two versions of a multianswer question which will lead to 398 // 2 parents and 4 child questions in the question bank. 399 $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); 400 $multianswernew = $generator->update_question($multianswer, null, ['name' => 'This is a new version']); 401 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 402 $this->assertCount(2, $questionids); 403 $this->assertContains($multianswer->id, $questionids); 404 $this->assertContains($multianswernew->id, $questionids); 405 $this->assertEquals(6, $DB->count_records('question') - $countq); 406 $this->assertEquals(6, $DB->count_records('question_versions') - $countqv); 407 $this->assertEquals(3, $DB->count_records('question_bank_entries') - $countqbe); 408 } 409 410 /** 411 * Test that get_real_question_ids_in_category() returns question id 412 * of a multianswer question in a category even if their child questions are 413 * linked to a category that doesn't exist. 414 * 415 * @covers ::get_real_question_ids_in_category 416 */ 417 public function test_get_real_question_ids_in_category_multianswer_bad_data() { 418 global $DB; 419 $countqbe = $DB->count_records('question_bank_entries'); 420 421 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 422 $categoryid = $this->defaultcategoryobj->id; 423 424 // Multi answer question is made of one parent and two child questions. 425 $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); 426 $qversion = $DB->get_record('question_versions', ['questionid' => $multianswer->id]); 427 428 // Update category id for child questions to a category that doesn't exist. 429 $DB->set_field_select('question_bank_entries', 'questioncategoryid', 430 123456, 'id <> :id', ['id' => $qversion->questionbankentryid]); 431 432 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 433 $this->assertCount(1, $questionids); 434 $this->assertContains($multianswer->id, $questionids); 435 $this->assertEquals(3, $DB->count_records('question_bank_entries') - $countqbe); 436 } 437 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body