Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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   */
  44  class datalib_test extends \qbehaviour_walkthrough_test_base {
  45  
  46      /**
  47       * We create two usages, each with two questions, a short-answer marked
  48       * out of 5, and and essay marked out of 10. We just start these attempts.
  49       *
  50       * Then we change the max mark for the short-answer question in one of the
  51       * usages to 20, using a qubaid_list, and verify.
  52       *
  53       * Then we change the max mark for the essay question in the other
  54       * usage to 2, using a qubaid_join, and verify.
  55       */
  56      public function test_set_max_mark_in_attempts() {
  57  
  58          // Set up some things the tests will need.
  59          $this->resetAfterTest();
  60          $dm = new question_engine_data_mapper();
  61  
  62          // Create the questions.
  63          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
  64          $cat = $generator->create_question_category();
  65          $sa = $generator->create_question('shortanswer', null,
  66                  array('category' => $cat->id));
  67          $essay = $generator->create_question('essay', null,
  68                  array('category' => $cat->id));
  69  
  70          // Create the first usage.
  71          $q = question_bank::load_question($sa->id);
  72          $this->start_attempt_at_question($q, 'interactive', 5);
  73  
  74          $q = question_bank::load_question($essay->id);
  75          $this->start_attempt_at_question($q, 'interactive', 10);
  76  
  77          $this->finish();
  78          $this->save_quba();
  79          $usage1id = $this->quba->get_id();
  80  
  81          // Create the second usage.
  82          $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
  83                  \context_system::instance());
  84  
  85          $q = question_bank::load_question($sa->id);
  86          $this->start_attempt_at_question($q, 'interactive', 5);
  87          $this->process_submission(array('answer' => 'fish'));
  88  
  89          $q = question_bank::load_question($essay->id);
  90          $this->start_attempt_at_question($q, 'interactive', 10);
  91  
  92          $this->finish();
  93          $this->save_quba();
  94          $usage2id = $this->quba->get_id();
  95  
  96          // Test set_max_mark_in_attempts with a qubaid_list.
  97          $usagestoupdate = new qubaid_list(array($usage1id));
  98          $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0);
  99          $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
 100          $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
 101          $this->assertEquals(20, $quba1->get_question_max_mark(1));
 102          $this->assertEquals(10, $quba1->get_question_max_mark(2));
 103          $this->assertEquals( 5, $quba2->get_question_max_mark(1));
 104          $this->assertEquals(10, $quba2->get_question_max_mark(2));
 105  
 106          // Test set_max_mark_in_attempts with a qubaid_join.
 107          $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id',
 108                  'qu.id = :usageid', array('usageid' => $usage2id));
 109          $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
 110          $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
 111          $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
 112          $this->assertEquals(20, $quba1->get_question_max_mark(1));
 113          $this->assertEquals(10, $quba1->get_question_max_mark(2));
 114          $this->assertEquals( 5, $quba2->get_question_max_mark(1));
 115          $this->assertEquals( 2, $quba2->get_question_max_mark(2));
 116  
 117          // Test the nothing to do case.
 118          $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id',
 119                  'qu.id = :usageid', array('usageid' => -1));
 120          $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
 121          $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
 122          $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
 123          $this->assertEquals(20, $quba1->get_question_max_mark(1));
 124          $this->assertEquals(10, $quba1->get_question_max_mark(2));
 125          $this->assertEquals( 5, $quba2->get_question_max_mark(1));
 126          $this->assertEquals( 2, $quba2->get_question_max_mark(2));
 127      }
 128  
 129      public function test_load_used_variants() {
 130          $this->resetAfterTest();
 131          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 132  
 133          $cat = $generator->create_question_category();
 134          $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
 135          $questiondata2 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
 136          $questiondata3 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
 137  
 138          $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
 139          $quba->set_preferred_behaviour('deferredfeedback');
 140          $question1 = question_bank::load_question($questiondata1->id);
 141          $question3 = question_bank::load_question($questiondata3->id);
 142          $quba->add_question($question1);
 143          $quba->add_question($question1);
 144          $quba->add_question($question3);
 145          $quba->start_all_questions();
 146          question_engine::save_questions_usage_by_activity($quba);
 147  
 148          $this->assertEquals(array(
 149                      $questiondata1->id => array(1 => 2),
 150                      $questiondata2->id => array(),
 151                      $questiondata3->id => array(1 => 1),
 152                  ), question_engine::load_used_variants(
 153                      array($questiondata1->id, $questiondata2->id, $questiondata3->id),
 154                      new qubaid_list(array($quba->get_id()))));
 155      }
 156  
 157      public function test_repeated_usage_saving_new_usage() {
 158          global $DB;
 159  
 160          $this->resetAfterTest();
 161  
 162          $initialqurows = $DB->count_records('question_usages');
 163          $initialqarows = $DB->count_records('question_attempts');
 164          $initialqasrows = $DB->count_records('question_attempt_steps');
 165  
 166          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 167          $cat = $generator->create_question_category();
 168          $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
 169  
 170          $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
 171          $quba->set_preferred_behaviour('deferredfeedback');
 172          $quba->add_question(question_bank::load_question($questiondata1->id));
 173          $quba->start_all_questions();
 174          question_engine::save_questions_usage_by_activity($quba);
 175  
 176          // Check one usage, question_attempts and step added.
 177          $firstid = $quba->get_id();
 178          $this->assertEquals(1, $DB->count_records('question_usages') - $initialqurows);
 179          $this->assertEquals(1, $DB->count_records('question_attempts') - $initialqarows);
 180          $this->assertEquals(1, $DB->count_records('question_attempt_steps') - $initialqasrows);
 181  
 182          $quba->finish_all_questions();
 183          question_engine::save_questions_usage_by_activity($quba);
 184  
 185          // Check usage id not changed.
 186          $this->assertEquals($firstid, $quba->get_id());
 187  
 188          // Check still one usage, question_attempts, but now two steps.
 189          $this->assertEquals(1, $DB->count_records('question_usages') - $initialqurows);
 190          $this->assertEquals(1, $DB->count_records('question_attempts') - $initialqarows);
 191          $this->assertEquals(2, $DB->count_records('question_attempt_steps') - $initialqasrows);
 192      }
 193  
 194      public function test_repeated_usage_saving_existing_usage() {
 195          global $DB;
 196  
 197          $this->resetAfterTest();
 198  
 199          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 200          $cat = $generator->create_question_category();
 201          $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
 202  
 203          $initquba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
 204          $initquba->set_preferred_behaviour('deferredfeedback');
 205          $slot = $initquba->add_question(question_bank::load_question($questiondata1->id));
 206          $initquba->start_all_questions();
 207          question_engine::save_questions_usage_by_activity($initquba);
 208  
 209          $quba = question_engine::load_questions_usage_by_activity($initquba->get_id());
 210  
 211          $initialqurows = $DB->count_records('question_usages');
 212          $initialqarows = $DB->count_records('question_attempts');
 213          $initialqasrows = $DB->count_records('question_attempt_steps');
 214  
 215          $quba->process_all_actions(time(), $quba->prepare_simulated_post_data(
 216                  [$slot => ['answer' => 'Frog']]));
 217          question_engine::save_questions_usage_by_activity($quba);
 218  
 219          // Check one usage, question_attempts and step added.
 220          $this->assertEquals(0, $DB->count_records('question_usages') - $initialqurows);
 221          $this->assertEquals(0, $DB->count_records('question_attempts') - $initialqarows);
 222          $this->assertEquals(1, $DB->count_records('question_attempt_steps') - $initialqasrows);
 223  
 224          $quba->finish_all_questions();
 225          question_engine::save_questions_usage_by_activity($quba);
 226  
 227          // Check still one usage, question_attempts, but now two steps.
 228          $this->assertEquals(0, $DB->count_records('question_usages') - $initialqurows);
 229          $this->assertEquals(0, $DB->count_records('question_attempts') - $initialqarows);
 230          $this->assertEquals(2, $DB->count_records('question_attempt_steps') - $initialqasrows);
 231      }
 232  
 233      /**
 234       * Test that database operations on an empty usage work without errors.
 235       */
 236      public function test_save_and_load_an_empty_usage() {
 237          $this->resetAfterTest();
 238  
 239          // Create a new usage.
 240          $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
 241          $quba->set_preferred_behaviour('deferredfeedback');
 242  
 243          // Save it.
 244          question_engine::save_questions_usage_by_activity($quba);
 245  
 246          // Reload it.
 247          $reloadedquba = question_engine::load_questions_usage_by_activity($quba->get_id());
 248          $this->assertCount(0, $quba->get_slots());
 249  
 250          // Delete it.
 251          question_engine::delete_questions_usage_by_activity($quba->get_id());
 252      }
 253  }