Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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