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 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   * Unit tests for importing csv files.
  19   *
  20   * @package    mod_data
  21   * @category   test
  22   * @copyright  2019 Tobias Reischmann
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  /**
  29   * Unit tests for import.php.
  30   *
  31   * @package    mod_data
  32   * @copyright  2019 Tobias Reischmann
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class mod_data_import_test extends advanced_testcase {
  36  
  37      /**
  38       * Set up function.
  39       */
  40      protected function setUp(): void {
  41          parent::setUp();
  42  
  43          global $CFG;
  44          require_once($CFG->dirroot . '/mod/data/lib.php');
  45          require_once($CFG->dirroot . '/lib/datalib.php');
  46          require_once($CFG->dirroot . '/lib/csvlib.class.php');
  47          require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
  48          require_once($CFG->dirroot . '/mod/data/tests/generator/lib.php');
  49      }
  50  
  51      /**
  52       * Get the test data.
  53       * In this instance we are setting up database records to be used in the unit tests.
  54       *
  55       * @return array
  56       */
  57      protected function get_test_data(): array {
  58          $this->resetAfterTest(true);
  59  
  60          $generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
  61          $course = $this->getDataGenerator()->create_course();
  62          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
  63          $this->setUser($teacher);
  64          $student = $this->getDataGenerator()->create_and_enrol($course, 'student', array('username' => 'student'));
  65  
  66          $data = $generator->create_instance(array('course' => $course->id));
  67          $cm = get_coursemodule_from_instance('data', $data->id);
  68  
  69          // Add fields.
  70          $fieldrecord = new StdClass();
  71          $fieldrecord->name = 'ID'; // Identifier of the records for testing.
  72          $fieldrecord->type = 'number';
  73          $generator->create_field($fieldrecord, $data);
  74  
  75          $fieldrecord->name = 'Param2';
  76          $fieldrecord->type = 'text';
  77          $generator->create_field($fieldrecord, $data);
  78  
  79  
  80          return [
  81              'teacher' => $teacher,
  82              'student' => $student,
  83              'data' => $data,
  84              'cm' => $cm,
  85          ];
  86      }
  87  
  88      /**
  89       * Test uploading entries for a data instance without userdata.
  90       * @throws dml_exception
  91       */
  92      public function test_import(): void {
  93          [
  94              'data' => $data,
  95              'cm' => $cm,
  96              'teacher' => $teacher,
  97          ] = $this->get_test_data();
  98  
  99          $filecontent = file_get_contents(__DIR__ . '/fixtures/test_data_import.csv');
 100          ob_start();
 101          data_import_csv($cm, $data, $filecontent, 'UTF-8', 'comma');
 102          ob_end_clean();
 103  
 104          // No userdata is present in the file: Fallback is to assign the uploading user as author.
 105          $expecteduserids = array();
 106          $expecteduserids[1] = $teacher->id;
 107          $expecteduserids[2] = $teacher->id;
 108  
 109          $records = $this->get_data_records($data->id);
 110          $this->assertCount(2, $records);
 111          foreach ($records as $record) {
 112              $identifier = $record->items['ID']->content;
 113              $this->assertEquals($expecteduserids[$identifier], $record->userid);
 114          }
 115      }
 116  
 117      /**
 118       * Test uploading entries for a data instance with userdata.
 119       *
 120       * At least one entry has an identifiable user, which is assigned as author.
 121       * @throws dml_exception
 122       */
 123      public function test_import_with_userdata(): void {
 124          [
 125              'data' => $data,
 126              'cm' => $cm,
 127              'teacher' => $teacher,
 128              'student' => $student,
 129          ] = $this->get_test_data();
 130  
 131          $filecontent = file_get_contents(__DIR__ . '/fixtures/test_data_import_with_userdata.csv');
 132          ob_start();
 133          data_import_csv($cm, $data, $filecontent, 'UTF-8', 'comma');
 134          ob_end_clean();
 135  
 136          $expecteduserids = array();
 137          $expecteduserids[1] = $student->id; // User student exists and is assigned as author.
 138          $expecteduserids[2] = $teacher->id; // User student2 does not exist. Fallback is the uploading user.
 139  
 140          $records = $this->get_data_records($data->id);
 141          $this->assertCount(2, $records);
 142          foreach ($records as $record) {
 143              $identifier = $record->items['ID']->content;
 144              $this->assertEquals($expecteduserids[$identifier], $record->userid);
 145          }
 146      }
 147  
 148      /**
 149       * Test uploading entries for a data instance with userdata and a defined field 'Username'.
 150       *
 151       * This should test the corner case, in which a user has defined a data fields, which has the same name
 152       * as the current lang string for username. In that case, the first Username entry is used for the field.
 153       * The second one is used to identify the author.
 154       * @throws coding_exception
 155       * @throws dml_exception
 156       */
 157      public function test_import_with_field_username(): void {
 158          [
 159              'data' => $data,
 160              'cm' => $cm,
 161              'teacher' => $teacher,
 162              'student' => $student,
 163          ] = $this->get_test_data();
 164          $generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
 165  
 166          // Add username field.
 167          $fieldrecord = new StdClass();
 168          $fieldrecord->name = 'Username';
 169          $fieldrecord->type = 'text';
 170          $generator->create_field($fieldrecord, $data);
 171  
 172          $filecontent = file_get_contents(__DIR__ . '/fixtures/test_data_import_with_field_username.csv');
 173          ob_start();
 174          data_import_csv($cm, $data, $filecontent, 'UTF-8', 'comma');
 175          ob_end_clean();
 176  
 177          $expecteduserids = array();
 178          $expecteduserids[1] = $student->id; // User student exists and is assigned as author.
 179          $expecteduserids[2] = $teacher->id; // User student2 does not exist. Fallback is the uploading user.
 180          $expecteduserids[3] = $student->id; // User student exists and is assigned as author.
 181  
 182          $expectedcontent = array();
 183          $expectedcontent[1] = array(
 184              'Username' => 'otherusername1',
 185              'Param2' => 'My first entry',
 186          );
 187          $expectedcontent[2] = array(
 188              'Username' => 'otherusername2',
 189              'Param2' => 'My second entry',
 190          );
 191          $expectedcontent[3] = array(
 192              'Username' => 'otherusername3',
 193              'Param2' => 'My third entry',
 194          );
 195  
 196          $records = $this->get_data_records($data->id);
 197          $this->assertCount(3, $records);
 198          foreach ($records as $record) {
 199              $identifier = $record->items['ID']->content;
 200              $this->assertEquals($expecteduserids[$identifier], $record->userid);
 201  
 202              foreach ($expectedcontent[$identifier] as $field => $value) {
 203                  $this->assertEquals($value, $record->items[$field]->content,
 204                      "The value of field \"$field\" for the record at position \"$identifier\" ".
 205                      "which is \"{$record->items[$field]->content}\" does not match the expected value \"$value\".");
 206              }
 207          }
 208      }
 209  
 210      /**
 211       * Test uploading entries for a data instance with a field 'Username' but only one occurrence in the csv file.
 212       *
 213       * This should test the corner case, in which a user has defined a data fields, which has the same name
 214       * as the current lang string for username. In that case, the only Username entry is used for the field.
 215       * The author should not be set.
 216       * @throws coding_exception
 217       * @throws dml_exception
 218       */
 219      public function test_import_with_field_username_without_userdata(): void {
 220          [
 221              'data' => $data,
 222              'cm' => $cm,
 223              'teacher' => $teacher,
 224              'student' => $student,
 225          ] = $this->get_test_data();
 226          $generator = $this->getDataGenerator()->get_plugin_generator('mod_data');
 227  
 228          // Add username field.
 229          $fieldrecord = new StdClass();
 230          $fieldrecord->name = 'Username';
 231          $fieldrecord->type = 'text';
 232          $generator->create_field($fieldrecord, $data);
 233  
 234          $filecontent = file_get_contents(__DIR__ . '/fixtures/test_data_import_with_userdata.csv');
 235          ob_start();
 236          data_import_csv($cm, $data, $filecontent, 'UTF-8', 'comma');
 237          ob_end_clean();
 238  
 239          // No userdata is present in the file: Fallback is to assign the uploading user as author.
 240          $expecteduserids = array();
 241          $expecteduserids[1] = $teacher->id;
 242          $expecteduserids[2] = $teacher->id;
 243  
 244          $expectedcontent = array();
 245          $expectedcontent[1] = array(
 246              'Username' => 'student',
 247              'Param2' => 'My first entry',
 248          );
 249          $expectedcontent[2] = array(
 250              'Username' => 'student2',
 251              'Param2' => 'My second entry',
 252          );
 253  
 254          $records = $this->get_data_records($data->id);
 255          $this->assertCount(2, $records);
 256          foreach ($records as $record) {
 257              $identifier = $record->items['ID']->content;
 258              $this->assertEquals($expecteduserids[$identifier], $record->userid);
 259  
 260              foreach ($expectedcontent[$identifier] as $field => $value) {
 261                  $this->assertEquals($value, $record->items[$field]->content,
 262                      "The value of field \"$field\" for the record at position \"$identifier\" ".
 263                      "which is \"{$record->items[$field]->content}\" does not match the expected value \"$value\".");
 264              }
 265          }
 266      }
 267  
 268      /**
 269       * Returns the records of the data instance.
 270       *
 271       * Each records has an item entry, which contains all fields associated with this item.
 272       * Each fields has the parameters name, type and content.
 273       * @param int $dataid Id of the data instance.
 274       * @return array The records of the data instance.
 275       * @throws dml_exception
 276       */
 277      private function get_data_records(int $dataid): array {
 278          global $DB;
 279  
 280          $records = $DB->get_records('data_records', ['dataid' => $dataid]);
 281          foreach ($records as $record) {
 282              $sql = 'SELECT f.name, f.type, con.content FROM
 283                  {data_content} con JOIN {data_fields} f ON con.fieldid = f.id
 284                  WHERE con.recordid = :recordid';
 285              $items = $DB->get_records_sql($sql, array('recordid' => $record->id));
 286              $record->items = $items;
 287          }
 288          return $records;
 289      }
 290  }