AbstractDriverTest.php 8.29 KB
Newer Older
1 2
<?php

Michael Moravec's avatar
Michael Moravec committed
3 4
declare(strict_types=1);

5
namespace Doctrine\DBAL\Tests\Driver;
6 7

use Doctrine\DBAL\Connection;
8
use Doctrine\DBAL\DBALException;
Sergei Morozov's avatar
Sergei Morozov committed
9
use Doctrine\DBAL\Driver;
Sergei Morozov's avatar
Sergei Morozov committed
10
use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface;
11
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
Sergei Morozov's avatar
Sergei Morozov committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConstraintViolationException;
use Doctrine\DBAL\Exception\DatabaseObjectExistsException;
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\ReadOnlyException;
use Doctrine\DBAL\Exception\ServerException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
Sergei Morozov's avatar
Sergei Morozov committed
29 30
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
31
use Doctrine\DBAL\VersionAwarePlatformDriver;
32
use PHPUnit\Framework\MockObject\MockObject;
33
use PHPUnit\Framework\TestCase;
34
use ReflectionProperty;
35

36
use function array_merge;
37 38
use function get_class;
use function sprintf;
39

40
abstract class AbstractDriverTest extends TestCase
41
{
Sergei Morozov's avatar
Sergei Morozov committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
    public const EXCEPTION_CONNECTION                       = ConnectionException::class;
    public const EXCEPTION_CONSTRAINT_VIOLATION             = ConstraintViolationException::class;
    public const EXCEPTION_DATABASE_OBJECT_EXISTS           = DatabaseObjectExistsException::class;
    public const EXCEPTION_DATABASE_OBJECT_NOT_FOUND        = DatabaseObjectNotFoundException::class;
    public const EXCEPTION_DRIVER                           = DriverException::class;
    public const EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION = ForeignKeyConstraintViolationException::class;
    public const EXCEPTION_INVALID_FIELD_NAME               = InvalidFieldNameException::class;
    public const EXCEPTION_NON_UNIQUE_FIELD_NAME            = NonUniqueFieldNameException::class;
    public const EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION    = NotNullConstraintViolationException::class;
    public const EXCEPTION_READ_ONLY                        = ReadOnlyException::class;
    public const EXCEPTION_SERVER                           = ServerException::class;
    public const EXCEPTION_SYNTAX_ERROR                     = SyntaxErrorException::class;
    public const EXCEPTION_TABLE_EXISTS                     = TableExistsException::class;
    public const EXCEPTION_TABLE_NOT_FOUND                  = TableNotFoundException::class;
    public const EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION      = UniqueConstraintViolationException::class;
    public const EXCEPTION_DEADLOCK                         = DeadlockException::class;
    public const EXCEPTION_LOCK_WAIT_TIMEOUT                = LockWaitTimeoutException::class;
59 60 61 62

    /**
     * The driver mock under test.
     *
Sergei Morozov's avatar
Sergei Morozov committed
63
     * @var Driver
64 65 66
     */
    protected $driver;

67
    protected function setUp(): void
68 69 70 71 72 73
    {
        parent::setUp();

        $this->driver = $this->createDriver();
    }

74 75 76
    /**
     * @dataProvider exceptionConversionProvider
     */
Sergei Morozov's avatar
Sergei Morozov committed
77
    public function testConvertsException(string $expectedClass, int $errorCode, ?string $sqlState = null, string $message = ''): void
78
    {
Sergei Morozov's avatar
Sergei Morozov committed
79
        if (! $this->driver instanceof ExceptionConverterDriver) {
80
            self::markTestSkipped('This test is only intended for exception converter drivers.');
81 82
        }

83
        $driverException = $this->getMockBuilder(DriverExceptionInterface::class)
84
            ->setConstructorArgs([$message, $errorCode])
85 86 87
            ->getMock();
        $driverException->method('getSQLState')
            ->willReturn($sqlState);
88

89 90
        $dbalMessage   = 'DBAL exception message';
        $dbalException = $this->driver->convertException($dbalMessage, $driverException);
91

92
        self::assertInstanceOf($expectedClass, $dbalException);
93

94
        self::assertSame($driverException->getCode(), $dbalException->getCode());
95 96 97
        self::assertSame($driverException->getSQLState(), $dbalException->getSQLState());
        self::assertSame($driverException, $dbalException->getPrevious());
        self::assertSame($dbalMessage, $dbalException->getMessage());
98 99
    }

100
    public function testCreatesDatabasePlatformForVersion(): void
101
    {
Sergei Morozov's avatar
Sergei Morozov committed
102
        if (! $this->driver instanceof VersionAwarePlatformDriver) {
103
            self::markTestSkipped('This test is only intended for version aware platform drivers.');
104 105 106 107
        }

        $data = $this->getDatabasePlatformsForVersions();

108 109 110 111
        self::assertNotEmpty(
            $data,
            sprintf(
                'No test data found for test %s. You have to return test data from %s.',
Sergei Morozov's avatar
Sergei Morozov committed
112 113
                static::class . '::' . __FUNCTION__,
                static::class . '::getDatabasePlatformsForVersions'
114 115
            )
        );
116 117

        foreach ($data as $item) {
118 119 120 121 122
            $generatedVersion = get_class($this->driver->createDatabasePlatformForVersion($item[0]));

            self::assertSame(
                $item[1],
                $generatedVersion,
123
                sprintf(
124
                    'Expected platform for version "%s" should be "%s", "%s" given',
125 126
                    $item[0],
                    $item[1],
127 128 129
                    $generatedVersion
                )
            );
130 131 132
        }
    }

133
    public function testThrowsExceptionOnCreatingDatabasePlatformsForInvalidVersion(): void
134
    {
Sergei Morozov's avatar
Sergei Morozov committed
135
        if (! $this->driver instanceof VersionAwarePlatformDriver) {
136
            self::markTestSkipped('This test is only intended for version aware platform drivers.');
137 138
        }

139
        $this->expectException(DBALException::class);
140 141 142
        $this->driver->createDatabasePlatformForVersion('foo');
    }

143
    public function testReturnsDatabasePlatform(): void
144
    {
145
        self::assertEquals($this->createPlatform(), $this->driver->getDatabasePlatform());
146 147
    }

148
    public function testReturnsSchemaManager(): void
149 150 151 152
    {
        $connection    = $this->getConnectionMock();
        $schemaManager = $this->driver->getSchemaManager($connection);

153
        self::assertEquals($this->createSchemaManager($connection), $schemaManager);
154 155 156 157 158

        $re = new ReflectionProperty($schemaManager, '_conn');
        $re->setAccessible(true);

        self::assertSame($connection, $re->getValue($schemaManager));
159 160
    }

161 162 163
    /**
     * Factory method for creating the driver instance under test.
     */
164
    abstract protected function createDriver(): Driver;
165

166 167 168 169 170 171
    /**
     * Factory method for creating the the platform instance return by the driver under test.
     *
     * The platform instance returned by this method must be the same as returned by
     * the driver's getDatabasePlatform() method.
     */
172
    abstract protected function createPlatform(): AbstractPlatform;
173

174 175 176 177 178 179 180 181
    /**
     * Factory method for creating the the schema manager instance return by the driver under test.
     *
     * The schema manager instance returned by this method must be the same as returned by
     * the driver's getSchemaManager() method.
     *
     * @param Connection $connection The underlying connection to use.
     */
182
    abstract protected function createSchemaManager(Connection $connection): AbstractSchemaManager;
183

184
    /**
Grégoire Paris's avatar
Grégoire Paris committed
185
     * @return Connection&MockObject
186
     */
187
    protected function getConnectionMock(): Connection
188
    {
Grégoire Paris's avatar
Grégoire Paris committed
189
        return $this->createMock(Connection::class);
190 191
    }

192 193 194
    /**
     * @return array<int, array<int, string>>
     */
195
    protected function getDatabasePlatformsForVersions(): array
196
    {
Sergei Morozov's avatar
Sergei Morozov committed
197
        return [];
198 199
    }

200
    /**
201
     * @return iterable<mixed[]>
202
     */
203
    public static function exceptionConversionProvider(): iterable
204
    {
205 206
        foreach (static::getExceptionConversionData() as $expectedClass => $items) {
            foreach ($items as $item) {
207
                yield array_merge([$expectedClass], $item);
208 209 210
            }
        }

211
        yield [self::EXCEPTION_DRIVER, 1, 'HY000', 'The message'];
212 213 214 215 216
    }

    /**
     * @return array<string,mixed[][]>
     */
217
    protected static function getExceptionConversionData(): array
218 219
    {
        return [];
220 221
    }
}