Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [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 core_question; 18 19 use qubaid_join; 20 use qubaid_list; 21 use question_bank; 22 use question_engine; 23 use question_engine_data_mapper; 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 global $CFG; 28 require_once (__DIR__ . '/../lib.php'); 29 require_once (__DIR__ . '/helpers.php'); 30 31 /** 32 * Unit tests for parts of {@link question_engine_data_mapper}. 33 * 34 * Note that many of the methods used when attempting questions, like 35 * load_questions_usage_by_activity, insert_question_*, delete_steps are 36 * tested elsewhere, e.g. by {@link question_usage_autosave_test}. We do not 37 * re-test them here. 38 * 39 * @package core_question 40 * @category test 41 * @copyright 2014 The Open University 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 * @covers \question_engine_data_mapper 44 */ 45 class datalib_test extends \qbehaviour_walkthrough_test_base { 46 47 /** 48 * We create two usages, each with two questions, a short-answer marked 49 * out of 5, and and essay marked out of 10. We just start these attempts. 50 * 51 * Then we change the max mark for the short-answer question in one of the 52 * usages to 20, using a qubaid_list, and verify. 53 * 54 * Then we change the max mark for the essay question in the other 55 * usage to 2, using a qubaid_join, and verify. 56 */ 57 public function test_set_max_mark_in_attempts() { 58 59 // Set up some things the tests will need. 60 $this->resetAfterTest(); 61 $dm = new question_engine_data_mapper(); 62 63 // Create the questions. 64 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 65 $cat = $generator->create_question_category(); 66 $sa = $generator->create_question('shortanswer', null, 67 array('category' => $cat->id)); 68 $essay = $generator->create_question('essay', null, 69 array('category' => $cat->id)); 70 71 // Create the first usage. 72 $q = question_bank::load_question($sa->id); 73 $this->start_attempt_at_question($q, 'interactive', 5); 74 75 $q = question_bank::load_question($essay->id); 76 $this->start_attempt_at_question($q, 'interactive', 10); 77 78 $this->finish(); 79 $this->save_quba(); 80 $usage1id = $this->quba->get_id(); 81 82 // Create the second usage. 83 $this->quba = question_engine::make_questions_usage_by_activity('unit_test', 84 \context_system::instance()); 85 86 $q = question_bank::load_question($sa->id); 87 $this->start_attempt_at_question($q, 'interactive', 5); 88 $this->process_submission(array('answer' => 'fish')); 89 90 $q = question_bank::load_question($essay->id); 91 $this->start_attempt_at_question($q, 'interactive', 10); 92 93 $this->finish(); 94 $this->save_quba(); 95 $usage2id = $this->quba->get_id(); 96 97 // Test set_max_mark_in_attempts with a qubaid_list. 98 $usagestoupdate = new qubaid_list(array($usage1id)); 99 $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0); 100 $quba1 = question_engine::load_questions_usage_by_activity($usage1id); 101 $quba2 = question_engine::load_questions_usage_by_activity($usage2id); 102 $this->assertEquals(20, $quba1->get_question_max_mark(1)); 103 $this->assertEquals(10, $quba1->get_question_max_mark(2)); 104 $this->assertEquals( 5, $quba2->get_question_max_mark(1)); 105 $this->assertEquals(10, $quba2->get_question_max_mark(2)); 106 107 // Test set_max_mark_in_attempts with a qubaid_join. 108 $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 109 'qu.id = :usageid', array('usageid' => $usage2id)); 110 $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0); 111 $quba1 = question_engine::load_questions_usage_by_activity($usage1id); 112 $quba2 = question_engine::load_questions_usage_by_activity($usage2id); 113 $this->assertEquals(20, $quba1->get_question_max_mark(1)); 114 $this->assertEquals(10, $quba1->get_question_max_mark(2)); 115 $this->assertEquals( 5, $quba2->get_question_max_mark(1)); 116 $this->assertEquals( 2, $quba2->get_question_max_mark(2)); 117 118 // Test the nothing to do case. 119 $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', 120 'qu.id = :usageid', array('usageid' => -1)); 121 $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0); 122 $quba1 = question_engine::load_questions_usage_by_activity($usage1id); 123 $quba2 = question_engine::load_questions_usage_by_activity($usage2id); 124 $this->assertEquals(20, $quba1->get_question_max_mark(1)); 125 $this->assertEquals(10, $quba1->get_question_max_mark(2)); 126 $this->assertEquals( 5, $quba2->get_question_max_mark(1)); 127 $this->assertEquals( 2, $quba2->get_question_max_mark(2)); 128 } 129 130 public function test_load_used_variants() { 131 $this->resetAfterTest(); 132 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 133 134 $cat = $generator->create_question_category(); 135 $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id)); 136 $questiondata2 = $generator->create_question('shortanswer', null, array('category' => $cat->id)); 137 $questiondata3 = $generator->create_question('shortanswer', null, array('category' => $cat->id)); 138 139 $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance()); 140 $quba->set_preferred_behaviour('deferredfeedback'); 141 $question1 = question_bank::load_question($questiondata1->id); 142 $question3 = question_bank::load_question($questiondata3->id); 143 $quba->add_question($question1); 144 $quba->add_question($question1); 145 $quba->add_question($question3); 146 $quba->start_all_questions(); 147 question_engine::save_questions_usage_by_activity($quba); 148 149 $this->assertEquals(array( 150 $questiondata1->id => array(1 => 2), 151 $questiondata2->id => array(), 152 $questiondata3->id => array(1 => 1), 153 ), question_engine::load_used_variants( 154 array($questiondata1->id, $questiondata2->id, $questiondata3->id), 155 new qubaid_list(array($quba->get_id())))); 156 } 157 158 public function test_repeated_usage_saving_new_usage() { 159 global $DB; 160 161 $this->resetAfterTest(); 162 163 $initialqurows = $DB->count_records('question_usages'); 164 $initialqarows = $DB->count_records('question_attempts'); 165 $initialqasrows = $DB->count_records('question_attempt_steps'); 166 167 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 168 $cat = $generator->create_question_category(); 169 $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id)); 170 171 $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance()); 172 $quba->set_preferred_behaviour('deferredfeedback'); 173 $quba->add_question(question_bank::load_question($questiondata1->id)); 174 $quba->start_all_questions(); 175 question_engine::save_questions_usage_by_activity($quba); 176 177 // Check one usage, question_attempts and step added. 178 $firstid = $quba->get_id(); 179 $this->assertEquals(1, $DB->count_records('question_usages') - $initialqurows); 180 $this->assertEquals(1, $DB->count_records('question_attempts') - $initialqarows); 181 $this->assertEquals(1, $DB->count_records('question_attempt_steps') - $initialqasrows); 182 183 $quba->finish_all_questions(); 184 question_engine::save_questions_usage_by_activity($quba); 185 186 // Check usage id not changed. 187 $this->assertEquals($firstid, $quba->get_id()); 188 189 // Check still one usage, question_attempts, but now two steps. 190 $this->assertEquals(1, $DB->count_records('question_usages') - $initialqurows); 191 $this->assertEquals(1, $DB->count_records('question_attempts') - $initialqarows); 192 $this->assertEquals(2, $DB->count_records('question_attempt_steps') - $initialqasrows); 193 } 194 195 public function test_repeated_usage_saving_existing_usage() { 196 global $DB; 197 198 $this->resetAfterTest(); 199 200 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 201 $cat = $generator->create_question_category(); 202 $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id)); 203 204 $initquba = question_engine::make_questions_usage_by_activity('test', \context_system::instance()); 205 $initquba->set_preferred_behaviour('deferredfeedback'); 206 $slot = $initquba->add_question(question_bank::load_question($questiondata1->id)); 207 $initquba->start_all_questions(); 208 question_engine::save_questions_usage_by_activity($initquba); 209 210 $quba = question_engine::load_questions_usage_by_activity($initquba->get_id()); 211 212 $initialqurows = $DB->count_records('question_usages'); 213 $initialqarows = $DB->count_records('question_attempts'); 214 $initialqasrows = $DB->count_records('question_attempt_steps'); 215 216 $quba->process_all_actions(time(), $quba->prepare_simulated_post_data( 217 [$slot => ['answer' => 'Frog']])); 218 question_engine::save_questions_usage_by_activity($quba); 219 220 // Check one usage, question_attempts and step added. 221 $this->assertEquals(0, $DB->count_records('question_usages') - $initialqurows); 222 $this->assertEquals(0, $DB->count_records('question_attempts') - $initialqarows); 223 $this->assertEquals(1, $DB->count_records('question_attempt_steps') - $initialqasrows); 224 225 $quba->finish_all_questions(); 226 question_engine::save_questions_usage_by_activity($quba); 227 228 // Check still one usage, question_attempts, but now two steps. 229 $this->assertEquals(0, $DB->count_records('question_usages') - $initialqurows); 230 $this->assertEquals(0, $DB->count_records('question_attempts') - $initialqarows); 231 $this->assertEquals(2, $DB->count_records('question_attempt_steps') - $initialqasrows); 232 } 233 234 /** 235 * Test that database operations on an empty usage work without errors. 236 */ 237 public function test_save_and_load_an_empty_usage() { 238 $this->resetAfterTest(); 239 240 // Create a new usage. 241 $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance()); 242 $quba->set_preferred_behaviour('deferredfeedback'); 243 244 // Save it. 245 question_engine::save_questions_usage_by_activity($quba); 246 247 // Reload it. 248 $reloadedquba = question_engine::load_questions_usage_by_activity($quba->get_id()); 249 $this->assertCount(0, $quba->get_slots()); 250 251 // Delete it. 252 question_engine::delete_questions_usage_by_activity($quba->get_id()); 253 } 254 255 public function test_cannot_save_a_step_with_a_missing_state(): void { 256 global $DB; 257 258 $this->resetAfterTest(); 259 260 // Create a question. 261 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 262 $cat = $generator->create_question_category(); 263 $questiondata = $generator->create_question('shortanswer', null, ['category' => $cat->id]); 264 265 // Create a usage. 266 $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance()); 267 $quba->set_preferred_behaviour('deferredfeedback'); 268 $slot = $quba->add_question(question_bank::load_question($questiondata->id)); 269 $quba->start_all_questions(); 270 271 // Add a step with a bad state. 272 $newstep = new \question_attempt_step(); 273 $newstep->set_state(null); 274 $addstepmethod = new \ReflectionMethod('question_attempt', 'add_step'); 275 $addstepmethod->setAccessible(true); 276 $addstepmethod->invoke($quba->get_question_attempt($slot), $newstep); 277 278 // Verify that trying to save this throws an exception. 279 $this->expectException(\dml_write_exception::class); 280 question_engine::save_questions_usage_by_activity($quba); 281 } 282 283 /** 284 * Test cases for {@see test_get_file_area_name()}. 285 * 286 * @return array test cases 287 */ 288 public function get_file_area_name_cases(): array { 289 return [ 290 'simple variable' => ['response_attachments', 'response_attachments'], 291 'behaviour variable' => ['response_5:answer', 'response_5answer'], 292 'variable with special character' => ['response_5:answer', 'response_5answer'], 293 'multiple underscores in different places' => ['response_weird____variable__name', 'response_weird_variable_name'], 294 ]; 295 } 296 297 /** 298 * Test get_file_area_name. 299 * 300 * @covers \question_file_saver::clean_file_area_name 301 * @dataProvider get_file_area_name_cases 302 * 303 * @param string $uncleanedfilearea 304 * @param string $expectedfilearea 305 */ 306 public function test_clean_file_area_name(string $uncleanedfilearea, string $expectedfilearea): void { 307 $this->assertEquals($expectedfilearea, \question_file_saver::clean_file_area_name($uncleanedfilearea)); 308 } 309 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body