[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 /** 18 * Unit tests for (some of) ../questionlib.php. 19 * 20 * @package core_question 21 * @category phpunit 22 * @copyright 2006 The Open University 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 global $CFG; 29 30 require_once($CFG->libdir . '/questionlib.php'); 31 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 32 33 // Get the necessary files to perform backup and restore. 34 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 35 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 36 37 /** 38 * Unit tests for (some of) ../questionlib.php. 39 * 40 * @copyright 2006 The Open University 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class core_questionlib_testcase extends advanced_testcase { 44 45 /** 46 * Test set up. 47 * 48 * This is executed before running any test in this file. 49 */ 50 public function setUp() { 51 $this->resetAfterTest(); 52 } 53 54 /** 55 * Return true and false to test functions with feedback on and off. 56 * 57 * @return array Test data 58 */ 59 public function provider_feedback() { 60 return array( 61 'Feedback test' => array(true), 62 'No feedback test' => array(false) 63 ); 64 } 65 66 /** 67 * Setup a course, a quiz, a question category and a question for testing. 68 * 69 * @param string $type The type of question category to create. 70 * @return array The created data objects 71 */ 72 public function setup_quiz_and_questions($type = 'module') { 73 // Create course category. 74 $category = $this->getDataGenerator()->create_category(); 75 76 // Create course. 77 $course = $this->getDataGenerator()->create_course(array('numsections' => 5)); 78 79 $options = array( 80 'course' => $course->id, 81 'duedate' => time(), 82 ); 83 84 // Generate an assignment with due date (will generate a course event). 85 $quiz = $this->getDataGenerator()->create_module('quiz', $options); 86 87 $qgen = $this->getDataGenerator()->get_plugin_generator('core_question'); 88 89 if ('course' == $type) { 90 $context = context_course::instance($course->id); 91 } else if ('category' == $type) { 92 $context = context_coursecat::instance($category->id); 93 } else { 94 $context = context_module::instance($quiz->cmid); 95 } 96 97 $qcat = $qgen->create_question_category(array('contextid' => $context->id)); 98 99 $questions = array( 100 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)), 101 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)), 102 ); 103 104 quiz_add_quiz_question($questions[0]->id, $quiz); 105 106 return array($category, $course, $quiz, $qcat, $questions); 107 } 108 109 public function test_question_reorder_qtypes() { 110 $this->assertEquals( 111 array(0 => 't2', 1 => 't1', 2 => 't3'), 112 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't1', +1)); 113 $this->assertEquals( 114 array(0 => 't1', 1 => 't2', 2 => 't3'), 115 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't1', -1)); 116 $this->assertEquals( 117 array(0 => 't2', 1 => 't1', 2 => 't3'), 118 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't2', -1)); 119 $this->assertEquals( 120 array(0 => 't1', 1 => 't2', 2 => 't3'), 121 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 't3', +1)); 122 $this->assertEquals( 123 array(0 => 't1', 1 => 't2', 2 => 't3'), 124 question_reorder_qtypes(array('t1' => '', 't2' => '', 't3' => ''), 'missing', +1)); 125 } 126 127 public function test_match_grade_options() { 128 $gradeoptions = question_bank::fraction_options_full(); 129 130 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.3333333, 'error')); 131 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.333333, 'error')); 132 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.33333, 'error')); 133 $this->assertFalse(match_grade_options($gradeoptions, 0.3333, 'error')); 134 135 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.3333333, 'nearest')); 136 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.333333, 'nearest')); 137 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.33333, 'nearest')); 138 $this->assertEquals(0.3333333, match_grade_options($gradeoptions, 0.33, 'nearest')); 139 140 $this->assertEquals(-0.1428571, match_grade_options($gradeoptions, -0.15, 'nearest')); 141 } 142 143 /** 144 * This function tests that the functions responsible for moving questions to 145 * different contexts also updates the tag instances associated with the questions. 146 */ 147 public function test_altering_tag_instance_context() { 148 global $CFG, $DB; 149 150 // Set to admin user. 151 $this->setAdminUser(); 152 153 // Create two course categories - we are going to delete one of these later and will expect 154 // all the questions belonging to the course in the deleted category to be moved. 155 $coursecat1 = $this->getDataGenerator()->create_category(); 156 $coursecat2 = $this->getDataGenerator()->create_category(); 157 158 // Create a couple of categories and questions. 159 $context1 = context_coursecat::instance($coursecat1->id); 160 $context2 = context_coursecat::instance($coursecat2->id); 161 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 162 $questioncat1 = $questiongenerator->create_question_category(array('contextid' => 163 $context1->id)); 164 $questioncat2 = $questiongenerator->create_question_category(array('contextid' => 165 $context2->id)); 166 $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id)); 167 $question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id)); 168 $question3 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id)); 169 $question4 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id)); 170 171 // Now lets tag these questions. 172 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $context1, array('tag 1', 'tag 2')); 173 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $context1, array('tag 3', 'tag 4')); 174 core_tag_tag::set_item_tags('core_question', 'question', $question3->id, $context2, array('tag 5', 'tag 6')); 175 core_tag_tag::set_item_tags('core_question', 'question', $question4->id, $context2, array('tag 7', 'tag 8')); 176 177 // Test moving the questions to another category. 178 question_move_questions_to_category(array($question1->id, $question2->id), $questioncat2->id); 179 180 // Test that all tag_instances belong to one context. 181 $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question', 182 'contextid' => $questioncat2->contextid))); 183 184 // Test moving them back. 185 question_move_questions_to_category(array($question1->id, $question2->id), $questioncat1->id); 186 187 // Test that all tag_instances are now reset to how they were initially. 188 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 189 'contextid' => $questioncat1->contextid))); 190 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 191 'contextid' => $questioncat2->contextid))); 192 193 // Now test moving a whole question category to another context. 194 question_move_category_to_context($questioncat1->id, $questioncat1->contextid, $questioncat2->contextid); 195 196 // Test that all tag_instances belong to one context. 197 $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question', 198 'contextid' => $questioncat2->contextid))); 199 200 // Now test moving them back. 201 question_move_category_to_context($questioncat1->id, $questioncat2->contextid, 202 context_coursecat::instance($coursecat1->id)->id); 203 204 // Test that all tag_instances are now reset to how they were initially. 205 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 206 'contextid' => $questioncat1->contextid))); 207 $this->assertEquals(4, $DB->count_records('tag_instance', array('component' => 'core_question', 208 'contextid' => $questioncat2->contextid))); 209 210 // Now we want to test deleting the course category and moving the questions to another category. 211 question_delete_course_category($coursecat1, $coursecat2, false); 212 213 // Test that all tag_instances belong to one context. 214 $this->assertEquals(8, $DB->count_records('tag_instance', array('component' => 'core_question', 215 'contextid' => $questioncat2->contextid))); 216 217 // Create a course. 218 $course = $this->getDataGenerator()->create_course(); 219 220 // Create some question categories and questions in this course. 221 $coursecontext = context_course::instance($course->id); 222 $questioncat = $questiongenerator->create_question_category(array('contextid' => 223 $coursecontext->id)); 224 $question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id)); 225 $question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id)); 226 227 // Add some tags to these questions. 228 core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, array('tag 1', 'tag 2')); 229 core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, array('tag 1', 'tag 2')); 230 231 // Create a course that we are going to restore the other course to. 232 $course2 = $this->getDataGenerator()->create_course(); 233 234 // Create backup file and save it to the backup location. 235 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, 236 backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2); 237 $bc->execute_plan(); 238 $results = $bc->get_results(); 239 $file = $results['backup_destination']; 240 $fp = get_file_packer('application/vnd.moodle.backup'); 241 $filepath = $CFG->dataroot . '/temp/backup/test-restore-course'; 242 $file->extract_to_pathname($fp, $filepath); 243 $bc->destroy(); 244 245 // Now restore the course. 246 $rc = new restore_controller('test-restore-course', $course2->id, backup::INTERACTIVE_NO, 247 backup::MODE_GENERAL, 2, backup::TARGET_NEW_COURSE); 248 $rc->execute_precheck(); 249 $rc->execute_plan(); 250 251 // Get the created question category. 252 $restoredcategory = $DB->get_record('question_categories', array('contextid' => context_course::instance($course2->id)->id), 253 '*', MUST_EXIST); 254 255 // Check that there are two questions in the restored to course's context. 256 $this->assertEquals(2, $DB->count_records('question', array('category' => $restoredcategory->id))); 257 258 $rc->destroy(); 259 } 260 261 /** 262 * This function tests the question_category_delete_safe function. 263 */ 264 public function test_question_category_delete_safe() { 265 global $DB; 266 $this->resetAfterTest(true); 267 $this->setAdminUser(); 268 269 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions(); 270 271 question_category_delete_safe($qcat); 272 273 // Verify category deleted. 274 $criteria = array('id' => $qcat->id); 275 $this->assertEquals(0, $DB->count_records('question_categories', $criteria)); 276 277 // Verify questions deleted or moved. 278 $criteria = array('category' => $qcat->id); 279 $this->assertEquals(0, $DB->count_records('question', $criteria)); 280 281 // Verify question not deleted. 282 $criteria = array('id' => $questions[0]->id); 283 $this->assertEquals(1, $DB->count_records('question', $criteria)); 284 } 285 286 /** 287 * This function tests the question_delete_activity function. 288 * 289 * @param bool $feedback Whether to return feedback 290 * @dataProvider provider_feedback 291 */ 292 public function test_question_delete_activity($feedback) { 293 global $DB; 294 $this->resetAfterTest(true); 295 $this->setAdminUser(); 296 297 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions(); 298 299 $cm = get_coursemodule_from_instance('quiz', $quiz->id); 300 // Test that the feedback works. 301 if ($feedback) { 302 $this->expectOutputRegex('|'.get_string('unusedcategorydeleted', 'question').'|'); 303 } 304 question_delete_activity($cm, $feedback); 305 306 // Verify category deleted. 307 $criteria = array('id' => $qcat->id); 308 $this->assertEquals(0, $DB->count_records('question_categories', $criteria)); 309 310 // Verify questions deleted or moved. 311 $criteria = array('category' => $qcat->id); 312 $this->assertEquals(0, $DB->count_records('question', $criteria)); 313 } 314 315 /** 316 * This function tests the question_delete_context function. 317 */ 318 public function test_question_delete_context() { 319 global $DB; 320 $this->resetAfterTest(true); 321 $this->setAdminUser(); 322 323 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions(); 324 325 // Get the module context id. 326 $result = question_delete_context($qcat->contextid); 327 328 // Verify category deleted. 329 $criteria = array('id' => $qcat->id); 330 $this->assertEquals(0, $DB->count_records('question_categories', $criteria)); 331 332 // Verify questions deleted or moved. 333 $criteria = array('category' => $qcat->id); 334 $this->assertEquals(0, $DB->count_records('question', $criteria)); 335 336 // Test that the feedback works. 337 $expected[] = array($qcat->name, get_string('unusedcategorydeleted', 'question')); 338 $this->assertEquals($expected, $result); 339 } 340 341 /** 342 * This function tests the question_delete_course function. 343 * 344 * @param bool $feedback Whether to return feedback 345 * @dataProvider provider_feedback 346 */ 347 public function test_question_delete_course($feedback) { 348 global $DB; 349 $this->resetAfterTest(true); 350 $this->setAdminUser(); 351 352 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('course'); 353 354 // Test that the feedback works. 355 if ($feedback) { 356 $this->expectOutputRegex('|'.get_string('unusedcategorydeleted', 'question').'|'); 357 } 358 question_delete_course($course, $feedback); 359 360 // Verify category deleted. 361 $criteria = array('id' => $qcat->id); 362 $this->assertEquals(0, $DB->count_records('question_categories', $criteria)); 363 364 // Verify questions deleted or moved. 365 $criteria = array('category' => $qcat->id); 366 $this->assertEquals(0, $DB->count_records('question', $criteria)); 367 } 368 369 /** 370 * This function tests the question_delete_course_category function. 371 * 372 * @param bool $feedback Whether to return feedback 373 * @dataProvider provider_feedback 374 */ 375 public function test_question_delete_course_category($feedback) { 376 global $DB; 377 $this->resetAfterTest(true); 378 $this->setAdminUser(); 379 380 list($category, $course, $quiz, $qcat, $questions) = $this->setup_quiz_and_questions('category'); 381 382 // Test that the feedback works. 383 if ($feedback) { 384 $this->expectOutputRegex('|'.get_string('unusedcategorydeleted', 'question').'|'); 385 } 386 question_delete_course_category($category, 0, $feedback); 387 388 // Verify category deleted. 389 $criteria = array('id' => $qcat->id); 390 $this->assertEquals(0, $DB->count_records('question_categories', $criteria)); 391 392 // Verify questions deleted or moved. 393 $criteria = array('category' => $qcat->id); 394 $this->assertEquals(0, $DB->count_records('question', $criteria)); 395 } 396 397 public function test_question_remove_stale_questions_from_category() { 398 global $DB; 399 $this->resetAfterTest(true); 400 $dg = $this->getDataGenerator(); 401 $course = $dg->create_course(); 402 $quiz = $dg->create_module('quiz', ['course' => $course->id]); 403 404 $qgen = $dg->get_plugin_generator('core_question'); 405 $context = context_system::instance(); 406 407 $qcat1 = $qgen->create_question_category(['contextid' => $context->id]); 408 $q1a = $qgen->create_question('shortanswer', null, ['category' => $qcat1->id]); // Will be hidden. 409 $q1b = $qgen->create_question('random', null, ['category' => $qcat1->id]); // Will not be used. 410 $DB->set_field('question', 'hidden', 1, ['id' => $q1a->id]); 411 412 $qcat2 = $qgen->create_question_category(['contextid' => $context->id]); 413 $q2a = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden. 414 $q2b = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden but used. 415 $q2c = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will not be used. 416 $q2d = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will be used. 417 $DB->set_field('question', 'hidden', 1, ['id' => $q2a->id]); 418 $DB->set_field('question', 'hidden', 1, ['id' => $q2b->id]); 419 quiz_add_quiz_question($q2b->id, $quiz); 420 quiz_add_quiz_question($q2d->id, $quiz); 421 422 $this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id])); 423 $this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id])); 424 425 // Non-existing category, nothing will happen. 426 question_remove_stale_questions_from_category(0); 427 $this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id])); 428 $this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id])); 429 430 // First category, should be empty afterwards. 431 question_remove_stale_questions_from_category($qcat1->id); 432 $this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id])); 433 $this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id])); 434 $this->assertFalse($DB->record_exists('question', ['id' => $q1a->id])); 435 $this->assertFalse($DB->record_exists('question', ['id' => $q1b->id])); 436 437 // Second category, used questions should be left untouched. 438 question_remove_stale_questions_from_category($qcat2->id); 439 $this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id])); 440 $this->assertEquals(2, $DB->count_records('question', ['category' => $qcat2->id])); 441 $this->assertFalse($DB->record_exists('question', ['id' => $q2a->id])); 442 $this->assertTrue($DB->record_exists('question', ['id' => $q2b->id])); 443 $this->assertFalse($DB->record_exists('question', ['id' => $q2c->id])); 444 $this->assertTrue($DB->record_exists('question', ['id' => $q2d->id])); 445 } 446 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |