diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..2c1bae6
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,11 @@
+Tests export-ignore
+.github export-ignore
+
+.gitattributes export-ignore
+.gitignore export-ignore
+
+.php-cs-fixer.dist.php export-ignore
+phpstan.neon export-ignore
+phpunit.xml.dist export-ignore
+
+shell.nix export-ignore
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..12c1ffd
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,190 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ check-composer:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.2
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Validate composer.json
+ run: composer validate
+
+ php-linting:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php-version:
+ - 7.3
+ - 7.4
+ - 8.0
+ - 8.1
+ - 8.2
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+
+ - name: PHP lint
+ run: "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l"
+
+ xml-linting:
+ runs-on: ubuntu-latest
+ needs:
+ - check-composer
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "8.2"
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install xmllint
+ run: sudo apt-get install libxml2-utils
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress
+
+ - name: PHPUnit configuration file
+ run: xmllint --schema vendor/phpunit/phpunit/phpunit.xsd --noout phpunit.xml.dist
+
+ coding-guideline:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "8.1"
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress
+
+ - name: Coding Guideline
+ run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
+
+ tests-mysql:
+ runs-on: ubuntu-latest
+ needs:
+ - xml-linting
+ strategy:
+ matrix:
+ include:
+ - db-version: '5.7'
+ php-version: '7.3'
+ typo3-version: '^10.4'
+ - db-version: '8'
+ php-version: '7.4'
+ typo3-version: '^10.4'
+ - db-version: '8'
+ php-version: '7.4'
+ typo3-version: '^11.5'
+ - db-version: '8'
+ php-version: '8.0'
+ typo3-version: '^11.5'
+ - db-version: '8'
+ php-version: '8.1'
+ typo3-version: '^11.5'
+ - db-version: '8'
+ php-version: '8.2'
+ typo3-version: '^11.5'
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup MySQL
+ uses: mirromutth/mysql-action@v1.1
+ with:
+ mysql version: '${{ matrix.db-version }}'
+ mysql database: 'typo3'
+ mysql root password: 'root'
+
+ - name: Wait for MySQL
+ run: |
+ while ! mysqladmin ping --host=127.0.0.1 --password=root --silent; do
+ sleep 1
+ done
+
+ - name: Install dependencies with expected TYPO3 version
+ run: composer require --prefer-dist --no-progress "typo3/cms-core:${{ matrix.typo3-version }}"
+
+ - name: PHPUnit Tests
+ run: |-
+ export typo3DatabaseDriver="pdo_mysql"
+ export typo3DatabaseName="typo3"
+ export typo3DatabaseHost="127.0.0.1"
+ export typo3DatabaseUsername="root"
+ export typo3DatabasePassword="root"
+ ./vendor/bin/phpunit --testdox
+
+ code-quality:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ - php-version: '7.3'
+ typo3-version: '^10.4'
+ - php-version: '7.4'
+ typo3-version: '^10.4'
+ - php-version: '7.4'
+ typo3-version: '^11.5'
+ - php-version: '8.0'
+ typo3-version: '^11.5'
+ - php-version: '8.1'
+ typo3-version: '^11.5'
+ - php-version: '8.2'
+ typo3-version: '^11.5'
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install dependencies with expected TYPO3 version
+ run: composer require --prefer-dist --no-progress "typo3/cms-backend:${{ matrix.typo3-version }}" "typo3/cms-core:${{ matrix.typo3-version }}" "typo3/cms-dashboard:${{ matrix.typo3-version }}"
+
+ - name: Code Quality (by PHPStan)
+ run: ./vendor/bin/phpstan analyse
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..51313d0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/.Build/
+/composer.lock
+/vendor/
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..80e0228
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,62 @@
+ignoreVCSIgnored(true)
+ ->in(realpath(__DIR__));
+
+return (new \PhpCsFixer\Config())
+ ->setRiskyAllowed(true)
+ ->setRules([
+ '@DoctrineAnnotation' => true,
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'blank_line_after_opening_tag' => true,
+ 'braces' => ['allow_single_line_closure' => true],
+ 'cast_spaces' => ['space' => 'none'],
+ 'compact_nullable_typehint' => true,
+ 'concat_space' => ['spacing' => 'one'],
+ 'declare_equal_normalize' => ['space' => 'none'],
+ 'dir_constant' => true,
+ 'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']],
+ 'function_typehint_space' => true,
+ 'lowercase_cast' => true,
+ 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
+ 'modernize_strpos' => true,
+ 'modernize_types_casting' => true,
+ 'native_function_casing' => true,
+ 'new_with_braces' => true,
+ 'no_alias_functions' => true,
+ 'no_blank_lines_after_phpdoc' => true,
+ 'no_empty_phpdoc' => true,
+ 'no_empty_statement' => true,
+ 'no_extra_blank_lines' => true,
+ 'no_leading_import_slash' => true,
+ 'no_leading_namespace_whitespace' => true,
+ 'no_null_property_initialization' => true,
+ 'no_short_bool_cast' => true,
+ 'no_singleline_whitespace_before_semicolons' => true,
+ 'no_superfluous_elseif' => true,
+ 'no_trailing_comma_in_singleline_array' => true,
+ 'no_unneeded_control_parentheses' => true,
+ 'no_unused_imports' => true,
+ 'no_useless_else' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'ordered_imports' => true,
+ 'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']],
+ 'php_unit_mock_short_will_return' => true,
+ 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+ 'phpdoc_no_access' => true,
+ 'phpdoc_no_empty_return' => true,
+ 'phpdoc_no_package' => true,
+ 'phpdoc_scalar' => true,
+ 'phpdoc_trim' => true,
+ 'phpdoc_types' => true,
+ 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
+ 'return_type_declaration' => ['space_before' => 'none'],
+ 'single_quote' => true,
+ 'single_line_comment_style' => ['comment_types' => ['hash']],
+ 'single_trait_insert_per_statement' => true,
+ 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
+ 'whitespace_after_comma_in_array' => true,
+ 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false],
+ ])
+ ->setFinder($finder);
diff --git a/Classes/PhpDataSet.php b/Classes/PhpDataSet.php
new file mode 100644
index 0000000..795e833
--- /dev/null
+++ b/Classes/PhpDataSet.php
@@ -0,0 +1,52 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace Codappix\Typo3PhpDatasets;
+
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+class PhpDataSet
+{
+ public function import(array $dataSet): void
+ {
+ foreach ($dataSet as $tableName => $records) {
+ $connection = $this->getConnectionPool()->getConnectionForTable($tableName);
+ $tableDetails = $connection->getSchemaManager()->listTableDetails($tableName);
+
+ foreach ($records as $record) {
+ $types = [];
+ foreach (array_keys($record) as $columnName) {
+ $types[] = $tableDetails->getColumn((string)$columnName)->getType()->getBindingType();
+ }
+
+ $connection->insert($tableName, $record, $types);
+ }
+ }
+ }
+
+ private function getConnectionPool(): ConnectionPool
+ {
+ return GeneralUtility::makeInstance(ConnectionPool::class);
+ }
+}
diff --git a/Classes/TestingFramework.php b/Classes/TestingFramework.php
new file mode 100644
index 0000000..cd8a7b7
--- /dev/null
+++ b/Classes/TestingFramework.php
@@ -0,0 +1,104 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace Codappix\Typo3PhpDatasets;
+
+use Doctrine\DBAL\DBALException;
+use InvalidArgumentException;
+
+/**
+ * Only use this within an FunctionalTestCase.
+ */
+trait TestingFramework
+{
+ protected function importPHPDataSet(string $filePath): void
+ {
+ $this->ensureFileExists($filePath);
+
+ $dataSet = include $filePath;
+ try {
+ (new PhpDataSet())->import($dataSet);
+ } catch (DBALException $e) {
+ self::fail('SQL Error for PHP data-set "' . $filePath . '":' . PHP_EOL . $e->getMessage());
+ }
+ }
+
+ /**
+ * Highly inspired by TYPO3 testing framework.
+ */
+ protected function assertPHPDataSet(string $filePath): void
+ {
+ $this->ensureFileExists($filePath);
+
+ $dataSet = include $filePath;
+ $failMessages = [];
+
+ foreach ($dataSet as $tableName => $expectedRecords) {
+ $records = $this->getAllRecords($tableName, true);
+
+ foreach ($expectedRecords as $assertion) {
+ $result = $this->assertInRecords($assertion, $records);
+ if ($result === false) {
+ // Handle error
+ if (isset($assertion['uid']) && empty($records[$assertion['uid']])) {
+ $failMessages[] = 'Record "' . $tableName . ':' . $assertion['uid'] . '" not found in database';
+ continue;
+ }
+ if (isset($assertion['uid'])) {
+ $recordIdentifier = $tableName . ':' . $assertion['uid'];
+ $additionalInformation = $this->renderRecords($assertion, $records[$assertion['uid']]);
+ } else {
+ $recordIdentifier = $tableName;
+ $additionalInformation = $this->arrayToString($assertion);
+ }
+
+ $failMessages[] = 'Assertion in data-set failed for "' . $recordIdentifier . '":' . PHP_EOL . $additionalInformation;
+ continue;
+ }
+
+ // Unset asserted record
+ unset($records[$result]);
+ // Increase assertion counter
+ self::assertTrue($result !== false);
+ }
+
+ if (!empty($records)) {
+ foreach ($records as $record) {
+ $recordIdentifier = $tableName . ':' . $record['uid'];
+ $failMessages[] = 'Not asserted record found for "' . $recordIdentifier . '".';
+ }
+ }
+ }
+
+ if (!empty($failMessages)) {
+ self::fail(implode(PHP_EOL, $failMessages));
+ }
+ }
+
+ private function ensureFileExists(string $filePath): void
+ {
+ if (file_exists($filePath) === false) {
+ throw new InvalidArgumentException('The requested PHP data-set file "' . $filePath . '" does not exist.', 1681207108);
+ }
+ }
+}
diff --git a/README.md b/README.md
deleted file mode 100644
index 032cc6c..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# typo3-testing-framework-php-datasets
-Enables usage of PHP instead of XML or CSV for datasets
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..290d872
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,78 @@
+PHP DataSets for TYPO3
+======================
+
+Provides APIs to use data sets written as PHP arrays with TYPO3.
+
+Why
+===
+
+We don't like the approach of TYPO3 Testing Framework regarding DataSets.
+
+We have the following issues:
+
+#. XML is only supported for imports, not for assertions
+
+#. CSV is a bad format that already got hacked, e.g. ``#`` to indicate comments.
+ We consider it bad as one needs special toolings in order to properly write CSV files, they are not human readable.
+
+That's why we prefer PHP files instead. That way developers are free to use whatever
+they want. Either plain PHP or even YAML or other formats. They are not forced to
+anything but can stick to their known tooling.
+
+We also have situations where we wanna have static database records on production
+that are maintained by PHP data sets and update wizards.
+
+So this package should in general handle PHP data sets for TYPO3.
+It should ease the abstract usage by providing convenient integrations for general
+use cases, like the testing framework.
+
+Usage
+=====
+
+See our own tests for how to use, as they do nothing else.
+
+Within testing framework
+------------------------
+
+#. Create data set
+
+ A data set is a PHP file that returns an array of tables with their records.
+ Format is:
+
+ .. code-block:: php
+
+ return [
+ 'table_name' => [
+ // Records
+ [
+ // column_name => value
+ 'uid' => 1,
+ ],
+ ],
+ ];
+
+#. Add API
+
+ .. code-block:: php
+
+ use Codappix\Typo3PhpDatasets\TestingFramework;
+
+#. Use API
+
+ Import:
+
+ .. code-block:: php
+
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/SimpleSet.php');
+
+ Assert:
+
+ .. code-block:: php
+
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/SimpleSet.php');
+
+TODO
+====
+
+#. Implement use case to check for necessary updates and allow updates.
+ Use for static data during deployment within update wizards or other scripts.
diff --git a/Tests/Functional/AbstractFunctionalTestCase.php b/Tests/Functional/AbstractFunctionalTestCase.php
new file mode 100644
index 0000000..2d76629
--- /dev/null
+++ b/Tests/Functional/AbstractFunctionalTestCase.php
@@ -0,0 +1,32 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace Codappix\Typo3PhpDatasets\Tests\Functional;
+
+use Codappix\Typo3PhpDatasets\TestingFramework;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+abstract class AbstractFunctionalTestCase extends FunctionalTestCase
+{
+ use TestingFramework;
+}
diff --git a/Tests/Functional/AssertTest.php b/Tests/Functional/AssertTest.php
new file mode 100644
index 0000000..6d6f0e3
--- /dev/null
+++ b/Tests/Functional/AssertTest.php
@@ -0,0 +1,131 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace Codappix\Typo3PhpDatasets\Tests\Functional;
+
+use InvalidArgumentException;
+use PHPUnit\Framework\AssertionFailedError;
+
+/**
+ * @covers \Codappix\Typo3PhpDatasets\PhpDataSet
+ * @covers \Codappix\Typo3PhpDatasets\TestingFramework
+ * @testdox The Testing Framework trait
+ */
+class AssertTest extends AbstractFunctionalTestCase
+{
+ /**
+ * @test
+ */
+ public function canAssertAgainstSimpleSet(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/SimpleSet.php');
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/SimpleSet.php');
+ }
+
+ /**
+ * @test
+ */
+ public function canAssertAgainstNullValue(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/WithNull.php');
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/WithNull.php');
+ }
+
+ /**
+ * @test
+ */
+ public function canAssertAgainstDifferentSetOfColumns(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/WithDifferentColumns.php');
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/WithDifferentColumns.php');
+ }
+
+ /**
+ * @test
+ */
+ public function failsForMissingAssertionWithUid(): void
+ {
+ $this->expectException(AssertionFailedError::class);
+ $this->expectExceptionMessage('Record "pages:1" not found in database');
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/AssertSimpleMissingUidSet.php');
+ }
+
+ /**
+ * @test
+ */
+ public function failsForDifferingAssertionWithUid(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/SimpleSet.php');
+
+ $this->expectException(AssertionFailedError::class);
+ $this->expectExceptionMessage(
+ 'Assertion in data-set failed for "pages:1":'
+ . PHP_EOL
+ . 'Fields|Assertion |Record '
+ . PHP_EOL
+ . 'title |Rootpage without match|Rootpage'
+ . PHP_EOL
+ );
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/AssertDifferingWithUid.php');
+ }
+
+ /**
+ * @test
+ */
+ public function failsForAssertionWithoutUid(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/SimpleSet.php');
+
+ $this->expectException(AssertionFailedError::class);
+ $this->expectExceptionMessage(implode(PHP_EOL, [
+ 'Assertion in data-set failed for "pages":',
+ 'array(',
+ ' \'pid\' => \'0\', ',
+ ' \'title\' => \'Rootpage without match\'',
+ ')',
+ ]) . PHP_EOL);
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/AssertDifferingWithoutUid.php');
+ }
+
+ /**
+ * @test
+ */
+ public function failsForAdditionalNoneAssertedRecords(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/WithDifferentColumns.php');
+
+ $this->expectException(AssertionFailedError::class);
+ $this->expectExceptionMessage('Not asserted record found for "pages:2".');
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/AssertAdditionalRecords.php');
+ }
+
+ /**
+ * @test
+ */
+ public function throwsExceptionIfFileDoesNotExist(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('The requested PHP data-set file "' . __DIR__ . '/Fixtures/DoesNotExist.php' . '" does not exist.');
+ $this->assertPHPDataSet(__DIR__ . '/Fixtures/DoesNotExist.php');
+ }
+}
diff --git a/Tests/Functional/Fixtures/AssertAdditionalRecords.php b/Tests/Functional/Fixtures/AssertAdditionalRecords.php
new file mode 100644
index 0000000..f872947
--- /dev/null
+++ b/Tests/Functional/Fixtures/AssertAdditionalRecords.php
@@ -0,0 +1,12 @@
+ [
+ [
+ 'uid' => 1,
+ 'pid' => 0,
+ 'is_siteroot' => 1,
+ 'title' => 'Rootpage',
+ ],
+ ],
+];
diff --git a/Tests/Functional/Fixtures/AssertDifferingWithUid.php b/Tests/Functional/Fixtures/AssertDifferingWithUid.php
new file mode 100644
index 0000000..76bc0ba
--- /dev/null
+++ b/Tests/Functional/Fixtures/AssertDifferingWithUid.php
@@ -0,0 +1,13 @@
+ [
+ [
+ 'uid' => 1,
+ 'pid' => 0,
+ 'is_siteroot' => 1,
+ 'title' => 'Rootpage without match',
+ 'description' => 'Some text',
+ ],
+ ],
+];
diff --git a/Tests/Functional/Fixtures/AssertDifferingWithoutUid.php b/Tests/Functional/Fixtures/AssertDifferingWithoutUid.php
new file mode 100644
index 0000000..6b4d119
--- /dev/null
+++ b/Tests/Functional/Fixtures/AssertDifferingWithoutUid.php
@@ -0,0 +1,10 @@
+ [
+ [
+ 'pid' => 0,
+ 'title' => 'Rootpage without match',
+ ],
+ ],
+];
diff --git a/Tests/Functional/Fixtures/AssertSimpleMissingUidSet.php b/Tests/Functional/Fixtures/AssertSimpleMissingUidSet.php
new file mode 100644
index 0000000..741a2be
--- /dev/null
+++ b/Tests/Functional/Fixtures/AssertSimpleMissingUidSet.php
@@ -0,0 +1,13 @@
+ [
+ [
+ 'uid' => 1,
+ 'pid' => 0,
+ 'is_siteroot' => 1,
+ 'title' => 'Rootpage',
+ 'description' => 'Some text',
+ ],
+ ],
+];
diff --git a/Tests/Functional/Fixtures/SimpleSet.php b/Tests/Functional/Fixtures/SimpleSet.php
new file mode 100644
index 0000000..741a2be
--- /dev/null
+++ b/Tests/Functional/Fixtures/SimpleSet.php
@@ -0,0 +1,13 @@
+ [
+ [
+ 'uid' => 1,
+ 'pid' => 0,
+ 'is_siteroot' => 1,
+ 'title' => 'Rootpage',
+ 'description' => 'Some text',
+ ],
+ ],
+];
diff --git a/Tests/Functional/Fixtures/WithBrokenSql.php b/Tests/Functional/Fixtures/WithBrokenSql.php
new file mode 100644
index 0000000..5ae78ed
--- /dev/null
+++ b/Tests/Functional/Fixtures/WithBrokenSql.php
@@ -0,0 +1,9 @@
+ [
+ [
+ 'none_existing_column' => 1,
+ ],
+ ],
+];
diff --git a/Tests/Functional/Fixtures/WithDifferentColumns.php b/Tests/Functional/Fixtures/WithDifferentColumns.php
new file mode 100644
index 0000000..5b3c835
--- /dev/null
+++ b/Tests/Functional/Fixtures/WithDifferentColumns.php
@@ -0,0 +1,18 @@
+ [
+ [
+ 'uid' => 1,
+ 'pid' => 0,
+ 'is_siteroot' => 1,
+ 'title' => 'Rootpage',
+ ],
+ [
+ 'uid' => 2,
+ 'pid' => 0,
+ 'title' => 'Another Page',
+ 'description' => 'Some other text',
+ ],
+ ],
+];
diff --git a/Tests/Functional/Fixtures/WithNull.php b/Tests/Functional/Fixtures/WithNull.php
new file mode 100644
index 0000000..37c27e5
--- /dev/null
+++ b/Tests/Functional/Fixtures/WithNull.php
@@ -0,0 +1,12 @@
+ [
+ [
+ 'uid' => 1,
+ 'pid' => 0,
+ 'is_siteroot' => 1,
+ 'description' => null,
+ ],
+ ],
+];
diff --git a/Tests/Functional/ImportTest.php b/Tests/Functional/ImportTest.php
new file mode 100644
index 0000000..59cf918
--- /dev/null
+++ b/Tests/Functional/ImportTest.php
@@ -0,0 +1,99 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace Codappix\Typo3PhpDatasets\Tests\Functional;
+
+use InvalidArgumentException;
+use PHPUnit\Framework\AssertionFailedError;
+
+/**
+ * @covers \Codappix\Typo3PhpDatasets\PhpDataSet
+ * @covers \Codappix\Typo3PhpDatasets\TestingFramework
+ * @testdox The Testing Framework trait
+ */
+class ImportTest extends AbstractFunctionalTestCase
+{
+ /**
+ * @test
+ */
+ public function canImportSimpleSet(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/SimpleSet.php');
+
+ $records = $this->getAllRecords('pages', true);
+ self::assertCount(1, $records);
+ self::assertIsArray($records[1]);
+ self::assertSame('Rootpage', $records[1]['title']);
+ self::assertSame('Some text', $records[1]['description']);
+ }
+
+ /**
+ * @test
+ */
+ public function canImportWithNullValue(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/WithNull.php');
+
+ $records = $this->getAllRecords('pages', true);
+ self::assertNull($records[1]['description']);
+ }
+
+ /**
+ * @test
+ */
+ public function canImportRecordsWithDifferentSetOfColumns(): void
+ {
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/WithDifferentColumns.php');
+
+ $records = $this->getAllRecords('pages', true);
+ self::assertCount(2, $records);
+ self::assertIsArray($records[1]);
+ self::assertSame('Rootpage', $records[1]['title']);
+ self::assertIsArray($records[2]);
+ self::assertSame('Some other text', $records[2]['description']);
+ }
+
+ /**
+ * @test
+ */
+ public function failsIfSqlError(): void
+ {
+ $this->expectException(AssertionFailedError::class);
+ $this->expectExceptionMessage(
+ 'SQL Error for PHP data-set "' . __DIR__ . '/Fixtures/WithBrokenSql.php":'
+ . PHP_EOL
+ . 'There is no column with name \'none_existing_column\' on table \'pages\'.'
+ );
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/WithBrokenSql.php');
+ }
+
+ /**
+ * @test
+ */
+ public function throwsExceptionIfFileDoesNotExist(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('The requested PHP data-set file "' . __DIR__ . '/Fixtures/DoesNotExist.php' . '" does not exist.');
+ $this->importPHPDataSet(__DIR__ . '/Fixtures/DoesNotExist.php');
+ }
+}
diff --git a/composer.json b/composer.json
index e6c8dd4..41b2e20 100644
--- a/composer.json
+++ b/composer.json
@@ -8,6 +8,11 @@
"Codappix\\Typo3PhpDatasets\\": "Classes/"
}
},
+ "autoload-dev": {
+ "psr-4": {
+ "Codappix\\Typo3PhpDatasets\\Tests\\": "Tests/"
+ }
+ },
"authors": [
{
"name": "Daniel Siepmann",
@@ -18,5 +23,24 @@
"php": "^7.3 || ^7.4 || ^8.0 || ^8.1 || ^8.2",
"doctrine/dbal": "^2.13",
"typo3/cms-core": "^10.4 || ^11.5"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.4",
+ "phpstan/phpstan": "^1.10",
+ "phpstan/phpstan-phpunit": "^1.3",
+ "phpunit/phpunit": "^9.6",
+ "typo3/testing-framework": "^6.16"
+ },
+ "extra": {
+ "typo3/cms": {
+ "app-dir": ".Build",
+ "web-dir": ".Build/web"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "typo3/class-alias-loader": true,
+ "typo3/cms-composer-installers": true
+ }
}
}
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000..30dd237
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,6 @@
+parameters:
+ ignoreErrors:
+ -
+ message: "#^Method Codappix\\\\Typo3PhpDatasets\\\\PhpDataSet\\:\\:getConnectionPool\\(\\) should return TYPO3\\\\CMS\\\\Core\\\\Database\\\\ConnectionPool but returns object\\.$#"
+ count: 1
+ path: Classes/PhpDataSet.php
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..a97e54c
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,10 @@
+includes:
+ - phpstan-baseline.neon
+parameters:
+ level: max
+ paths:
+ - Classes
+ - Tests
+ checkMissingIterableValueType: false
+ reportUnmatchedIgnoredErrors: true
+ checkGenericClassInNonGenericObjectType: false
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..bb9e07e
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,35 @@
+
+
+
+
+
+ Tests/Functional/
+
+
+
+
+
+ Classes
+
+
+
+
+
+
+
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..94b9cde
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,45 @@
+{
+ pkgs ? import { }
+ ,phps ? import
+}:
+
+let
+ php = phps.packages.x86_64-linux.php81;
+ inherit(php.packages) composer;
+
+ phpWithXdebug = php.buildEnv {
+ extensions = { enabled, all }: enabled ++ (with all; [
+ xdebug
+ ]);
+
+ extraConfig = ''
+ xdebug.mode = debug
+ '';
+ };
+
+ projectInstall = pkgs.writeShellApplication {
+ name = "project-install";
+ runtimeInputs = [
+ php
+ composer
+ ];
+ text = ''
+ composer install --prefer-dist --no-progress --working-dir="$PROJECT_ROOT"
+ '';
+ };
+
+in pkgs.mkShell {
+ name = "TYPO3 PHP Datasets";
+ buildInputs = [
+ projectInstall
+ phpWithXdebug
+ composer
+ pkgs.parallel
+ ];
+
+ shellHook = ''
+ export PROJECT_ROOT="$(pwd)"
+
+ export typo3DatabaseDriver=pdo_sqlite
+ '';
+}