You are here

questions_import.test in Quiz 6.6

File

includes/questions_import/questions_import.test
View source
<?php

/*
 * @file
 * Test suite for Questions Import module
 *
 */
class QuestionsImportTestCase extends DrupalWebTestCase {

  /*
   * The getInfo() method provides information about the test.
   * In order for the test to be run, the getInfo() method needs
   * to be implemented.
   */
  public static function getInfo() {
    return array(
      'name' => t('Question importing unit test'),
      'description' => t('Test importing of questions in various formats.'),
      'group' => t('Quiz'),
    );
  }

  /*
   * The getInfo() method provides information about the test.
   * In order for the test to be run, the getInfo() method needs
   * to be implemented.
   */
  function setUp() {
    parent::setUp('taxonomy', 'quiz', 'views', 'autoload', 'multichoice', 'quiz_directions', 'quiz_question', 'querypath', 'questions_import', 'short_answer', 'truefalse', 'long_answer', 'matching', 'questions_export');

    // Create and log in our test user. Should be cleaned up as I something
    // was wrong with permissions and I basically kept adding potentially
    // useful ones until it worked.
    $user = $this
      ->drupalCreateUser(array(
      'administer site configuration',
      'access administration pages',
      'administer quiz',
      'access quiz',
      'administer blocks',
      'import questions',
      'create quiz',
      'administer quiz configuration',
      'use PHP for block visibility',
      'administer blocks',
      'create multichoice',
      'edit any multichoice',
      'administer taxonomy',
      'allow multiple correct answers',
      'allow any number of answers',
      'export questions',
    ));
    $this
      ->drupalLogin($user);

    // create one quiz, which will be the default in the import form
    $quiz_settings = array();
    $quiz_settings['title'] = $this
      ->randomName(128);
    $quiz_settings['comment'] = $this
      ->randomName(256);
    $quiz_settings['type'] = 'quiz';
    $this
      ->drupalCreateNode($quiz_settings);
  }

  /**
   *
   * @param $quiz_nid_id
   * @return array of question fields: nid, vid, body, type
   */
  function getQuizQuestionList($quiz_nid) {
    $db_questions = array();

    // question nid, question prompt (body), type
    $sql = "SELECT nr.nid, nr.vid, nr.body, n.type FROM {node_revisions} nr\nJOIN {quiz_node_relationship} qnr ON qnr.child_nid = nr.nid\nJOIN {node} n ON n.nid = nr.nid\nWHERE parent_nid = %d\nORDER BY n.nid";

    // FIXME shouldn't this be by qnr.weight?  how are questions ordered in a quiz?
    $result = db_query($sql, $quiz_nid);
    while ($question_node = db_fetch_array($result)) {
      $db_questions[] = $question_node;
    }
    return $db_questions;
  }

  /**
   * Loads a quiz and checks that it is behaving properly
   *
   * @param $answers
   *   List of answer to each question
   * @param $quiz_nid
   *   Node the quiz is located at
   */
  function quizHelper($import_questions = array(), $quiz_nid = 1) {
    $db_questions = $this
      ->getQuizQuestionList($quiz_nid);
    $this
      ->assertEqual(count($import_questions), count($db_questions), "Checking right number of questions in database 2 (expected " . count($import_questions) . " got " . count($db_questions) . ")");
    if (count($db_questions) != count($import_questions)) {
      return;
    }
    foreach ($import_questions as $index => $import_q) {
      $db_q = $db_questions[$index];
      $this
        ->assertEqual($import_q->question, $db_q['body'], "Question prompts in database match generated import (expected '{$import_q->question}' but got '{$db_q['body']}')");
      $this
        ->assertEqual($import_q->type, $db_q['type'], "Question node types in database match generated import (expected '{$import_q->type}' but got '{$db_q['type']}')");
      $this
        ->answersHelper($import_q, $db_q);
    }
  }

  /**
   * Compares import question with database question, checks that they match
   * @param $import_q
   * @param $db_q has nid, vid, type and body
   * @return void
   */
  function answersHelper($import_q, $db_q) {
    $node_obj = (object) $db_q;
    switch ($import_q->type) {
      case 'multichoice':
        $mc = multichoice_load($node_obj);
        $this
          ->assertEqual(count($import_q->answers), count($mc->answers), "[multichoice] Matching number of options (expected " . count($import_q->answers) . ", got " . count($mc->answers) . ") " . print_r($mc, true) . print_r($db_q, true));
        foreach ($import_q->answers as $index => $import_answer) {
          $db_answer = $mc->answers[$index]['answer'];
          $this
            ->assertEqual($import_answer, $db_answer, "[multichoice] Matching option text (expected {$import_answer}, got {$db_answer})");
          if ($import_q->answer == $index) {
            $this
              ->assertEqual($mc->answers[$index]['is_correct'], 1, "[multichoice] Matching correct answer(s) (expected question answer to be correct)");
          }
          else {
            $this
              ->assertEqual($mc->answers[$index]['is_correct'], 0, "[multichoice] Matching incorrect answer(s) (expected question answer to be incorrect)");
          }
        }
        break;
      case 'true_false':
        $tf = quiz_question_load($node_obj);
        $correct = $tf->correct_answer == 0 ? 'false' : 'true';
        $this
          ->assertEqual($import_q->answer, $correct, "[true_false] Matching right answer (expected " . $import_q->answer . ", got " . $correct . ")");
        break;
      case 'short_answer':
        $sa = quiz_question_load($node_obj);
        $correct = $sa->correct_answer;
        $this
          ->assertEqual($import_q->answer, $correct, "[short_answer] Checking right answer (expected " . $import_q->answer . ", got " . $correct . ")" . print_r($sa, true));
        $this
          ->assertEqual($import_q->value, $sa->maximum_score, "[short_answer] Checking right score (expected " . $import_q->value . ", got " . $sa->maximum_score . ")");
        $this
          ->assertEqual($import_q->sat, $sa->correct_answer_evaluation, "[short_answer] Checking short answer evaluation type (expected " . $import_q->sat . " (i.e." . $import_q->shortanswertype . "), got " . $sa->correct_answer_evaluation . ")");
        break;
      case 'matching':
        $ma = quiz_question_load($node_obj);
        $ma = $ma['answer'];
        foreach ($import_q->matches as $index => $i_match) {
          $this
            ->assertEqual($i_match, $ma[$index]['question'], "[matching] Checking corresponding matches {$i_match}, {$ma[$index]['question']}");
          $this
            ->assertEqual($import_q->answers[$index], $ma[$index]['answer'], "[matching] Checking corresponding answers {$import_q->answers[$index]}, {$ma[$index]['answer']}");
        }
        break;
    }
  }
  function writeImport($input_type, $question) {
    $char = array(
      'A',
      'B',
      'C',
      'D',
      'E',
      'F',
      'G',
      'H',
      'I',
      'J',
    );
    switch ($input_type) {
      case "aiken":
        switch ($question->type) {
          case "multichoice":
            $write = $question->type . "\r\n" . $question->question . "\r\n";
            for ($i = 0; $i < count($question->answers); $i++) {
              $write .= "{$char[$i]}: {$question->answers[$i]}\r\n nil \r\n";
            }
            $write .= "ANSWER: {$char[$question->answer]} \r\n \r\n";
            break;
          case "true_false":
            $write = $question->type . "\r\n" . $question->question . "\r\n" . $question->answer . "\r\n" . $question->feedback . "\r\n \r\n";
            break;
          case "short_answer":
            $write = $question->type . "\r\n" . $question->question . "\r\n" . $question->answer . "\r\n" . $question->value . "\r\n" . $question->shortanswertype . "\r\n \r\n";
            break;
          case "long_answer":
            $write = $question->type . "\r\n" . $question->question . "\r\n" . $question->value . "\r\n \r\n";
            break;
        }
        break;
      case "csv":
        switch ($question->type) {
          case "multichoice":
            $write = $question->type . ', ' . $question->question . ', ';
            for ($i = 0; $i < count($question->answers); $i++) {
              $write .= $question->answers[$i] . ', nil, ';
            }
            $write .= $question->answers[$question->answer] . "\r\n";
            break;
          case "true_false":
            $write = $question->type . ', ' . $question->question . ', ' . $question->answer . ', ' . $question->feedback . "\r\n";
            break;
          case "short_answer":
            $write = $question->type . ', ' . $question->question . ', ' . $question->answer . ', ' . $question->value . ', ' . $question->shortanswertype . "\r\n";
            break;
          case "matching":
            $write = $question->type . ', ' . $question->question . ', ';
            for ($i = 0; $i < count($question->matches); $i++) {
              $write .= $question->matches[$i] . ', ';
              $write .= $question->answers[$i] . ', nil, ';
            }
            $write .= "\r\n";
            break;
          case "long_answer":
            $write = $question->type . ', ' . $question->question . ', ' . $question->value . "\r\n";
            break;
        }
        break;
    }
    return $write;
  }
  function medlyHelper($import_type) {
    switch ($import_type) {
      case 'aiken':
        $import_settings['import_type'] = 'native_aiken';
        $filetype = '.txt';
        break;
      case 'csv':
        $import_settings['import_type'] = 'native_csv';
        $filetype = '.csv';
        break;
    }
    $import_questions = array();
    $filepath = file_create_filename($import_type . '_medly' . $filetype, file_directory_temp());
    $handle = fopen($filepath, "w+");
    for ($i = 0; $i < 30; $i++) {
      $question_type = mt_rand(0, 2);
      $question = new stdClass();
      switch ($question_type) {
        case 0:
          $question->num_options = mt_rand(1, 10);
          $question->answer = mt_rand(0, $question->num_options - 1);
          $question->question = "Question #{$i}:";
          $question->type = "multichoice";
          $question->answers = array();
          $write = $question->type . "\r\n" . $question->question . "\r\n";
          for ($j = 0; $j < $question->num_options; $j++) {
            $question->answers[$j] = "Option #{$j}";
          }
          break;
        case 1:
          $question->answer = mt_rand(0, 1) == 0 ? "true" : "false";
          $question->question = "Question #{$i}:";
          $question->type = "true_false";
          $question->feedback = "Feedback on question #{$i}";
          break;
        case 2:
          $question->answer = "The answer is {$i}.";
          $question->question = "Question #{$i}:";
          $question->type = "short_answer";
          $question->value = mt_rand(1, 5);
          $question->sat = mt_rand(0, 3);
          switch ($question->sat) {
            case 0:
              $question->shortanswertype = "case sensitive match";
              break;
            case 1:
              $question->shortanswertype = "case insensitive match";
              break;
            case 2:
              $question->shortanswertype = "regular expression match";
              break;
            case 3:
              $question->shortanswertype = "manually score match";
              break;
          }
          break;
      }
      $write = $this
        ->writeImport($import_type, $question);
      fwrite($handle, $write);
      $import_questions[] = $question;
    }
    $import_settings['quiz_node'] = '1';
    $import_settings['field_separator'] = ',';
    $import_settings['files[upload]'] = $filepath;
    $msg = $this
      ->drupalPost('admin/quiz/questions_import', $import_settings, 'Import');
    $this
      ->assertPattern('/30 questions were imported successfully/', t('Checking import success message'));
    $this
      ->quizHelper($import_questions);
  }
  function multichoiceHelper($import_type) {
    $import_questions = array();
    switch ($import_type) {
      case 'aiken':
        $import_settings['import_type'] = 'native_aiken';
        $filetype = '.txt';
        break;
      case 'csv':
        $import_settings['import_type'] = 'native_csv';
        $filetype = '.csv';
        break;
    }
    $filepath = file_create_filename($import_type . '_multichoice' . $filetype, file_directory_temp());
    $handle = fopen($filepath, "w+");
    for ($i = 0; $i < 30; $i++) {
      $question = new stdClass();
      $question->num_options = mt_rand(2, 6);
      $question->answer = mt_rand(0, $question->num_options - 1);
      $question->question = "Question #{$i}:";
      $question->type = "multichoice";
      $question->answers = array();
      for ($j = 0; $j < $question->num_options; $j++) {
        $question->answers[$j] = "Option #{$j}";
      }
      $write = $this
        ->writeImport($import_type, $question);
      fwrite($handle, $write);
      $import_questions[] = $question;
    }
    fclose($handle);
    $import_settings['quiz_node'] = '1';
    $import_settings['field_separator'] = ',';
    $import_settings['files[upload]'] = $filepath;
    $msg = $this
      ->drupalPost('admin/quiz/questions_import', $import_settings, 'Import');
    $this
      ->assertPattern('/30 questions were imported successfully/', t('Checking import success message'));
    $this
      ->quizHelper($import_questions);
  }
  function matchingHelper($import_type) {
    $import_questions = array();
    switch ($import_type) {
      case 'aiken':
        $this
          ->assertEqual(0, 1, "Aiken does not support matching import questions");
        return;
        break;
      case 'csv':
        $import_settings['import_type'] = 'native_csv';
        $filetype = '.csv';
        break;
    }
    $filepath = file_create_filename($import_type . '_matching' . $filetype, "sites/default/files");
    $handle = fopen($filepath, "w+");
    for ($i = 0; $i < 30; $i++) {
      $question = new stdClass();
      $question->num_options = mt_rand(1, 10);
      $question->question = "Question #{$i}:";
      $question->type = "matching";
      $question->matches = array();
      $question->answers = array();
      for ($j = 0; $j < $question->num_options; $j++) {
        $question->matches[] = "Match #{$j}";
        $question->answers[] = "Answer #{$j}";
      }
      $write = $this
        ->writeImport($import_type, $question);
      fwrite($handle, $write);
      $import_questions[] = $question;
    }
    fclose($handle);
    $import_settings['quiz_node'] = '1';
    $import_settings['field_separator'] = ',';
    $import_settings['files[upload]'] = $filepath;
    $msg = $this
      ->drupalPost('admin/quiz/questions_import', $import_settings, 'Import');
    $this
      ->assertPattern('/30 questions were imported successfully/', t('Checking import success message'));
    $this
      ->quizHelper($import_questions);
  }
  function truefalseHelper($import_type) {
    $import_questions = array();
    switch ($import_type) {
      case 'aiken':
        $import_settings['import_type'] = 'native_aiken';
        $filetype = '.txt';
        break;
      case 'csv':
        $import_settings['import_type'] = 'native_csv';
        $filetype = '.csv';
        break;
    }
    $filepath = file_create_filename($import_type . '_truefalse' . $filetype, file_directory_temp());
    $handle = fopen($filepath, "w+");
    for ($i = 0; $i < 50; $i++) {
      $question = new stdClass();
      $question->answer = mt_rand(0, 1) == 0 ? "true" : "false";
      $question->question = "Question #{$i}:";
      $question->type = "true_false";
      $question->feedback = "Feedback on question #{$i}";
      $write = $this
        ->writeImport($import_type, $question);
      fwrite($handle, $write);
      $import_questions[] = $question;
    }
    fclose($handle);
    $import_settings['quiz_node'] = '1';
    $import_settings['field_separator'] = ',';
    $import_settings['files[upload]'] = $filepath;
    $msg = $this
      ->drupalPost('admin/quiz/questions_import', $import_settings, 'Import');
    $this
      ->assertEqual(0, 1, $msg);
    $this
      ->assertPattern('/50 questions were imported successfully/', t('Checking import success message'));
    $this
      ->quizHelper($import_questions);
  }
  function shortanswerHelper($import_type) {
    $import_questions = array();
    switch ($import_type) {
      case 'aiken':
        $import_settings['import_type'] = 'native_aiken';
        $filetype = '.txt';
        break;
      case 'csv':
        $import_settings['import_type'] = 'native_csv';
        $filetype = '.csv';
        break;
    }
    $filepath = file_create_filename($import_type . '_shortanswer' . $filetype, file_directory_temp());
    $handle = fopen($filepath, "w+");
    for ($i = 0; $i < 20; $i++) {
      $question = new stdClass();
      $question->answer = "The answer is {$i}.";
      $question->question = "Question #{$i}:";
      $question->type = "short_answer";
      $question->value = mt_rand(1, 5);
      $question->sat = mt_rand(0, 3);
      switch ($question->sat) {
        case 0:
          $question->shortanswertype = "case sensitive match";
          break;
        case 1:
          $question->shortanswertype = "case insensitive match";
          break;
        case 2:
          $question->shortanswertype = "regular expression match";
          break;
        case 3:
          $question->shortanswertype = "manually score match";
          break;
      }
      $write = $this
        ->writeImport($import_type, $question);
      fwrite($handle, $write);
      $import_questions[] = $question;
    }
    fclose($handle);
    $import_settings['quiz_node'] = '1';
    $import_settings['field_separator'] = ',';
    $import_settings['files[upload]'] = $filepath;
    $msg = $this
      ->drupalPost('admin/quiz/questions_import', $import_settings, 'Import');
    $this
      ->assertPattern('/20 questions were imported successfully/', t('Checking import success message'));
    $this
      ->quizHelper($import_questions);
  }
  function multiloadHelper($import_type) {
    $import_settings['quiz_node'] = '1';
    switch ($import_type) {
      case 'aiken':
        $import_settings['import_type'] = 'native_aiken';
        $filetype = '.txt';
        break;
      case 'csv':
        $import_settings['import_type'] = 'native_csv';
        $filetype = '.csv';
        break;
    }
    $import_settings['field_separator'] = ',';
    $import_questions = array();
    for ($i = 0; $i < 6; $i++) {
      $filepath = file_create_filename($import_type . '_multi_' . $i . $filetype, file_directory_temp());
      $handle = fopen($filepath, "w+");
      for ($j = $i * 5; $j < ($i + 1) * 5; $j++) {
        $question = new stdClass();
        $question->num_options = mt_rand(2, 5);
        $question->answer = mt_rand(0, $question->num_options - 1);
        $question->question = "Question #{$j}:";
        $question->type = "multichoice";
        $question->answers = array(
          "true",
          "false",
        );
        for ($k = 0; $k < $question->num_options; $k++) {
          $question->answers[$k] = "Option #{$k}";
        }
        $write = $this
          ->writeImport($import_type, $question);
        fwrite($handle, $write);
        $import_questions[] = $question;
      }
      fclose($handle);
      $import_settings['files[upload]'] = $filepath;
      $this
        ->drupalPost('admin/quiz/questions_import', $import_settings, 'Import');
      $this
        ->assertPattern('/5 questions were imported successfully/', t('Checking import success message'));
    }
    $this
      ->quizHelper($import_questions);
  }

  /**
   * takes a export file for the Drupal Aiken format, imports it then
   * exports it and checks if they are the same. fails as export doesn't
   * export a proper import file.
   * @return void
   */
  function disabled_testAikenRoundTrip() {
    $import_settings['quiz_node'] = '1';
    $import_settings['import_type'] = 'native_aiken';
    $import_settings['field_separator'] = ',';
    $import_settings['files[upload]'] = realpath(drupal_get_path('module', 'quiz') . '/Examples/questions_import/aiken_example.txt');
    $msg = $this
      ->drupalPost('admin/quiz/questions_import', $import_settings, 'Import');
    $export_settings['quiz_node'] = '1';
    $export_settings['export_format'] = 'native_aiken';
    $this
      ->drupalPost('admin/quiz/questions_export', $export_settings, 'Export');
    $h1 = file(file_directory_path() . '/Quiz.txt');
    $h2 = file(realpath(drupal_get_path('module', 'quiz') . '/Examples/questions_import/aiken_export.txt'));
    $this
      ->assertEqual(count($h1), count($h2), "[Aiken roundtrip] Same number of lines");
    for ($i = 0; $i < count($h2) && $i < count($h1); $i++) {
      $this
        ->assertIdentical($h1[$i], $h2[$i], "[Aiken roundtrip] Checking lines are the same");
    }
  }
  function testAikenMedly() {
    $this
      ->medlyHelper('aiken');
  }
  function testAikenMultichoice() {
    $this
      ->multichoiceHelper('aiken');
  }
  function testAikenTrueFalse() {
    $this
      ->truefalseHelper('aiken');
  }
  function testAikenShortAnswer() {
    $this
      ->shortanswerHelper('aiken');
  }
  function testAikenMultiload() {
    $this
      ->multiloadHelper('aiken');
  }
  function testCsvMedly() {
    $this
      ->medlyHelper('csv');
  }
  function testCsvMultichoice() {
    $this
      ->multichoiceHelper('csv');
  }
  function testCsvMatching() {
    $this
      ->matchingHelper('csv');
  }
  function testCsvTrueFalse() {
    $this
      ->truefalseHelper('csv');
  }
  function testCsvShortAnswer() {
    $this
      ->shortanswerHelper('csv');
  }
  function testCsvMultiload() {
    $this
      ->multiloadHelper('csv');
  }

}

Classes