From ed103545ddfad712886c0d4180e73482bb7d9e13 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Sun, 4 Oct 2020 17:30:44 +0200 Subject: [PATCH] MDL-67673 phpunit: Introduce a new, lightweight phpunit_dataset In charge of taking over the, now removed/archived, phpunit/dbunit package. It supports loading of CSV/XML files and strings and PHP arrays, allowing to send the loaded information to database. Perform some basic controls about the consistency of information, surely not super-exhaustive but fair enough. 100% covered with unit test. Planned like an "interim" replacement for phpunit/dbunit uses in core that, ideally, should be moved to generators stuff at some point. Note, expect a few tests in core to fail with this commit, I've changed some fixtures around. Next commit will fix existing uses. Originally MDL-64600 --- lib/phpunit/classes/phpunit_dataset.php | 371 +++++++ lib/phpunit/lib.php | 1 + lib/phpunit/tests/fixtures/sample_dataset.csv | 6 +- lib/phpunit/tests/fixtures/sample_dataset.txt | 3 + lib/phpunit/tests/fixtures/sample_dataset.xml | 10 +- .../tests/fixtures/sample_dataset2.xml | 18 + .../sample_dataset_col_before_row.xml | 19 + .../tests/fixtures/sample_dataset_insert.xml | 15 + .../tests/fixtures/sample_dataset_many.xml | 33 + .../sample_dataset_many_with_empty.xml | 39 + .../sample_dataset_number_of_columns.xml | 19 + .../fixtures/sample_dataset_only_colrow.xml | 18 + .../fixtures/sample_dataset_repeated.xml | 33 + .../fixtures/sample_dataset_row_after_col.xml | 21 + .../sample_dataset_wrong_attribute.xml | 18 + .../fixtures/sample_dataset_wrong_dataset.xml | 18 + .../fixtures/sample_dataset_wrong_table.xml | 18 + .../fixtures/sample_dataset_wrong_value.xml | 33 + lib/phpunit/tests/phpunit_dataset.test.php | 938 ++++++++++++++++++ 19 files changed, 1623 insertions(+), 8 deletions(-) create mode 100644 lib/phpunit/classes/phpunit_dataset.php create mode 100644 lib/phpunit/tests/fixtures/sample_dataset.txt create mode 100644 lib/phpunit/tests/fixtures/sample_dataset2.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_col_before_row.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_insert.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_many.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_many_with_empty.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_number_of_columns.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_only_colrow.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_repeated.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_row_after_col.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_wrong_attribute.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_wrong_dataset.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_wrong_table.xml create mode 100644 lib/phpunit/tests/fixtures/sample_dataset_wrong_value.xml create mode 100644 lib/phpunit/tests/phpunit_dataset.test.php diff --git a/lib/phpunit/classes/phpunit_dataset.php b/lib/phpunit/classes/phpunit_dataset.php new file mode 100644 index 00000000000..28a4b3bc062 --- /dev/null +++ b/lib/phpunit/classes/phpunit_dataset.php @@ -0,0 +1,371 @@ +. + +/** + * Handle simple PHP/CSV/XML datasets to be use with ease by unit tests. + * + * This is a very minimal class, able to load data from PHP arrays and + * CSV/XML files, optionally uploading them to database. + * + * This doesn't aim to be a complex or complete solution, but just a + * utility class to replace old phpunit/dbunit uses, because that package + * is not longer maintained. Note that, ideally, generators should provide + * the needed utilities to proceed with this loading of information to + * database and, if there is any future that should be it. + * + * @package core + * @category test + * @copyright 2020 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types=1); + +/** + * Lightweight dataset class for phpunit, supports XML, CSV and array datasets. + * + * This is a simple replacement class for the old old phpunit/dbunit, now + * archived. It allows to load CSV, XML and array structures to database. + */ +class phpunit_dataset { + + /** @var array tables being handled by the dataset */ + protected $tables = []; + /** @var array columns belonging to every table (keys) handled by the dataset */ + protected $columns = []; + /** @var array rows belonging to every table (keys) handled by the dataset */ + protected $rows = []; + + /** + * Load information from multiple files (XML, CSV) to the dataset. + * + * This method accepts an array of full paths to CSV or XML files to be loaded + * into the dataset. For CSV files, the name of the table which the file belongs + * to needs to be specified. Example: + * + * $fullpaths = [ + * '/path/to/users.xml', + * 'course' => '/path/to/courses.csv', + * ]; + * + * @param array $fullpaths full paths to CSV or XML files to load. + */ + public function from_files(array $fullpaths): void { + foreach ($fullpaths as $table => $fullpath) { + $table = is_int($table) ? null : $table; // Only a table when it's an associative array. + $this->from_file($fullpath, $table); + } + } + + /** + * Load information from one file (XML, CSV) to the dataset. + * + * @param string $fullpath full path to CSV or XML file to load. + * @param string|null $table name of the table which the file belongs to (only for CSV files). + */ + public function from_file(string $fullpath, ?string $table = null): void { + if (!file_exists($fullpath)) { + throw new coding_exception('from_file, file not found: ' . $fullpath); + } + + if (!is_readable($fullpath)) { + throw new coding_exception('from_file, file not readable: ' . $fullpath); + } + + $extension = strtolower(pathinfo($fullpath, PATHINFO_EXTENSION)); + if (!in_array($extension, ['csv', 'xml'])) { + throw new coding_exception('from_file, cannot handle files with extension: ' . $extension); + } + + $this->from_string(file_get_contents($fullpath), $extension, $table); + } + + /** + * Load information from a string (XML, CSV) to the dataset. + * + * @param string $content contents (CSV or XML) to load. + * @param string $type format of the content to be loaded (csv or xml). + * @param string $table|null name of the table which the file belongs to (only for CSV files). + */ + public function from_string(string $content, string $type, ?string $table = null): void { + switch ($type) { + case 'xml': + $this->load_xml($content); + break; + case 'csv': + if (empty($table)) { + throw new coding_exception('from_string, contents of type "cvs" require a $table to be passed, none found'); + } + $this->load_csv($content, $table); + break; + default: + throw new coding_exception('from_string, cannot handle contents of type: ' . $type); + } + } + + /** + * Load information from a PHP array to the dataset. + * + * The general structure of the PHP array must be + * [table name] => [array of rows, each one being an array of values or column => values. + * The format of the array must be one of the following: + * - non-associative array, with column names in the first row (pretty much like CSV files are): + * $structure = [ + * 'table 1' => [ + * ['column name 1', 'column name 2'], + * ['row 1 column 1 value', 'row 1 column 2 value'*, + * ['row 2 column 1 value', 'row 2 column 2 value'*, + * ], + * 'table 2' => ... + * ]; + * - associative array, with column names being keys in the array. + * $structure = [ + * 'table 1' => [ + * ['column name 1' => 'row 1 column 1 value', 'column name 2' => 'row 1 column 2 value'], + * ['column name 1' => 'row 2 column 1 value', 'column name 2' => 'row 2 column 2 value'], + * ], + * 'table 2' => ... + * ]; + * @param array $structure php array with a valid structure to be loaded to the dataset. + */ + public function from_array(array $structure): void { + foreach ($structure as $tablename => $rows) { + if (in_array($tablename, $this->tables)) { + throw new coding_exception('from_array, table already added to dataset: ' . $tablename); + } + + $this->tables[] = $tablename; + $this->columns[$tablename] = []; + $this->rows[$tablename] = []; + + $isassociative = false; + $firstrow = reset($rows); + + if (array_key_exists(0, $firstrow)) { + // Columns are the first row (csv-like). + $this->columns[$tablename] = $firstrow; + array_shift($rows); + } else { + // Columns are the keys on every record, first one must have all. + $this->columns[$tablename] = array_keys($firstrow); + $isassociative = true; + } + + $countcols = count($this->columns[$tablename]); + foreach ($rows as $row) { + $countvalues = count($row); + if ($countcols != $countvalues) { + throw new coding_exception('from_array, number of columns must match number of values, found: ' . + $countcols . ' vs ' . $countvalues); + } + if ($isassociative && $this->columns[$tablename] != array_keys($row)) { + throw new coding_exception('from_array, columns in all elements must match first one, found: ' . + implode(', ', array_keys($row))); + } + $this->rows[$tablename][] = array_combine($this->columns[$tablename], array_values($row)); + } + } + } + + /** + * Send all the information to the dataset to the database. + * + * This method gets all the information loaded in the dataset, using the from_xxx() methods + * and sends it to the database; table and column names must match. + * + * Note that, if the information to be sent to database contains sequence columns (usually 'id') + * then those values will be preserved (performing an import and adjusting sequences later). Else + * normal inserts will happen and sequence (auto-increment) columns will be fed automatically. + * + * @param string[] $filter Tables to be sent to database. If not specified, all tables are processed. + */ + public function to_database(array $filter = []): void { + global $DB; + + // Verify all filter elements are correct. + foreach ($filter as $table) { + if (!in_array($table, $this->tables)) { + throw new coding_exception('dataset_to_database, table is not in the dataset: ' . $table); + } + } + + $structure = phpunit_util::get_tablestructure(); + + foreach ($this->tables as $table) { + // Apply filter. + if (!empty($filter) && !in_array($table, $filter)) { + continue; + } + + $doimport = false; + + if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) { + $doimport = in_array('id', $this->columns[$table]); + } + + foreach ($this->rows[$table] as $row) { + if ($doimport) { + $DB->import_record($table, $row); + } else { + $DB->insert_record($table, $row); + } + } + + if ($doimport) { + $DB->get_manager()->reset_sequence(new xmldb_table($table)); + } + } + } + + /** + * Returns the rows, for a given table, that the dataset holds. + * + * @param string[] $filter Tables to return rows. If not specified, all tables are processed. + * @return array tables as keys with rows on each as sub array. + */ + public function get_rows(array $filter = []): array { + // Verify all filter elements are correct. + foreach ($filter as $table) { + if (!in_array($table, $this->tables)) { + throw new coding_exception('dataset_get_rows, table is not in the dataset: ' . $table); + } + } + + $result = []; + foreach ($this->tables as $table) { + // Apply filter. + if (!empty($filter) && !in_array($table, $filter)) { + continue; + } + $result[$table] = $this->rows[$table]; + } + return $result; + } + + /** + * Given a CSV content, process and load it as a table into the dataset. + * + * @param string $content CSV content to be loaded (only one table). + * @param string $tablename Name of the table the content belongs to. + */ + protected function load_csv(string $content, string $tablename): void { + if (in_array($tablename, $this->tables)) { + throw new coding_exception('csv_dataset_format, table already added to dataset: ' . $tablename); + } + + $this->tables[] = $tablename; + $this->columns[$tablename] = []; + $this->rows[$tablename] = []; + + // Normalise newlines. + $content = preg_replace('#\r\n?#', '\n', $content); + + // Function str_getcsv() is not good for new lines within the data, so lets use temp file and fgetcsv() instead. + $tempfile = tempnam(make_temp_directory('phpunit'), 'csv'); + $fh = fopen($tempfile, 'w+b'); + fwrite($fh, $content); + + // And let's read it using fgetcsv(). + rewind($fh); + + // We just accept default, delimiter = comma, enclosure = double quote. + while ( ($row = fgetcsv($fh) ) !== false ) { + if (empty($this->columns[$tablename])) { + $this->columns[$tablename] = $row; + } else { + $this->rows[$tablename][] = array_combine($this->columns[$tablename], $row); + } + } + fclose($fh); + unlink($tempfile); + } + + /** + * Given a XML content, process and load it as tables into the dataset. + * + * @param string $content XML content to be loaded (can be multi-table). + */ + protected function load_xml(string $content): void { + $xml = new SimpleXMLElement($content); + // Main element must be dataset. + if ($xml->getName() !== 'dataset') { + throw new coding_exception('xml_dataset_format, main xml element must be "dataset", found: ' . $xml->getName()); + } + + foreach ($xml->children() as $table) { + // Only table elements allowed. + if ($table->getName() !== 'table') { + throw new coding_exception('xml_dataset_format, only "table" elements allowed, found: ' . $table->getName()); + } + // Only allowed attribute of table is "name". + if (!isset($table['name'])) { + throw new coding_exception('xml_dataset_format, "table" element only allows "name" attribute.'); + } + + $tablename = (string)$table['name']; + if (in_array($tablename, $this->tables)) { + throw new coding_exception('xml_dataset_format, table already added to dataset: ' . $tablename); + } + + $this->tables[] = $tablename; + $this->columns[$tablename] = []; + $this->rows[$tablename] = []; + + $countcols = 0; + foreach ($table->children() as $colrow) { + // Only column and row allowed. + if ($colrow->getName() !== 'column' && $colrow->getName() !== 'row') { + throw new coding_exception('xml_dataset_format, only "column or "row" elements allowed, found: ' . + $colrow->getName()); + } + // Column always before row. + if ($colrow->getName() == 'column' && !empty($this->rows[$tablename])) { + throw new coding_exception('xml_dataset_format, "column" elements always must be before "row" ones'); + } + // Row always after column. + if ($colrow->getName() == 'row' && empty($this->columns[$tablename])) { + throw new coding_exception('xml_dataset_format, "row" elements always must be after "column" ones'); + } + + // Process column. + if ($colrow->getName() == 'column') { + $this->columns[$tablename][] = (string)$colrow; + $countcols++; + } + + // Process row. + if ($colrow->getName() == 'row') { + $countvalues = 0; + $row = []; + foreach ($colrow->children() as $value) { + // Only value allowed under row. + if ($value->getName() !== 'value') { + throw new coding_exception('xml_dataset_format, only "value" elements allowed, found: ' . + $value->getName()); + } + $row[$this->columns[$tablename][$countvalues]] = (string)$value; + $countvalues++; + } + if ($countcols !== $countvalues) { + throw new coding_exception('xml_dataset_format, number of columns must match number of values, found: ' . + $countcols . ' vs ' . $countvalues); + } + $this->rows[$tablename][] = $row; + } + } + } + } +} diff --git a/lib/phpunit/lib.php b/lib/phpunit/lib.php index d470c34126e..3396c7b5a19 100644 --- a/lib/phpunit/lib.php +++ b/lib/phpunit/lib.php @@ -26,6 +26,7 @@ // NOTE: MOODLE_INTERNAL is not verified here because we load this before setup.php! require_once(__DIR__.'/classes/util.php'); +require_once(__DIR__.'/classes/phpunit_dataset.php'); require_once(__DIR__.'/classes/event_mock.php'); require_once(__DIR__.'/classes/event_sink.php'); require_once(__DIR__.'/classes/message_sink.php'); diff --git a/lib/phpunit/tests/fixtures/sample_dataset.csv b/lib/phpunit/tests/fixtures/sample_dataset.csv index 51a1eb9fd71..c7ae86885cd 100644 --- a/lib/phpunit/tests/fixtures/sample_dataset.csv +++ b/lib/phpunit/tests/fixtures/sample_dataset.csv @@ -1,3 +1,3 @@ -username,email -pepa.novak,pepa@example.com -bozka.novakova,bozka@example.com \ No newline at end of file +id,username,email +5,bozka.novakova,bozka@example.com +7,pepa.novak,pepa@example.com diff --git a/lib/phpunit/tests/fixtures/sample_dataset.txt b/lib/phpunit/tests/fixtures/sample_dataset.txt new file mode 100644 index 00000000000..2adc79cc43b --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset.txt @@ -0,0 +1,3 @@ +username,email +pepa.novak,pepa@example.com +bozka.novakova,bozka@example.com diff --git a/lib/phpunit/tests/fixtures/sample_dataset.xml b/lib/phpunit/tests/fixtures/sample_dataset.xml index 2066fbf52c9..ef28d010fe2 100644 --- a/lib/phpunit/tests/fixtures/sample_dataset.xml +++ b/lib/phpunit/tests/fixtures/sample_dataset.xml @@ -6,13 +6,13 @@ email 5 - john.doe - john@example.com + bozka.novakova + bozka@example.com 7 - jane.doe - jane@example.com + pepa.novak + pepa@example.com - \ No newline at end of file + diff --git a/lib/phpunit/tests/fixtures/sample_dataset2.xml b/lib/phpunit/tests/fixtures/sample_dataset2.xml new file mode 100644 index 00000000000..2dba5be792a --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset2.xml @@ -0,0 +1,18 @@ + + + + id + shortname + fullname + + 6 + 101 + 1-0-1 + + + 8 + 202 + 2-0-2 + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_col_before_row.xml b/lib/phpunit/tests/fixtures/sample_dataset_col_before_row.xml new file mode 100644 index 00000000000..469b792c54f --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_col_before_row.xml @@ -0,0 +1,19 @@ + + + + id + username + email + + 5 + pepa.novak + pepa@example.com + + toolate + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_insert.xml b/lib/phpunit/tests/fixtures/sample_dataset_insert.xml new file mode 100644 index 00000000000..111a92bc7f3 --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_insert.xml @@ -0,0 +1,15 @@ + + + + username + email + + bozka.novakova + bozka@example.com + + + pepa.novak + pepa@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_many.xml b/lib/phpunit/tests/fixtures/sample_dataset_many.xml new file mode 100644 index 00000000000..2ca91d178b5 --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_many.xml @@ -0,0 +1,33 @@ + + + + id + username + email + + 5 + bozka.novakova + bozka@example.com + + + 7 + pepa.novak + pepa@example.com + +
+ + id + shortname + fullname + + 6 + 101 + 1-0-1 + + + 8 + 202 + 2-0-2 + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_many_with_empty.xml b/lib/phpunit/tests/fixtures/sample_dataset_many_with_empty.xml new file mode 100644 index 00000000000..612433229c7 --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_many_with_empty.xml @@ -0,0 +1,39 @@ + + + + id + username + email + + 5 + bozka.novakova + bozka@example.com + + + 7 + pepa.novak + pepa@example.com + +
+ +
+ id + shortname + fullname +
+ + id + shortname + fullname + + 6 + 101 + 1-0-1 + + + 8 + 202 + 2-0-2 + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_number_of_columns.xml b/lib/phpunit/tests/fixtures/sample_dataset_number_of_columns.xml new file mode 100644 index 00000000000..10b60cd7d5a --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_number_of_columns.xml @@ -0,0 +1,19 @@ + + + + id + username + email + toomany + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_only_colrow.xml b/lib/phpunit/tests/fixtures/sample_dataset_only_colrow.xml new file mode 100644 index 00000000000..57db48841ce --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_only_colrow.xml @@ -0,0 +1,18 @@ + + + + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_repeated.xml b/lib/phpunit/tests/fixtures/sample_dataset_repeated.xml new file mode 100644 index 00000000000..2fd9e50537f --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_repeated.xml @@ -0,0 +1,33 @@ + + + + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+ + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_row_after_col.xml b/lib/phpunit/tests/fixtures/sample_dataset_row_after_col.xml new file mode 100644 index 00000000000..8f27acdb423 --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_row_after_col.xml @@ -0,0 +1,21 @@ + + + + + tooearly + + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_wrong_attribute.xml b/lib/phpunit/tests/fixtures/sample_dataset_wrong_attribute.xml new file mode 100644 index 00000000000..f2787da63f2 --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_wrong_attribute.xml @@ -0,0 +1,18 @@ + + + + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_wrong_dataset.xml b/lib/phpunit/tests/fixtures/sample_dataset_wrong_dataset.xml new file mode 100644 index 00000000000..ba2c46e083f --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_wrong_dataset.xml @@ -0,0 +1,18 @@ + + + + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/fixtures/sample_dataset_wrong_table.xml b/lib/phpunit/tests/fixtures/sample_dataset_wrong_table.xml new file mode 100644 index 00000000000..fb1d743a345 --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_wrong_table.xml @@ -0,0 +1,18 @@ + + + + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + + + diff --git a/lib/phpunit/tests/fixtures/sample_dataset_wrong_value.xml b/lib/phpunit/tests/fixtures/sample_dataset_wrong_value.xml new file mode 100644 index 00000000000..9ff4911f31c --- /dev/null +++ b/lib/phpunit/tests/fixtures/sample_dataset_wrong_value.xml @@ -0,0 +1,33 @@ + + + + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+ + id + username + email + + 5 + pepa.novak + pepa@example.com + + + 7 + bozka.novakova + bozka@example.com + +
+
diff --git a/lib/phpunit/tests/phpunit_dataset.test.php b/lib/phpunit/tests/phpunit_dataset.test.php new file mode 100644 index 00000000000..fcb1c5619da --- /dev/null +++ b/lib/phpunit/tests/phpunit_dataset.test.php @@ -0,0 +1,938 @@ +. + +/** + * Test phpunit_dataset features. + * + * @package core + * @category tests + * @copyright 2020 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types=1); + +use org\bovigo\vfs\vfsStream; + +/** + * Test phpunit_dataset features. + * + * @coversDefaultClass phpunit_dataset + */ +class core_phpunit_dataset_testcase extends advanced_testcase { + + + /** + * @covers ::from_files + */ + public function test_from_files() { + + $ds = new phpunit_dataset(); + + $files = [ + __DIR__ . '/fixtures/sample_dataset.xml', + 'user2' => __DIR__ . '/fixtures/sample_dataset.csv', + ]; + + // We need public properties to check the basis. + $dsref = new ReflectionClass($ds); + $dstables = $dsref->getProperty('tables'); + $dstables->setAccessible(true); + $dscolumns = $dsref->getProperty('columns'); + $dscolumns->setAccessible(true); + $dsrows = $dsref->getProperty('rows'); + $dsrows->setAccessible(true); + + // Expectations. + $exptables = ['user', 'user2']; + $expcolumns = ['id', 'username', 'email']; + $exprows = [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ]; + + $ds->from_files($files); + + $this->assertIsArray($dstables->getValue($ds)); + $this->assertSame($exptables, $dstables->getValue($ds)); + $this->assertIsArray($dscolumns->getValue($ds)); + $this->assertSame($expcolumns, $dscolumns->getValue($ds)['user']); + $this->assertSame($expcolumns, $dscolumns->getValue($ds)['user2']); + $this->assertIsArray($dsrows->getValue($ds)); + $this->assertEquals($exprows, $dsrows->getValue($ds)['user']); // Equals because of stringified integers on load. + $this->assertEquals($exprows, $dsrows->getValue($ds)['user2']); // Equals because of stringified integers on load. + } + + /** + * test_from_file() data provider. + */ + public function from_file_provider() { + // Create an unreadable file with vfsStream. + $vfsfile = vfsStream::newFile('unreadable', 0222); + vfsStream::setup('root')->addChild($vfsfile); + + return [ + 'file not found' => [ + 'fullpath' => '/this/does/not/exist', + 'tablename' => 'user', + 'exception' => 'from_file, file not found: /this/does/not/exist', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'file not readable' => [ + 'fullpath' => $vfsfile->url(), + 'tablename' => 'user', + 'exception' => 'from_file, file not readable: ' . $vfsfile->url(), + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'wrong extension' => [ + 'fullpath' => __DIR__ . '/fixtures/sample_dataset.txt', + 'tablename' => 'user', + 'exception' => 'from_file, cannot handle files with extension: txt', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'csv loads ok' => [ + 'fullpath' => __DIR__ . '/fixtures/sample_dataset.csv', + 'tablename' => 'user', + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'username', 'email'] + ], + 'rows' => ['user' => + [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + 'xml loads ok' => [ + 'fullpath' => __DIR__ . '/fixtures/sample_dataset.xml', + 'tablename' => 'user', + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'username', 'email'] + ], + 'rows' => ['user' => + [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + ]; + } + + /** + * @dataProvider from_file_provider + * @covers ::from_file + */ + public function test_from_file(string $fullpath, string $tablename, ?string $exception, + array $tables, array $columns, array $rows) { + + $ds = new phpunit_dataset(); + + // We need public properties to check the basis. + $dsref = new ReflectionClass($ds); + $dstables = $dsref->getProperty('tables'); + $dstables->setAccessible(true); + $dscolumns = $dsref->getProperty('columns'); + $dscolumns->setAccessible(true); + $dsrows = $dsref->getProperty('rows'); + $dsrows->setAccessible(true); + + // We are expecting an exception. + if (!empty($exception)) { + $this->expectException('coding_exception'); + $this->expectExceptionMessage($exception); + } + + $ds->from_file($fullpath, $tablename); + + $this->assertIsArray($dstables->getValue($ds)); + $this->assertSame($tables, $dstables->getValue($ds)); + $this->assertIsArray($dscolumns->getValue($ds)); + $this->assertSame($columns, $dscolumns->getValue($ds)); + $this->assertIsArray($dsrows->getValue($ds)); + $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load. + } + + /** + * test_from_string() data provider. + */ + public function from_string_provider() { + + return [ + 'wrong type' => [ + 'content' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.xml'), + 'type' => 'txt', + 'tablename' => 'user', + 'exception' => 'from_string, cannot handle contents of type: txt', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'missing cvs table' => [ + 'content' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.csv'), + 'type' => 'csv', + 'tablename' => '', + 'exception' => 'from_string, contents of type "cvs" require a $table to be passed, none found', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'csv loads ok' => [ + 'fullpath' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.csv'), + 'type' => 'csv', + 'tablename' => 'user', + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'username', 'email'] + ], + 'rows' => ['user' => + [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + 'xml loads ok' => [ + 'fullpath' => file_get_contents(__DIR__ . '/fixtures/sample_dataset.xml'), + 'type' => 'xml', + 'tablename' => 'user', + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'username', 'email'] + ], + 'rows' => ['user' => + [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + ]; + } + + /** + * @dataProvider from_string_provider + * @covers ::from_string + */ + public function test_from_string(string $content, string $type, string $tablename, ?string $exception, + array $tables, array $columns, array $rows) { + + $ds = new phpunit_dataset(); + + // We need public properties to check the basis. + $dsref = new ReflectionClass($ds); + $dstables = $dsref->getProperty('tables'); + $dstables->setAccessible(true); + $dscolumns = $dsref->getProperty('columns'); + $dscolumns->setAccessible(true); + $dsrows = $dsref->getProperty('rows'); + $dsrows->setAccessible(true); + + // We are expecting an exception. + if (!empty($exception)) { + $this->expectException('coding_exception'); + $this->expectExceptionMessage($exception); + } + + $ds->from_string($content, $type, $tablename); + + $this->assertIsArray($dstables->getValue($ds)); + $this->assertSame($tables, $dstables->getValue($ds)); + $this->assertIsArray($dscolumns->getValue($ds)); + $this->assertSame($columns, $dscolumns->getValue($ds)); + $this->assertIsArray($dsrows->getValue($ds)); + $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load. + } + + /** + * test_from_array() data provider. + */ + public function from_array_provider() { + return [ + 'repeated array table many structures' => [ + 'structure' => [ + 'user' => [ + ['id' => 5, 'name' => 'John'], + ['id' => 6, 'name' => 'Jane'], + ], + ], + 'exception' => 'from_array, table already added to dataset: user', + 'tables' => [], + 'columns' => [], + 'rows' => [], + 'repeated' => true, // To force the table already exists exception. + ], + 'wrong number of columns' => [ + 'structure' => [ + 'user' => [ + ['id' => 5, 'name' => 'John'], + ['id' => 6], + ], + ], + 'exception' => 'from_array, number of columns must match number of values, found: 2 vs 1', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'wrong not matching names of columns' => [ + 'structure' => [ + 'user' => [ + ['id' => 5, 'name' => 'John'], + ['id' => 6, 'noname' => 'Jane'], + ], + ], + 'exception' => 'from_array, columns in all elements must match first one, found: id, noname', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'ok non-associative format' => [ + 'structure' => [ + 'user' => [ + ['id', 'name'], + [5, 'John'], + [6, 'Jane'], + ], + ], + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'name'], + ], + 'rows' => ['user' => + [ + ['id' => 5, 'name' => 'John'], + ['id' => 6, 'name' => 'Jane'], + ], + ], + ], + 'ok associative format' => [ + 'structure' => [ + 'user' => [ + ['id' => 5, 'name' => 'John'], + ['id' => 6, 'name' => 'Jane'], + ], + ], + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'name'], + ], + 'rows' => ['user' => + [ + ['id' => 5, 'name' => 'John'], + ['id' => 6, 'name' => 'Jane'], + ], + ], + ], + 'ok multiple' => [ + 'structure' => [ + 'user' => [ + ['id' => 5, 'name' => 'John'], + ['id' => 6, 'name' => 'Jane'], + ], + 'course' => [ + ['id' => 7, 'name' => '101'], + ['id' => 8, 'name' => '102'], + ], + ], + 'exception' => null, + 'tables' => ['user', 'course'], + 'columns' => [ + 'user' => ['id', 'name'], + 'course' => ['id', 'name'], + ], + 'rows' => [ + 'user' => [ + ['id' => 5, 'name' => 'John'], + ['id' => 6, 'name' => 'Jane'], + ], + 'course' => [ + ['id' => 7, 'name' => '101'], + ['id' => 8, 'name' => '102'], + ], + ], + ], + ]; + } + + /** + * @dataProvider from_array_provider + * @covers ::from_array + */ + public function test_from_array(array $structure, ?string $exception, + array $tables, array $columns, array $rows, ?bool $repeated = false) { + + $ds = new phpunit_dataset(); + + // We need public properties to check the basis. + $dsref = new ReflectionClass($ds); + $dstables = $dsref->getProperty('tables'); + $dstables->setAccessible(true); + $dscolumns = $dsref->getProperty('columns'); + $dscolumns->setAccessible(true); + $dsrows = $dsref->getProperty('rows'); + $dsrows->setAccessible(true); + + // We are expecting an exception. + if (!empty($exception)) { + $this->expectException('coding_exception'); + $this->expectExceptionMessage($exception); + } + + $ds->from_array($structure); + if ($repeated) { + $ds->from_array($structure); + } + + $this->assertIsArray($dstables->getValue($ds)); + $this->assertSame($tables, $dstables->getValue($ds)); + $this->assertIsArray($dscolumns->getValue($ds)); + $this->assertSame($columns, $dscolumns->getValue($ds)); + $this->assertIsArray($dsrows->getValue($ds)); + $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load. + } + + /** + * test_load_csv() data provider. + */ + public function load_csv_provider() { + + return [ + 'repeated csv table many files' => [ + 'files' => [ + __DIR__ . '/fixtures/sample_dataset.xml', + 'user' => __DIR__ . '/fixtures/sample_dataset.csv', + ], + 'exception' => 'csv_dataset_format, table already added to dataset: user', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'ok one csv file' => [ + 'files' => [ + 'user' => __DIR__ . '/fixtures/sample_dataset.csv', + ], + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'username', 'email'] + ], + 'rows' => ['user' => + [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + 'ok multiple csv files' => [ + 'files' => [ + 'user1' => __DIR__ . '/fixtures/sample_dataset.csv', + 'user2' => __DIR__ . '/fixtures/sample_dataset.csv', + ], + 'exception' => null, + 'tables' => ['user1', 'user2'], + 'columns' => [ + 'user1' => ['id', 'username', 'email'], + 'user2' => ['id', 'username', 'email'], + ], + 'rows' => [ + 'user1' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'user2' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + ], + ], + ]; + } + + /** + * @dataProvider load_csv_provider + * @covers ::load_csv + */ + public function test_load_csv(array $files, ?string $exception, + array $tables, array $columns, array $rows) { + + $ds = new phpunit_dataset(); + + // We need public properties to check the basis. + $dsref = new ReflectionClass($ds); + $dstables = $dsref->getProperty('tables'); + $dstables->setAccessible(true); + $dscolumns = $dsref->getProperty('columns'); + $dscolumns->setAccessible(true); + $dsrows = $dsref->getProperty('rows'); + $dsrows->setAccessible(true); + + // We are expecting an exception. + if (!empty($exception)) { + $this->expectException('coding_exception'); + $this->expectExceptionMessage($exception); + } + + $ds->from_files($files); + + $this->assertIsArray($dstables->getValue($ds)); + $this->assertSame($tables, $dstables->getValue($ds)); + $this->assertIsArray($dscolumns->getValue($ds)); + $this->assertSame($columns, $dscolumns->getValue($ds)); + $this->assertIsArray($dsrows->getValue($ds)); + $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load. + } + + /** + * test_load_xml() data provider. + */ + public function load_xml_provider() { + + return [ + 'repeated xml table multiple files' => [ + 'files' => [ + 'user' => __DIR__ . '/fixtures/sample_dataset.csv', + __DIR__ . '/fixtures/sample_dataset.xml', + ], + 'exception' => 'xml_dataset_format, table already added to dataset: user', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'repeated xml table one file' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_repeated.xml'], + 'exception' => 'xml_dataset_format, table already added to dataset: user', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'wrong dataset element' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_dataset.xml'], + 'exception' => 'xml_dataset_format, main xml element must be "dataset", found: nodataset', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'wrong table element' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_table.xml'], + 'exception' => 'xml_dataset_format, only "table" elements allowed, found: notable', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'wrong table name attribute' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_attribute.xml'], + 'exception' => 'xml_dataset_format, "table" element only allows "name" attribute', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'only col and row allowed' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_only_colrow.xml'], + 'exception' => 'xml_dataset_format, only "column or "row" elements allowed, found: nocolumn', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'wrong value element' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_wrong_value.xml'], + 'exception' => 'xml_dataset_format, only "value" elements allowed, found: novalue', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'column before row' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_col_before_row.xml'], + 'exception' => 'xml_dataset_format, "column" elements always must be before "row" ones', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'row after column' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_row_after_col.xml'], + 'exception' => 'xml_dataset_format, "row" elements always must be after "column" ones', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'number of columns' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_number_of_columns.xml'], + 'exception' => 'xml_dataset_format, number of columns must match number of values, found: 4 vs 3', + 'tables' => [], + 'columns' => [], + 'rows' => [], + ], + 'ok one xml file' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset.xml'], + 'exception' => null, + 'tables' => ['user'], + 'columns' => ['user' => + ['id', 'username', 'email'] + ], + 'rows' => ['user' => + [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + 'ok multiple xml files' => [ + 'files' => [ + 'user1' => __DIR__ . '/fixtures/sample_dataset.csv', + __DIR__ . '/fixtures/sample_dataset.xml', + ], + 'exception' => null, + 'tables' => ['user1', 'user'], + 'columns' => [ + 'user1' => ['id', 'username', 'email'], + 'user' => ['id', 'username', 'email'], + ], + 'rows' => [ + 'user1' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'user' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + ], + ], + 'ok many tables in one xml' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'], + 'exception' => null, + 'tables' => ['user', 'course'], + 'columns' => [ + 'user' => ['id', 'username', 'email'], + 'course' => ['id', 'shortname', 'fullname'], + ], + 'rows' => [ + 'user' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'course' => [ + ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'], + ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'], + ], + ], + ], + ]; + } + + /** + * @dataProvider load_xml_provider + * @covers ::load_xml + */ + public function test_load_xml(array $files, ?string $exception, + array $tables, array $columns, array $rows) { + + $ds = new phpunit_dataset(); + + // We need public properties to check the basis. + $dsref = new ReflectionClass($ds); + $dstables = $dsref->getProperty('tables'); + $dstables->setAccessible(true); + $dscolumns = $dsref->getProperty('columns'); + $dscolumns->setAccessible(true); + $dsrows = $dsref->getProperty('rows'); + $dsrows->setAccessible(true); + + // We are expecting an exception. + if (!empty($exception)) { + $this->expectException('coding_exception'); + $this->expectExceptionMessage($exception); + } + + $ds->from_files($files); + + $this->assertIsArray($dstables->getValue($ds)); + $this->assertSame($tables, $dstables->getValue($ds)); + $this->assertIsArray($dscolumns->getValue($ds)); + $this->assertSame($columns, $dscolumns->getValue($ds)); + $this->assertIsArray($dsrows->getValue($ds)); + $this->assertEquals($rows, $dsrows->getValue($ds)); // Equals because of stringified integers on load. + } + + /** + * test_to_database() data provider. + */ + public function to_database_provider() { + + return [ + 'wrong table requested' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_insert.xml'], + 'filter' => ['wrongtable'], + 'exception' => 'dataset_to_database, table is not in the dataset: wrongtable', + 'columns' => [], + 'rows' => [], + ], + 'one table insert' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_insert.xml'], + 'filter' => [], + 'exception' => null, + 'columns' => [ + 'user' => ['username', 'email'], + ], + 'rows' => ['user' => + [ + (object)['username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + (object)['username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + 'one table import' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset.xml'], + 'filter' => [], + 'exception' => null, + 'columns' => [ + 'user' => ['id', 'username', 'email'], + ], + 'rows' => ['user' => + [ + (object)['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + (object)['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ] + ], + ], + 'multiple table many files import' => [ + 'files' => [ + __DIR__ . '/fixtures/sample_dataset.xml', + __DIR__ . '/fixtures/sample_dataset2.xml', + ], + 'filter' => [], + 'exception' => null, + 'columns' => [ + 'user' => ['id', 'username', 'email'], + 'course' => ['id', 'shortname', 'fullname'], + ], + 'rows' => [ + 'user' => [ + (object)['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + (object)['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'course' => [ + (object)['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'], + (object)['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'], + ], + ], + ], + 'multiple table one file import' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'], + 'filter' => [], + 'exception' => null, + 'columns' => [ + 'user' => ['id', 'username', 'email'], + 'course' => ['id', 'shortname', 'fullname'], + ], + 'rows' => [ + 'user' => [ + (object)['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + (object)['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'course' => [ + (object)['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'], + (object)['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'], + ], + ], + ], + 'filtering tables' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'], + 'filter' => ['course'], + 'exception' => null, + 'columns' => [ + 'user' => ['id', 'username', 'email'], + 'course' => ['id', 'shortname', 'fullname'], + ], + 'rows' => [ + 'user' => [], // Table user is being excluded via filter, expect no rows sent to database. + 'course' => [ + (object)['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'], + (object)['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'], + ], + ], + ], + ]; + } + + /** + * @dataProvider to_database_provider + * @covers ::to_database + */ + public function test_to_database(array $files, ?array $filter = [], ?string $exception, array $columns, array $rows) { + global $DB; + + $this->resetAfterTest(); + + // Grab the status before loading to database. + $before = []; + foreach ($columns as $tablename => $tablecolumns) { + if (!isset($before[$tablename])) { + $before[$tablename] = []; + } + $before[$tablename] = $DB->get_records($tablename, null, '', implode(', ', $tablecolumns)); + } + + $ds = new phpunit_dataset(); + + // We are expecting an exception. + if (!empty($exception)) { + $this->expectException('coding_exception'); + $this->expectExceptionMessage($exception); + } + + $ds->from_files($files); + $ds->to_database($filter); + + // Grab the status after loading to database. + $after = []; + foreach ($columns as $tablename => $tablecolumns) { + if (!isset($after[$tablename])) { + $after[$tablename] = []; + } + $sortandcol = implode(', ', $tablecolumns); + $after[$tablename] = $DB->get_records($tablename, null, $sortandcol, $sortandcol); + } + + // Differences must match the expectations. + foreach ($rows as $tablename => $expectedrows) { + $changes = array_udiff($after[$tablename], $before[$tablename], function ($b, $a) { + if ((array)$b > (array)$a) { + return 1; + } else if ((array)$b < (array)$a) { + return -1; + } else { + return 0; + } + }); + $this->assertEquals(array_values($expectedrows), array_values($changes)); + } + } + + /** + * test_get_rows() data provider. + */ + public function get_rows_provider() { + + return [ + 'wrong table requested' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many.xml'], + 'filter' => ['wrongtable'], + 'exception' => 'dataset_get_rows, table is not in the dataset: wrongtable', + 'rows' => [], + ], + 'ok get rows from empty tables' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'], + 'filter' => ['empty1', 'empty2'], + 'exception' => null, + 'rows' => [ + 'empty1' => [], + 'empty2' => [], + ], + ], + 'ok get rows from one table' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'], + 'filter' => ['user'], + 'exception' => null, + 'rows' => [ + 'user' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + ], + ], + 'ok get rows from two tables' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'], + 'filter' => ['user', 'course'], + 'exception' => null, + 'rows' => [ + 'user' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'course' => [ + ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'], + ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'], + ], + ], + ], + 'ok get rows from three tables' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'], + 'filter' => ['user', 'empty1', 'course'], + 'exception' => null, + 'rows' => [ + 'user' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'empty1' => [], + 'course' => [ + ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'], + ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'], + ], + ], + ], + 'ok no filter returns all' => [ + 'files' => [__DIR__ . '/fixtures/sample_dataset_many_with_empty.xml'], + 'filter' => [], + 'exception' => null, + 'rows' => [ + 'user' => [ + ['id' => 5, 'username' => 'bozka.novakova', 'email' => 'bozka@example.com'], + ['id' => 7, 'username' => 'pepa.novak', 'email' => 'pepa@example.com'], + ], + 'empty1' => [], + 'empty2' => [], + 'course' => [ + ['id' => 6, 'shortname' => '101', 'fullname' => '1-0-1'], + ['id' => 8, 'shortname' => '202', 'fullname' => '2-0-2'], + ], + ], + ], + ]; + } + + /** + * @dataProvider get_rows_provider + * @covers ::get_rows + */ + public function test_get_rows(array $files, array $filter, ?string $exception, array $rows) { + + $ds = new phpunit_dataset(); + + // We are expecting an exception. + if (!empty($exception)) { + $this->expectException('coding_exception'); + $this->expectExceptionMessage($exception); + } + + $ds->from_files($files); + $this->assertEquals($rows, $ds->get_rows($filter)); + } +} -- 2.43.0