Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   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 question_bank;
  20  use question_hint;
  21  use question_test_recordset;
  22  use question_usage_by_activity;
  23  use testable_question_engine_unit_of_work;
  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 the {@link question_engine_unit_of_work} class.
  33   *
  34   * @package    core_question
  35   * @category   test
  36   * @copyright  2012 The Open University
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class unitofwork_test extends \data_loading_method_test_base {
  40      /** @var question_usage_by_activity the test question usage. */
  41      protected $quba;
  42  
  43      /** @var int the slot number of the one qa in the test usage.*/
  44      protected $slot;
  45  
  46      /** @var testable_question_engine_unit_of_work the unit of work we are testing. */
  47      protected $observer;
  48  
  49      protected function setUp(): void {
  50          // Create a usage in an initial state, with one shortanswer question added,
  51          // and attempted in interactive mode submitted responses 'toad' then 'frog'.
  52          // Then set it to use a new unit of work for any subsequent changes.
  53          // Create a short answer question.
  54          $question = \test_question_maker::make_question('shortanswer');
  55          $question->hints = array(
  56              new question_hint(0, 'This is the first hint.', FORMAT_HTML),
  57              new question_hint(0, 'This is the second hint.', FORMAT_HTML),
  58          );
  59          $question->id = -1;
  60          question_bank::start_unit_test();
  61          question_bank::load_test_question_data($question);
  62  
  63          $this->setup_initial_test_state($this->get_test_data());
  64       }
  65  
  66      public function tearDown(): void {
  67          question_bank::end_unit_test();
  68      }
  69  
  70      protected function setup_initial_test_state($testdata) {
  71          $records = new question_test_recordset($testdata);
  72  
  73          $this->quba = question_usage_by_activity::load_from_records($records, 1);
  74  
  75          $this->slot = 1;
  76          $this->observer = new testable_question_engine_unit_of_work($this->quba);
  77          $this->quba->set_observer($this->observer);
  78      }
  79  
  80      protected function get_test_data() {
  81          return array(
  82          array('qubaid', 'contextid', 'component', 'preferredbehaviour',
  83                                                  'questionattemptid', 'contextid', 'questionusageid', 'slot',
  84                                                                 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
  85                                                                                                                'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
  86                                                                                                                                       'attemptstepid', 'sequencenumber', 'state', 'fraction',
  87                                                                                                                                                                       'timecreated', 'userid', 'name', 'value'),
  88          array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo',             null, 1256233700, 1, '-_triesleft', 3),
  89          array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo',             null, 1256233720, 1, 'answer',     'toad'),
  90          array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo',             null, 1256233720, 1, '-submit',     1),
  91          array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo',             null, 1256233720, 1, '-_triesleft', 1),
  92          array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 3, 2, 'todo',             null, 1256233740, 1, '-tryagain',   1),
  93          array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 0.6666667, 1256233790, 1, 'answer',     'frog'),
  94          array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 0.6666667, 1256233790, 1, '-submit',     1),
  95          );
  96      }
  97  
  98      public function test_initial_state() {
  99          $this->assertFalse($this->observer->get_modified());
 100          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 101          $this->assertEquals(0, count($this->observer->get_attempts_modified()));
 102          $this->assertEquals(0, count($this->observer->get_steps_added()));
 103          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 104          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 105          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 106          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 107      }
 108  
 109      public function test_update_usage() {
 110  
 111          $this->quba->set_preferred_behaviour('deferredfeedback');
 112  
 113          $this->assertTrue($this->observer->get_modified());
 114      }
 115  
 116      public function test_add_question() {
 117  
 118          $slot = $this->quba->add_question(\test_question_maker::make_question('truefalse'));
 119  
 120          $newattempts = $this->observer->get_attempts_added();
 121          $this->assertEquals(1, count($newattempts));
 122          $this->assertTrue($this->quba->get_question_attempt($slot) === reset($newattempts));
 123          $this->assertSame($slot, key($newattempts));
 124  
 125          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 126          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 127      }
 128  
 129      public function test_add_and_start_question() {
 130  
 131          $slot = $this->quba->add_question(\test_question_maker::make_question('truefalse'));
 132                  $this->quba->start_question($slot);
 133  
 134          // The point here is that, although we have added a step, it is not listed
 135          // separately becuase it is part of a newly added attempt, and all steps
 136          // for a newly added attempt are automatically added to the DB, so it does
 137          // not need to be tracked separately.
 138          $newattempts = $this->observer->get_attempts_added();
 139          $this->assertEquals(1, count($newattempts));
 140          $this->assertTrue($this->quba->get_question_attempt($slot) === reset($newattempts));
 141          $this->assertSame($slot, key($newattempts));
 142          $this->assertEquals(0, count($this->observer->get_steps_added()));
 143  
 144          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 145          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 146      }
 147  
 148      public function test_process_action() {
 149  
 150          $this->quba->manual_grade($this->slot, 'Actually, that is not quite right', 0.5, FORMAT_HTML);
 151  
 152          // Here, however, were we are adding a step to an existing qa, we do need to track that.
 153          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 154  
 155          $updatedattempts = $this->observer->get_attempts_modified();
 156          $this->assertEquals(1, count($updatedattempts));
 157  
 158          $updatedattempt = reset($updatedattempts);
 159          $this->assertTrue($this->quba->get_question_attempt($this->slot) === $updatedattempt);
 160          $this->assertSame($this->slot, key($updatedattempts));
 161  
 162          $newsteps = $this->observer->get_steps_added();
 163          $this->assertEquals(1, count($newsteps));
 164  
 165          list($newstep, $qaid, $seq) = reset($newsteps);
 166          $this->assertSame($this->quba->get_question_attempt($this->slot)->get_last_step(), $newstep);
 167  
 168          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 169          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 170      }
 171  
 172      public function test_regrade_same_steps() {
 173  
 174          // Change the question in a minor way and regrade.
 175          $this->quba->get_question($this->slot, false)->answers[14]->fraction = 0.5;
 176          $this->quba->regrade_all_questions();
 177  
 178          // Here, the qa, and all the steps, should be marked as updated.
 179          // Here, however, were we are adding a step to an existing qa, we do need to track that.
 180          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 181          $this->assertEquals(0, count($this->observer->get_steps_added()));
 182          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 183  
 184          $updatedattempts = $this->observer->get_attempts_modified();
 185          $this->assertEquals(1, count($updatedattempts));
 186  
 187          $updatedattempt = reset($updatedattempts);
 188          $this->assertTrue($this->quba->get_question_attempt($this->slot) === $updatedattempt);
 189  
 190          $updatedsteps = $this->observer->get_steps_modified();
 191          $this->assertEquals($updatedattempt->get_num_steps(), count($updatedsteps));
 192  
 193          foreach ($updatedattempt->get_step_iterator() as $seq => $step) {
 194              $this->assertSame(array($step, $updatedattempt->get_database_id(), $seq),
 195                      $updatedsteps[$seq]);
 196          }
 197  
 198          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 199          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 200      }
 201  
 202      public function test_regrade_losing_steps() {
 203  
 204          // Change the question so that 'toad' is also right, and regrade. This
 205          // will mean that the try again, and second try states are no longer
 206          // needed, so they should be dropped.
 207          $this->quba->get_question($this->slot, false)->answers[14]->fraction = 1;
 208          $this->quba->regrade_all_questions();
 209  
 210          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 211          $this->assertEquals(0, count($this->observer->get_steps_added()));
 212  
 213          $updatedattempts = $this->observer->get_attempts_modified();
 214          $this->assertEquals(1, count($updatedattempts));
 215  
 216          $updatedattempt = reset($updatedattempts);
 217          $this->assertTrue($this->quba->get_question_attempt($this->slot) === $updatedattempt);
 218  
 219          $updatedsteps = $this->observer->get_steps_modified();
 220          $this->assertEquals($updatedattempt->get_num_steps(), count($updatedsteps));
 221  
 222          foreach ($updatedattempt->get_step_iterator() as $seq => $step) {
 223              $this->assertSame(array($step, $updatedattempt->get_database_id(), $seq),
 224                      $updatedsteps[$seq]);
 225          }
 226  
 227          $deletedsteps = $this->observer->get_steps_deleted();
 228          $this->assertEquals(2, count($deletedsteps));
 229  
 230          $firstdeletedstep = reset($deletedsteps);
 231          $this->assertEquals(array('-tryagain' => 1), $firstdeletedstep->get_all_data());
 232  
 233          $seconddeletedstep = end($deletedsteps);
 234          $this->assertEquals(array('answer' => 'frog', '-submit' => 1),
 235                  $seconddeletedstep->get_all_data());
 236  
 237          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 238          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 239      }
 240  
 241      public function test_tricky_regrade() {
 242  
 243          // The tricky thing here is that we take a half-complete question-attempt,
 244          // and then as one transaction, we submit some more responses, and then
 245          // change the question attempt as in test_regrade_losing_steps, and regrade
 246          // before the steps are even written to the database the first time.
 247          $somedata = $this->get_test_data();
 248          $somedata = array_slice($somedata, 0, 5);
 249          $this->setup_initial_test_state($somedata);
 250  
 251          $this->quba->process_action($this->slot, array('-tryagain' => 1));
 252          $this->quba->process_action($this->slot, array('answer' => 'frog', '-submit' => 1));
 253          $this->quba->finish_all_questions();
 254  
 255          $this->quba->get_question($this->slot, false)->answers[14]->fraction = 1;
 256          $this->quba->regrade_all_questions();
 257  
 258          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 259  
 260          $updatedattempts = $this->observer->get_attempts_modified();
 261          $this->assertEquals(1, count($updatedattempts));
 262  
 263          $updatedattempt = reset($updatedattempts);
 264          $this->assertTrue($this->quba->get_question_attempt($this->slot) === $updatedattempt);
 265  
 266          $this->assertEquals(0, count($this->observer->get_steps_added()));
 267  
 268          $updatedsteps = $this->observer->get_steps_modified();
 269          $this->assertEquals($updatedattempt->get_num_steps(), count($updatedsteps));
 270  
 271          foreach ($updatedattempt->get_step_iterator() as $seq => $step) {
 272              $this->assertSame(array($step, $updatedattempt->get_database_id(), $seq),
 273                      $updatedsteps[$seq]);
 274          }
 275  
 276          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 277  
 278          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 279          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 280      }
 281  
 282      public function test_move_question() {
 283  
 284          $q = \test_question_maker::make_question('truefalse');
 285          $newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
 286          $this->quba->start_question($this->slot);
 287  
 288          $addedattempts = $this->observer->get_attempts_added();
 289          $this->assertEquals(1, count($addedattempts));
 290          $addedattempt = reset($addedattempts);
 291          $this->assertSame($this->quba->get_question_attempt($this->slot), $addedattempt);
 292  
 293          $updatedattempts = $this->observer->get_attempts_modified();
 294          $this->assertEquals(1, count($updatedattempts));
 295          $updatedattempt = reset($updatedattempts);
 296          $this->assertSame($this->quba->get_question_attempt($newslot), $updatedattempt);
 297  
 298          $this->assertEquals(0, count($this->observer->get_steps_added()));
 299          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 300          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 301  
 302          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 303          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 304      }
 305  
 306      public function test_move_question_then_modify() {
 307  
 308          $q = \test_question_maker::make_question('truefalse');
 309          $newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
 310          $this->quba->start_question($this->slot);
 311          $this->quba->process_action($this->slot, array('answer' => 'frog', '-submit' => 1));
 312          $this->quba->manual_grade($newslot, 'Test', 0.5, FORMAT_HTML);
 313  
 314          $addedattempts = $this->observer->get_attempts_added();
 315          $this->assertEquals(1, count($addedattempts));
 316          $addedattempt = reset($addedattempts);
 317          $this->assertSame($this->quba->get_question_attempt($this->slot), $addedattempt);
 318  
 319          $updatedattempts = $this->observer->get_attempts_modified();
 320          $this->assertEquals(1, count($updatedattempts));
 321          $updatedattempt = reset($updatedattempts);
 322          $this->assertSame($this->quba->get_question_attempt($newslot), $updatedattempt);
 323  
 324          $newsteps = $this->observer->get_steps_added();
 325          $this->assertEquals(1, count($newsteps));
 326          list($newstep, $qaid, $seq) = reset($newsteps);
 327          $this->assertSame($this->quba->get_question_attempt($newslot)->get_last_step(), $newstep);
 328  
 329          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 330          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 331  
 332          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 333          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 334      }
 335  
 336      public function test_move_question_then_move_again() {
 337          $originalqa = $this->quba->get_question_attempt($this->slot);
 338  
 339          $q1 = \test_question_maker::make_question('truefalse');
 340          $newslot = $this->quba->add_question_in_place_of_other($this->slot, $q1);
 341          $this->quba->start_question($this->slot);
 342  
 343          $q2 = \test_question_maker::make_question('truefalse');
 344          $newslot2 = $this->quba->add_question_in_place_of_other($newslot, $q2);
 345          $this->quba->start_question($newslot);
 346  
 347          $addedattempts = $this->observer->get_attempts_added();
 348          $this->assertEquals(2, count($addedattempts));
 349  
 350          $updatedattempts = $this->observer->get_attempts_modified();
 351          $this->assertEquals(1, count($updatedattempts));
 352          $updatedattempt = reset($updatedattempts);
 353          $this->assertSame($originalqa, $updatedattempt);
 354  
 355          $this->assertEquals(0, count($this->observer->get_steps_added()));
 356          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 357          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 358  
 359          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 360          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 361      }
 362  
 363      public function test_set_max_mark() {
 364          $this->quba->set_max_mark($this->slot, 6.0);
 365          $this->assertEqualsWithDelta(4.0, $this->quba->get_total_mark(), 0.0000005);
 366  
 367          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 368  
 369          $updatedattempts = $this->observer->get_attempts_modified();
 370          $this->assertEquals(1, count($updatedattempts));
 371          $updatedattempt = reset($updatedattempts);
 372          $this->assertSame($this->quba->get_question_attempt($this->slot), $updatedattempt);
 373  
 374          $this->assertEquals(0, count($this->observer->get_steps_added()));
 375          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 376          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 377  
 378          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 379          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 380      }
 381  
 382      public function test_set_question_attempt_metadata() {
 383          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
 384          $this->assertEquals('a value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
 385  
 386          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 387          $this->assertEquals(0, count($this->observer->get_attempts_modified()));
 388  
 389          $this->assertEquals(0, count($this->observer->get_steps_added()));
 390          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 391          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 392  
 393          $this->assertEquals(array($this->slot => array('metathingy' => $this->quba->get_question_attempt($this->slot))),
 394                  $this->observer->get_metadata_added());
 395          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 396      }
 397  
 398      public function test_set_question_attempt_metadata_then_change() {
 399          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
 400          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'different value');
 401          $this->assertEquals('different value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
 402  
 403          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 404          $this->assertEquals(0, count($this->observer->get_attempts_modified()));
 405  
 406          $this->assertEquals(0, count($this->observer->get_steps_added()));
 407          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 408          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 409  
 410          $this->assertEquals(array($this->slot => array('metathingy' => $this->quba->get_question_attempt($this->slot))),
 411                  $this->observer->get_metadata_added());
 412          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 413      }
 414  
 415      public function test_set_metadata_previously_set_but_dont_actually_change() {
 416          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
 417          $this->observer = new testable_question_engine_unit_of_work($this->quba);
 418          $this->quba->set_observer($this->observer);
 419          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
 420          $this->assertEquals('a value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
 421  
 422          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 423          $this->assertEquals(0, count($this->observer->get_attempts_modified()));
 424  
 425          $this->assertEquals(0, count($this->observer->get_steps_added()));
 426          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 427          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 428  
 429          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 430          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 431      }
 432  
 433      public function test_set_metadata_previously_set() {
 434          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
 435          $this->observer = new testable_question_engine_unit_of_work($this->quba);
 436          $this->quba->set_observer($this->observer);
 437          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'different value');
 438          $this->assertEquals('different value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
 439  
 440          $this->assertEquals(0, count($this->observer->get_attempts_added()));
 441          $this->assertEquals(0, count($this->observer->get_attempts_modified()));
 442  
 443          $this->assertEquals(0, count($this->observer->get_steps_added()));
 444          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 445          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 446  
 447          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 448          $this->assertEquals(array($this->slot => array('metathingy' => $this->quba->get_question_attempt($this->slot))),
 449                  $this->observer->get_metadata_modified());
 450      }
 451  
 452      public function test_set_metadata_in_new_question() {
 453          $newslot = $this->quba->add_question(\test_question_maker::make_question('truefalse'));
 454          $this->quba->start_question($newslot);
 455          $this->quba->set_question_attempt_metadata($newslot, 'metathingy', 'a value');
 456          $this->assertEquals('a value', $this->quba->get_question_attempt_metadata($newslot, 'metathingy'));
 457  
 458          $this->assertEquals(array($newslot => $this->quba->get_question_attempt($newslot)),
 459                  $this->observer->get_attempts_added());
 460          $this->assertEquals(0, count($this->observer->get_attempts_modified()));
 461  
 462          $this->assertEquals(0, count($this->observer->get_steps_added()));
 463          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 464          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 465  
 466          $this->assertEquals(0, count($this->observer->get_metadata_added()));
 467          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 468      }
 469  
 470      public function test_set_metadata_then_move() {
 471          $this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
 472          $q = \test_question_maker::make_question('truefalse');
 473          $newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
 474          $this->quba->start_question($this->slot);
 475          $this->assertEquals('a value', $this->quba->get_question_attempt_metadata($newslot, 'metathingy'));
 476  
 477          $this->assertEquals(array($this->slot => $this->quba->get_question_attempt($this->slot)),
 478                  $this->observer->get_attempts_added());
 479          $this->assertEquals(array($newslot => $this->quba->get_question_attempt($newslot)),
 480                  $this->observer->get_attempts_modified());
 481  
 482          $this->assertEquals(0, count($this->observer->get_steps_added()));
 483          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 484          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 485  
 486          $this->assertEquals(array($newslot => array('metathingy' => $this->quba->get_question_attempt($newslot))),
 487                  $this->observer->get_metadata_added());
 488          $this->assertEquals(0, count($this->observer->get_metadata_modified()));
 489      }
 490  
 491      public function test_move_then_set_metadata() {
 492          $q = \test_question_maker::make_question('truefalse');
 493          $newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
 494          $this->quba->start_question($this->slot);
 495          $this->quba->set_question_attempt_metadata($newslot, 'metathingy', 'a value');
 496          $this->assertEquals('a value', $this->quba->get_question_attempt_metadata($newslot, 'metathingy'));
 497  
 498          $this->assertEquals(array($this->slot => $this->quba->get_question_attempt($this->slot)),
 499                  $this->observer->get_attempts_added());
 500          $this->assertEquals(array($newslot => $this->quba->get_question_attempt($newslot)),
 501                  $this->observer->get_attempts_modified());
 502  
 503          $this->assertEquals(0, count($this->observer->get_steps_added()));
 504          $this->assertEquals(0, count($this->observer->get_steps_modified()));
 505          $this->assertEquals(0, count($this->observer->get_steps_deleted()));
 506  
 507          $this->assertEquals(array($newslot => array('metathingy' => $this->quba->get_question_attempt($newslot))),
 508                  $this->observer->get_metadata_added());
 509      }
 510  }