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 + ''; +}