Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,16 @@ To provide a change to the testing framework, you have to

./Build/Scripts/runTests.sh -s functional

and confirming that the test runs have the same result as without the
Run the framework's own functional regression tests from that Core
checkout as well. They test the snapshot export and import of
``DatabaseAccessor`` and run on every database, SQLite included:

.. code-block:: bash

./Build/Scripts/runTests.sh -s functional -d sqlite -- \
vendor/typo3/testing-framework/Tests/Functional/

Confirm that the Core test runs have the same result as without the
changes,

4. push the change to your fork by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ function (string $columnName) use ($table) {
// @todo Simplify this after Doctine DBAL v3 support can be dropped.
$type = $table->getColumn($columnName)->getType();
$bindingType = $type->getBindingType();
// Snapshot strings already contain database values, including encoded JSON.
if ($bindingType === ParameterType::STRING) {
return Types::STRING;
}
return (enum_exists(ParameterType::class))
? Type::lookupName($type)
: $type->getBindingType();
Expand Down
83 changes: 83 additions & 0 deletions Tests/Functional/DatabaseAccessorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\TestingFramework\Tests\Functional;

use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Snapshot\DatabaseAccessor;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

final class DatabaseAccessorTest extends FunctionalTestCase
{
#[Test]
public function snapshotPreservesDatabaseValues(): void
{
$connection = $this->get(ConnectionPool::class)->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
$schemaManager = $connection->createSchemaManager();
$table = new Table('tx_testingframework_snapshot');
$table->addColumn('uid', Types::INTEGER, ['autoincrement' => true]);
$table->addColumn('payload', Types::JSON, ['notnull' => false]);
$table->addColumn('created_at', Types::DATETIME_MUTABLE);
$table->addColumn('enabled', Types::BOOLEAN);
$table->setPrimaryKey(['uid']);
$schemaManager->createTable($table);

try {
$jsonValues = [
'{"name":"Alice","nested":{"items":[1,true,null]}}',
'[{"name":"Alice"},{"name":"Bob"}]',
'"quoted \\"text\\""',
'42',
'1.5',
'true',
'false',
'null',
'{}',
'[]',
null,
];
foreach ($jsonValues as $index => $json) {
$connection->insert($table->getName(), [
'uid' => $index + 1,
'payload' => $json,
'created_at' => '2026-11-14 12:34:56',
'enabled' => $index % 2 === 0,
], [
'uid' => ParameterType::INTEGER,
'payload' => $json === null ? ParameterType::NULL : ParameterType::STRING,
'created_at' => ParameterType::STRING,
'enabled' => ParameterType::BOOLEAN,
]);
}
$expected = $connection->select(['*'], $table->getName(), [], [], ['uid' => 'ASC'])->fetchAllAssociative();
self::assertCount(count($jsonValues), $expected);
$subject = new DatabaseAccessor($connection);
$snapshot = json_decode(json_encode($subject->export(), JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR);
$connection->executeStatement('DELETE FROM ' . $connection->quoteIdentifier($table->getName()));

$subject->import($snapshot);

self::assertSame($expected, $connection->select(['*'], $table->getName(), [], [], ['uid' => 'ASC'])->fetchAllAssociative());
} finally {
$schemaManager->dropTable($table->getName());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\TestingFramework\Tests\Unit\Core\Functional\Framework\DataHandling\Snapshot;

use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Snapshot\DatabaseAccessor;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

final class DatabaseAccessorTest extends UnitTestCase
{
public static function databaseValuesDataProvider(): iterable
{
yield 'encoded JSON' => [Types::JSON, '{"value":[1,true,null]}', Types::STRING];
yield 'database datetime' => [Types::DATETIME_MUTABLE, '2026-11-14 12:34:56', Types::STRING];
yield 'plain text' => [Types::TEXT, 'unchanged', Types::STRING];
yield 'integer keeps its type' => [Types::INTEGER, '42', Types::INTEGER];
}

#[Test]
#[DataProvider('databaseValuesDataProvider')]
public function exportPreservesValuesAndNormalizesStringBoundTypes(string $type, string $value, string $expectedType): void
{
$table = new Table('tx_testingframework_snapshot');
$table->addColumn('payload', $type);
$schemaManager = self::createStub(AbstractSchemaManager::class);
$schemaManager->method('listTables')->willReturn([$table]);
$result = self::createStub(Result::class);
$result->method('fetchAllAssociative')->willReturn([['payload' => $value]]);
$queryBuilder = self::createStub(QueryBuilder::class);
$queryBuilder->method('getRestrictions')->willReturn(self::createStub(DefaultRestrictionContainer::class));
$queryBuilder->method('select')->willReturnSelf();
$queryBuilder->method('from')->willReturnSelf();
$queryBuilder->method('executeQuery')->willReturn($result);
$connection = self::createStub(Connection::class);
$connection->method('createSchemaManager')->willReturn($schemaManager);
$connection->method('createQueryBuilder')->willReturn($queryBuilder);

$export = (new DatabaseAccessor($connection))->export();
self::assertSame(['payload' => $expectedType], $export[0]['columns']);
self::assertSame([[$value]], $export[0]['items']);
}
}