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.
   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   * Test phpunit_dataset features.
  19   *
  20   * @package    core
  21   * @category   tests
  22   * @copyright  2020 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  declare(strict_types=1);
  27  
  28  namespace core;
  29  
  30  use advanced_testcase;
  31  use phpunit_dataset;
  32  use org\bovigo\vfs\vfsStream;
  33  
  34  /**
  35   * Test phpunit_dataset features.
  36   *
  37   * @coversDefaultClass \phpunit_dataset
  38   */
  39  class phpunit_dataset_test extends advanced_testcase {
  40  
  41  
  42      /**
  43       * @covers ::from_files
  44       */
  45      public function test_from_files() {
  46  
  47          $ds = new phpunit_dataset();
  48  
  49          $files = [
  50              __DIR__ . '/fixtures/sample_dataset.xml',
  51              'user2' => __DIR__ . '/fixtures/sample_dataset.csv',
  52          ];
  53  
  54          // We need public properties to check the basis.
  55          $dsref = new \ReflectionClass($ds);
  56          $dstables = $dsref->getProperty('tables');
  57          $dstables->setAccessible(true);
  58          $dscolumns = $dsref->getProperty('columns');
  59          $dscolumns->setAccessible(true);
  60          $dsrows = $dsref->getProperty('rows');
  61          $dsrows->setAccessible(true);
  62  
  63          // Expectations.
  64          $exptables = ['user', 'user2'];
  65          $expcolumns = ['id', 'username', 'email'];
  66          $exprows = [
  67              ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
  68              ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
  69          ];
  70  
  71          $ds->from_files($files);
  72  
  73          $this->assertIsArray($dstables->getValue($ds));
  74          $this->assertSame($exptables, $dstables->getValue($ds));
  75          $this->assertIsArray($dscolumns->getValue($ds));
  76          $this->assertSame($expcolumns, $dscolumns->getValue($ds)['user']);
  77          $this->assertSame($expcolumns, $dscolumns->getValue($ds)['user2']);
  78          $this->assertIsArray($dsrows->getValue($ds));
  79          $this->assertEquals($exprows, $dsrows->getValue($ds)['user']); // Equals because of stringified integers on load.
  80          $this->assertEquals($exprows, $dsrows->getValue($ds)['user2']); // Equals because of stringified integers on load.
  81      }
  82  
  83      /**
  84       * test_from_file() data provider.
  85       */
  86      public function from_file_provider() {
  87          // Create an unreadable file with vfsStream.
  88          $vfsfile = vfsStream::newFile('unreadable', 0222);
  89          vfsStream::setup('root')->addChild($vfsfile);
  90  
  91          return [
  92              'file not found' => [
  93                  'fullpath' => '/this/does/not/exist',
  94                  'tablename' => 'user',
  95                  'exception' => 'from_file, file not found: /this/does/not/exist',
  96                  'tables' => [],
  97                  'columns' => [],
  98                  'rows' => [],
  99              ],
 100              'file not readable' => [
 101                  'fullpath' => $vfsfile->url(),
 102                  'tablename' => 'user',
 103                  'exception' => 'from_file, file not readable: ' . $vfsfile->url(),
 104                  'tables' => [],
 105                  'columns' => [],
 106                  'rows' => [],
 107              ],
 108              'wrong extension' => [
 109                  'fullpath' => __DIR__ . '/fixtures/sample_dataset.txt',
 110                  'tablename' => 'user',
 111                  'exception' => 'from_file, cannot handle files with extension: txt',
 112                  'tables' => [],
 113                  'columns' => [],
 114                  'rows' => [],
 115              ],
 116              'csv loads ok' => [
 117                  'fullpath' => __DIR__ . '/fixtures/sample_dataset.csv',
 118                  'tablename' => 'user',
 119                  'exception' => null,
 120                  'tables' => ['user'],
 121                  'columns' => ['user' =>
 122                      ['id', 'username', 'email']
 123                  ],
 124                  'rows' => ['user' =>
 125                      [
 126                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 127                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 128                      ]
 129                  ],
 130              ],
 131              'xml loads ok' => [
 132                  'fullpath' => __DIR__ . '/fixtures/sample_dataset.xml',
 133                  'tablename' => 'user',
 134                  'exception' => null,
 135                  'tables' => ['user'],
 136                  'columns' => ['user' =>
 137                      ['id', 'username', 'email']
 138                  ],
 139                  'rows' => ['user' =>
 140                      [
 141                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 142                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 143                      ]
 144                  ],
 145              ],
 146          ];
 147      }
 148  
 149      /**
 150       * @dataProvider from_file_provider
 151       * @covers ::from_file
 152       */
 153      public function test_from_file(string $fullpath, string $tablename, ?string $exception,
 154          array $tables, array $columns, array $rows) {
 155  
 156          $ds = new phpunit_dataset();
 157  
 158          // We need public properties to check the basis.
 159          $dsref = new \ReflectionClass($ds);
 160          $dstables = $dsref->getProperty('tables');
 161          $dstables->setAccessible(true);
 162          $dscolumns = $dsref->getProperty('columns');
 163          $dscolumns->setAccessible(true);
 164          $dsrows = $dsref->getProperty('rows');
 165          $dsrows->setAccessible(true);
 166  
 167          // We are expecting an exception.
 168          if (!empty($exception)) {
 169              $this->expectException('coding_exception');
 170              $this->expectExceptionMessage($exception);
 171          }
 172  
 173          $ds->from_file($fullpath, $tablename);
 174  
 175          $this->assertIsArray($dstables->getValue($ds));
 176          $this->assertSame($tables, $dstables->getValue($ds));
 177          $this->assertIsArray($dscolumns->getValue($ds));
 178          $this->assertSame($columns, $dscolumns->getValue($ds));
 179          $this->assertIsArray($dsrows->getValue($ds));
 180          $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load.
 181      }
 182  
 183      /**
 184       * test_from_string() data provider.
 185       */
 186      public function from_string_provider() {
 187  
 188          return [
 189              'wrong type' => [
 190                  'content' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.xml'),
 191                  'type' => 'txt',
 192                  'tablename' => 'user',
 193                  'exception' => 'from_string, cannot handle contents of type: txt',
 194                  'tables' => [],
 195                  'columns' => [],
 196                  'rows' => [],
 197              ],
 198              'missing cvs table' => [
 199                  'content' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.csv'),
 200                  'type' => 'csv',
 201                  'tablename' => '',
 202                  'exception' => 'from_string, contents of type "cvs" require a $table to be passed, none found',
 203                  'tables' => [],
 204                  'columns' => [],
 205                  'rows' => [],
 206              ],
 207              'csv loads ok' => [
 208                  'fullpath' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.csv'),
 209                  'type' => 'csv',
 210                  'tablename' => 'user',
 211                  'exception' => null,
 212                  'tables' => ['user'],
 213                  'columns' => ['user' =>
 214                      ['id', 'username', 'email']
 215                  ],
 216                  'rows' => ['user' =>
 217                      [
 218                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 219                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 220                      ]
 221                  ],
 222              ],
 223              'xml loads ok' => [
 224                  'fullpath' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.xml'),
 225                  'type' => 'xml',
 226                  'tablename' => 'user',
 227                  'exception' => null,
 228                  'tables' => ['user'],
 229                  'columns' => ['user' =>
 230                      ['id', 'username', 'email']
 231                  ],
 232                  'rows' => ['user' =>
 233                      [
 234                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 235                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 236                      ]
 237                  ],
 238              ],
 239          ];
 240      }
 241  
 242      /**
 243       * @dataProvider from_string_provider
 244       * @covers ::from_string
 245       */
 246      public function test_from_string(string $content, string $type, string $tablename, ?string $exception,
 247          array $tables, array $columns, array $rows) {
 248  
 249          $ds = new phpunit_dataset();
 250  
 251          // We need public properties to check the basis.
 252          $dsref = new \ReflectionClass($ds);
 253          $dstables = $dsref->getProperty('tables');
 254          $dstables->setAccessible(true);
 255          $dscolumns = $dsref->getProperty('columns');
 256          $dscolumns->setAccessible(true);
 257          $dsrows = $dsref->getProperty('rows');
 258          $dsrows->setAccessible(true);
 259  
 260          // We are expecting an exception.
 261          if (!empty($exception)) {
 262              $this->expectException('coding_exception');
 263              $this->expectExceptionMessage($exception);
 264          }
 265  
 266          $ds->from_string($content, $type, $tablename);
 267  
 268          $this->assertIsArray($dstables->getValue($ds));
 269          $this->assertSame($tables, $dstables->getValue($ds));
 270          $this->assertIsArray($dscolumns->getValue($ds));
 271          $this->assertSame($columns, $dscolumns->getValue($ds));
 272          $this->assertIsArray($dsrows->getValue($ds));
 273          $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load.
 274      }
 275  
 276      /**
 277       * test_from_array() data provider.
 278       */
 279      public function from_array_provider() {
 280          return [
 281              'repeated array table many structures' => [
 282                  'structure' => [
 283                      'user' => [
 284                          ['id' => 5, 'name' => 'John'],
 285                          ['id' => 6, 'name' => 'Jane'],
 286                      ],
 287                  ],
 288                  'exception' => 'from_array, table already added to dataset: user',
 289                  'tables' => [],
 290                  'columns' => [],
 291                  'rows' => [],
 292                  'repeated' => true, // To force the table already exists exception.
 293              ],
 294              'wrong number of columns' => [
 295                  'structure' => [
 296                      'user' => [
 297                          ['id' => 5, 'name' => 'John'],
 298                          ['id' => 6],
 299                      ],
 300                  ],
 301                  'exception' => 'from_array, number of columns must match number of values, found: 2 vs 1',
 302                  'tables' => [],
 303                  'columns' => [],
 304                  'rows' => [],
 305              ],
 306              'wrong not matching names of columns' => [
 307                  'structure' => [
 308                      'user' => [
 309                          ['id' => 5, 'name' => 'John'],
 310                          ['id' => 6, 'noname' => 'Jane'],
 311                      ],
 312                  ],
 313                  'exception' => 'from_array, columns in all elements must match first one, found: id, noname',
 314                  'tables' => [],
 315                  'columns' => [],
 316                  'rows' => [],
 317              ],
 318              'ok non-associative format' => [
 319                  'structure' => [
 320                      'user' => [
 321                          ['id', 'name'],
 322                          [5, 'John'],
 323                          [6, 'Jane'],
 324                      ],
 325                  ],
 326                  'exception' => null,
 327                  'tables' => ['user'],
 328                  'columns' => ['user' =>
 329                      ['id', 'name'],
 330                  ],
 331                  'rows' => ['user' =>
 332                      [
 333                          ['id' => 5, 'name' => 'John'],
 334                          ['id' => 6, 'name' => 'Jane'],
 335                      ],
 336                  ],
 337              ],
 338              'ok associative format' => [
 339                  'structure' => [
 340                      'user' => [
 341                          ['id' => 5, 'name' => 'John'],
 342                          ['id' => 6, 'name' => 'Jane'],
 343                      ],
 344                  ],
 345                  'exception' => null,
 346                  'tables' => ['user'],
 347                  'columns' => ['user' =>
 348                      ['id', 'name'],
 349                  ],
 350                  'rows' => ['user' =>
 351                      [
 352                          ['id' => 5, 'name' => 'John'],
 353                          ['id' => 6, 'name' => 'Jane'],
 354                      ],
 355                  ],
 356              ],
 357              'ok multiple' => [
 358                  'structure' => [
 359                      'user' => [
 360                          ['id' => 5, 'name' => 'John'],
 361                          ['id' => 6, 'name' => 'Jane'],
 362                      ],
 363                      'course' => [
 364                          ['id' => 7, 'name' => '101'],
 365                          ['id' => 8, 'name' => '102'],
 366                      ],
 367                  ],
 368                  'exception' => null,
 369                  'tables' => ['user', 'course'],
 370                  'columns' => [
 371                      'user' => ['id', 'name'],
 372                      'course' => ['id', 'name'],
 373                  ],
 374                  'rows' => [
 375                      'user' => [
 376                          ['id' => 5, 'name' => 'John'],
 377                          ['id' => 6, 'name' => 'Jane'],
 378                      ],
 379                      'course' => [
 380                          ['id' => 7, 'name' => '101'],
 381                          ['id' => 8, 'name' => '102'],
 382                      ],
 383                  ],
 384              ],
 385          ];
 386      }
 387  
 388      /**
 389       * @dataProvider from_array_provider
 390       * @covers ::from_array
 391       */
 392      public function test_from_array(array $structure, ?string $exception,
 393          array $tables, array $columns, array $rows, ?bool $repeated = false) {
 394  
 395          $ds = new phpunit_dataset();
 396  
 397          // We need public properties to check the basis.
 398          $dsref = new \ReflectionClass($ds);
 399          $dstables = $dsref->getProperty('tables');
 400          $dstables->setAccessible(true);
 401          $dscolumns = $dsref->getProperty('columns');
 402          $dscolumns->setAccessible(true);
 403          $dsrows = $dsref->getProperty('rows');
 404          $dsrows->setAccessible(true);
 405  
 406          // We are expecting an exception.
 407          if (!empty($exception)) {
 408              $this->expectException('coding_exception');
 409              $this->expectExceptionMessage($exception);
 410          }
 411  
 412          $ds->from_array($structure);
 413          if ($repeated) {
 414              $ds->from_array($structure);
 415          }
 416  
 417          $this->assertIsArray($dstables->getValue($ds));
 418          $this->assertSame($tables, $dstables->getValue($ds));
 419          $this->assertIsArray($dscolumns->getValue($ds));
 420          $this->assertSame($columns, $dscolumns->getValue($ds));
 421          $this->assertIsArray($dsrows->getValue($ds));
 422          $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load.
 423      }
 424  
 425      /**
 426       * test_load_csv() data provider.
 427       */
 428      public function load_csv_provider() {
 429  
 430          return [
 431              'repeated csv table many files' => [
 432                  'files' => [
 433                      __DIR__ . '/fixtures/sample_dataset.xml',
 434                      'user' => __DIR__ . '/fixtures/sample_dataset.csv',
 435                  ],
 436                  'exception' => 'csv_dataset_format, table already added to dataset: user',
 437                  'tables' => [],
 438                  'columns' => [],
 439                  'rows' => [],
 440              ],
 441              'ok one csv file' => [
 442                  'files' => [
 443                      'user' => __DIR__ . '/fixtures/sample_dataset.csv',
 444                  ],
 445                  'exception' => null,
 446                  'tables' => ['user'],
 447                  'columns' => ['user' =>
 448                      ['id', 'username', 'email']
 449                  ],
 450                  'rows' => ['user' =>
 451                      [
 452                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 453                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 454                      ]
 455                  ],
 456              ],
 457              'ok multiple csv files' => [
 458                  'files' => [
 459                      'user1' => __DIR__ . '/fixtures/sample_dataset.csv',
 460                      'user2' => __DIR__ . '/fixtures/sample_dataset.csv',
 461                  ],
 462                  'exception' => null,
 463                  'tables' => ['user1', 'user2'],
 464                  'columns' => [
 465                      'user1' => ['id', 'username', 'email'],
 466                      'user2' => ['id', 'username', 'email'],
 467                  ],
 468                  'rows' => [
 469                      'user1' => [
 470                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 471                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 472                      ],
 473                      'user2' => [
 474                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 475                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 476                      ],
 477                  ],
 478              ],
 479          ];
 480      }
 481  
 482      /**
 483       * @dataProvider load_csv_provider
 484       * @covers ::load_csv
 485       */
 486      public function test_load_csv(array $files, ?string $exception,
 487          array $tables, array $columns, array $rows) {
 488  
 489          $ds = new phpunit_dataset();
 490  
 491          // We need public properties to check the basis.
 492          $dsref = new \ReflectionClass($ds);
 493          $dstables = $dsref->getProperty('tables');
 494          $dstables->setAccessible(true);
 495          $dscolumns = $dsref->getProperty('columns');
 496          $dscolumns->setAccessible(true);
 497          $dsrows = $dsref->getProperty('rows');
 498          $dsrows->setAccessible(true);
 499  
 500          // We are expecting an exception.
 501          if (!empty($exception)) {
 502              $this->expectException('coding_exception');
 503              $this->expectExceptionMessage($exception);
 504          }
 505  
 506          $ds->from_files($files);
 507  
 508          $this->assertIsArray($dstables->getValue($ds));
 509          $this->assertSame($tables, $dstables->getValue($ds));
 510          $this->assertIsArray($dscolumns->getValue($ds));
 511          $this->assertSame($columns, $dscolumns->getValue($ds));
 512          $this->assertIsArray($dsrows->getValue($ds));
 513          $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load.
 514      }
 515  
 516      /**
 517       * test_load_xml() data provider.
 518       */
 519      public function load_xml_provider() {
 520  
 521          return [
 522              'repeated xml table multiple files' => [
 523                  'files' => [
 524                      'user' => __DIR__ . '/fixtures/sample_dataset.csv',
 525                      __DIR__ . '/fixtures/sample_dataset.xml',
 526                  ],
 527                  'exception' => 'xml_dataset_format, table already added to dataset: user',
 528                  'tables' => [],
 529                  'columns' => [],
 530                  'rows' => [],
 531              ],
 532              'repeated xml table one file' => [
 533                  'files' => [__DIR__ . '/fixtures/sample_dataset_repeated.xml'],
 534                  'exception' => 'xml_dataset_format, table already added to dataset: user',
 535                  'tables' => [],
 536                  'columns' => [],
 537                  'rows' => [],
 538              ],
 539              'wrong dataset element' => [
 540                  'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_dataset.xml'],
 541                  'exception' => 'xml_dataset_format, main xml element must be "dataset", found: nodataset',
 542                  'tables' => [],
 543                  'columns' => [],
 544                  'rows' => [],
 545              ],
 546              'wrong table element' => [
 547                  'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_table.xml'],
 548                  'exception' => 'xml_dataset_format, only "table" elements allowed, found: notable',
 549                  'tables' => [],
 550                  'columns' => [],
 551                  'rows' => [],
 552              ],
 553              'wrong table name attribute' => [
 554                  'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_attribute.xml'],
 555                  'exception' => 'xml_dataset_format, "table" element only allows "name" attribute',
 556                  'tables' => [],
 557                  'columns' => [],
 558                  'rows' => [],
 559              ],
 560              'only col and row allowed' => [
 561                  'files' => [__DIR__ . '/fixtures/sample_dataset_only_colrow.xml'],
 562                  'exception' => 'xml_dataset_format, only "column or "row" elements allowed, found: nocolumn',
 563                  'tables' => [],
 564                  'columns' => [],
 565                  'rows' => [],
 566              ],
 567              'wrong value element' => [
 568                  'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_value.xml'],
 569                  'exception' => 'xml_dataset_format, only "value" elements allowed, found: novalue',
 570                  'tables' => [],
 571                  'columns' => [],
 572                  'rows' => [],
 573              ],
 574              'column before row' => [
 575                  'files' => [__DIR__ . '/fixtures/sample_dataset_col_before_row.xml'],
 576                  'exception' => 'xml_dataset_format, "column" elements always must be before "row" ones',
 577                  'tables' => [],
 578                  'columns' => [],
 579                  'rows' => [],
 580              ],
 581              'row after column' => [
 582                  'files' => [__DIR__ . '/fixtures/sample_dataset_row_after_col.xml'],
 583                  'exception' => 'xml_dataset_format, "row" elements always must be after "column" ones',
 584                  'tables' => [],
 585                  'columns' => [],
 586                  'rows' => [],
 587              ],
 588              'number of columns' => [
 589                  'files' => [__DIR__ . '/fixtures/sample_dataset_number_of_columns.xml'],
 590                  'exception' => 'xml_dataset_format, number of columns must match number of values, found: 4 vs 3',
 591                  'tables' => [],
 592                  'columns' => [],
 593                  'rows' => [],
 594              ],
 595              'ok one xml file' => [
 596                  'files' => [__DIR__ . '/fixtures/sample_dataset.xml'],
 597                  'exception' => null,
 598                  'tables' => ['user'],
 599                  'columns' => ['user' =>
 600                      ['id', 'username', 'email']
 601                  ],
 602                  'rows' => ['user' =>
 603                      [
 604                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 605                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 606                      ]
 607                  ],
 608              ],
 609              'ok multiple xml files' => [
 610                  'files' => [
 611                      'user1' => __DIR__ . '/fixtures/sample_dataset.csv',
 612                      __DIR__ . '/fixtures/sample_dataset.xml',
 613                  ],
 614                  'exception' => null,
 615                  'tables' => ['user1', 'user'],
 616                  'columns' => [
 617                      'user1' => ['id', 'username', 'email'],
 618                      'user' => ['id', 'username', 'email'],
 619                  ],
 620                  'rows' => [
 621                      'user1' => [
 622                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 623                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 624                      ],
 625                      'user' => [
 626                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 627                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 628                      ],
 629                  ],
 630              ],
 631              'ok many tables in one xml' => [
 632                  'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'],
 633                  'exception' => null,
 634                  'tables' => ['user', 'course'],
 635                  'columns' => [
 636                      'user' => ['id', 'username', 'email'],
 637                      'course' => ['id', 'shortname', 'fullname'],
 638                  ],
 639                  'rows' => [
 640                      'user' => [
 641                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 642                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 643                      ],
 644                      'course' => [
 645                          ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'],
 646                          ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'],
 647                      ],
 648                  ],
 649              ],
 650          ];
 651      }
 652  
 653      /**
 654       * @dataProvider load_xml_provider
 655       * @covers ::load_xml
 656       */
 657      public function test_load_xml(array $files, ?string $exception,
 658          array $tables, array $columns, array $rows) {
 659  
 660          $ds = new phpunit_dataset();
 661  
 662          // We need public properties to check the basis.
 663          $dsref = new \ReflectionClass($ds);
 664          $dstables = $dsref->getProperty('tables');
 665          $dstables->setAccessible(true);
 666          $dscolumns = $dsref->getProperty('columns');
 667          $dscolumns->setAccessible(true);
 668          $dsrows = $dsref->getProperty('rows');
 669          $dsrows->setAccessible(true);
 670  
 671          // We are expecting an exception.
 672          if (!empty($exception)) {
 673              $this->expectException('coding_exception');
 674              $this->expectExceptionMessage($exception);
 675          }
 676  
 677          $ds->from_files($files);
 678  
 679          $this->assertIsArray($dstables->getValue($ds));
 680          $this->assertSame($tables, $dstables->getValue($ds));
 681          $this->assertIsArray($dscolumns->getValue($ds));
 682          $this->assertSame($columns, $dscolumns->getValue($ds));
 683          $this->assertIsArray($dsrows->getValue($ds));
 684          $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load.
 685      }
 686  
 687      /**
 688       * test_to_database() data provider.
 689       */
 690      public function to_database_provider() {
 691  
 692          return [
 693              'wrong table requested' => [
 694                  'files' => [__DIR__ . '/fixtures/sample_dataset_insert.xml'],
 695                  'filter' => ['wrongtable'],
 696                  'exception' => 'dataset_to_database, table is not in the dataset: wrongtable',
 697                  'columns' => [],
 698                  'rows' => [],
 699              ],
 700              'one table insert' => [
 701                  'files' => [__DIR__ . '/fixtures/sample_dataset_insert.xml'],
 702                  'filter' => [],
 703                  'exception' => null,
 704                  'columns' => [
 705                      'user' => ['username', 'email'],
 706                  ],
 707                  'rows' => ['user' =>
 708                      [
 709                          (object)['username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 710                          (object)['username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 711                      ]
 712                  ],
 713              ],
 714              'one table import' => [
 715                  'files' => [__DIR__ . '/fixtures/sample_dataset.xml'],
 716                  'filter' => [],
 717                  'exception' => null,
 718                  'columns' => [
 719                      'user' => ['id', 'username', 'email'],
 720                  ],
 721                  'rows' => ['user' =>
 722                      [
 723                          (object)['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 724                          (object)['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 725                      ]
 726                  ],
 727              ],
 728              'multiple table many files import' => [
 729                  'files' => [
 730                      __DIR__ . '/fixtures/sample_dataset.xml',
 731                      __DIR__ . '/fixtures/sample_dataset2.xml',
 732                  ],
 733                  'filter' => [],
 734                  'exception' => null,
 735                  'columns' => [
 736                      'user' => ['id', 'username', 'email'],
 737                      'course' => ['id', 'shortname', 'fullname'],
 738                  ],
 739                  'rows' => [
 740                      'user' => [
 741                          (object)['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 742                          (object)['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 743                      ],
 744                      'course' => [
 745                          (object)['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'],
 746                          (object)['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'],
 747                      ],
 748                  ],
 749              ],
 750              'multiple table one file import' => [
 751                  'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'],
 752                  'filter' => [],
 753                  'exception' => null,
 754                  'columns' => [
 755                      'user' => ['id', 'username', 'email'],
 756                      'course' => ['id', 'shortname', 'fullname'],
 757                  ],
 758                  'rows' => [
 759                      'user' => [
 760                          (object)['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 761                          (object)['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 762                      ],
 763                      'course' => [
 764                          (object)['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'],
 765                          (object)['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'],
 766                      ],
 767                  ],
 768              ],
 769              'filtering tables' => [
 770                  'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'],
 771                  'filter' => ['course'],
 772                  'exception' => null,
 773                  'columns' => [
 774                      'user' => ['id', 'username', 'email'],
 775                      'course' => ['id', 'shortname', 'fullname'],
 776                  ],
 777                  'rows' => [
 778                      'user' => [], // Table user is being excluded via filter, expect no rows sent to database.
 779                      'course' => [
 780                          (object)['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'],
 781                          (object)['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'],
 782                      ],
 783                  ],
 784              ],
 785          ];
 786      }
 787  
 788      /**
 789       * @dataProvider to_database_provider
 790       * @covers ::to_database
 791       */
 792      public function test_to_database(array $files, ?array $filter, ?string $exception, array $columns, array $rows) {
 793          global $DB;
 794  
 795          $this->resetAfterTest();
 796  
 797          // Grab the status before loading to database.
 798          $before = [];
 799          foreach ($columns as $tablename => $tablecolumns) {
 800              if (!isset($before[$tablename])) {
 801                  $before[$tablename] = [];
 802              }
 803              $before[$tablename] = $DB->get_records($tablename, null, '', implode(', ', $tablecolumns));
 804          }
 805  
 806          $ds = new phpunit_dataset();
 807  
 808          // We are expecting an exception.
 809          if (!empty($exception)) {
 810              $this->expectException('coding_exception');
 811              $this->expectExceptionMessage($exception);
 812          }
 813  
 814          $ds->from_files($files);
 815          $ds->to_database($filter);
 816  
 817          // Grab the status after loading to database.
 818          $after = [];
 819          foreach ($columns as $tablename => $tablecolumns) {
 820              if (!isset($after[$tablename])) {
 821                  $after[$tablename] = [];
 822              }
 823              $sortandcol = implode(', ', $tablecolumns);
 824              $after[$tablename] = $DB->get_records($tablename, null, $sortandcol, $sortandcol);
 825          }
 826  
 827          // Differences must match the expectations.
 828          foreach ($rows as $tablename => $expectedrows) {
 829              $changes = array_udiff($after[$tablename], $before[$tablename], function ($b, $a) {
 830                  if ((array)$b > (array)$a) {
 831                      return 1;
 832                  } else if ((array)$b < (array)$a) {
 833                      return -1;
 834                  } else {
 835                      return 0;
 836                  }
 837              });
 838              $this->assertEquals(array_values($expectedrows), array_values($changes));
 839          }
 840      }
 841  
 842      /**
 843       * test_get_rows() data provider.
 844       */
 845      public function get_rows_provider() {
 846  
 847          return [
 848              'wrong table requested' => [
 849                  'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'],
 850                  'filter' => ['wrongtable'],
 851                  'exception' => 'dataset_get_rows, table is not in the dataset: wrongtable',
 852                  'rows' => [],
 853              ],
 854              'ok get rows from empty tables' => [
 855                  'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'],
 856                  'filter' => ['empty1', 'empty2'],
 857                  'exception' => null,
 858                  'rows' => [
 859                      'empty1' => [],
 860                      'empty2' => [],
 861                  ],
 862              ],
 863              'ok get rows from one table' => [
 864                  'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'],
 865                  'filter' => ['user'],
 866                  'exception' => null,
 867                  'rows' => [
 868                      'user' => [
 869                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 870                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 871                      ],
 872                  ],
 873              ],
 874              'ok get rows from two tables' => [
 875                  'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'],
 876                  'filter' => ['user', 'course'],
 877                  'exception' => null,
 878                  'rows' => [
 879                      'user' => [
 880                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 881                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 882                      ],
 883                      'course' => [
 884                          ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'],
 885                          ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'],
 886                      ],
 887                  ],
 888              ],
 889              'ok get rows from three tables' => [
 890                  'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'],
 891                  'filter' => ['user', 'empty1', 'course'],
 892                  'exception' => null,
 893                  'rows' => [
 894                      'user' => [
 895                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 896                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 897                      ],
 898                      'empty1' => [],
 899                      'course' => [
 900                          ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'],
 901                          ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'],
 902                      ],
 903                  ],
 904              ],
 905              'ok no filter returns all' => [
 906                  'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'],
 907                  'filter' => [],
 908                  'exception' => null,
 909                  'rows' => [
 910                      'user' => [
 911                          ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'],
 912                          ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'],
 913                      ],
 914                      'empty1' => [],
 915                      'empty2' => [],
 916                      'course' => [
 917                          ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'],
 918                          ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'],
 919                      ],
 920                  ],
 921              ],
 922          ];
 923      }
 924  
 925      /**
 926       * @dataProvider get_rows_provider
 927       * @covers ::get_rows
 928       */
 929      public function test_get_rows(array $files, array $filter, ?string $exception, array $rows) {
 930  
 931          $ds = new phpunit_dataset();
 932  
 933          // We are expecting an exception.
 934          if (!empty($exception)) {
 935              $this->expectException('coding_exception');
 936              $this->expectExceptionMessage($exception);
 937          }
 938  
 939          $ds->from_files($files);
 940          $this->assertEquals($rows, $ds->get_rows($filter));
 941      }
 942  }