AbstractDriverTest.php 10.4 KB
Newer Older
1 2 3 4 5
<?php

namespace Doctrine\Tests\DBAL\Driver;

use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\DBALException;
Sergei Morozov's avatar
Sergei Morozov committed
7
use Doctrine\DBAL\Driver;
Sergei Morozov's avatar
Sergei Morozov committed
8
use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface;
9
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
Sergei Morozov's avatar
Sergei Morozov committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
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
27 28
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
29 30
use Doctrine\DBAL\VersionAwarePlatformDriver;
use Doctrine\Tests\DbalTestCase;
Sergei Morozov's avatar
Sergei Morozov committed
31
use Exception;
32
use ReflectionProperty;
33 34
use function get_class;
use function sprintf;
35 36 37

abstract class AbstractDriverTest extends DbalTestCase
{
Sergei Morozov's avatar
Sergei Morozov committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
    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;
55 56 57 58

    /**
     * The driver mock under test.
     *
Sergei Morozov's avatar
Sergei Morozov committed
59
     * @var Driver
60 61 62
     */
    protected $driver;

63
    protected function setUp() : void
64 65 66 67 68 69 70 71
    {
        parent::setUp();

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

    public function testConvertsException()
    {
Sergei Morozov's avatar
Sergei Morozov committed
72
        if (! $this->driver instanceof ExceptionConverterDriver) {
73 74 75 76 77 78 79 80 81
            $this->markTestSkipped('This test is only intended for exception converter drivers.');
        }

        $data = $this->getExceptionConversions();

        if (empty($data)) {
            $this->fail(
                sprintf(
                    'No test data found for test %s. You have to return test data from %s.',
Sergei Morozov's avatar
Sergei Morozov committed
82 83
                    static::class . '::' . __FUNCTION__,
                    static::class . '::getExceptionConversionData'
84 85 86 87
                )
            );
        }

Sergei Morozov's avatar
Sergei Morozov committed
88
        $driverException = new class extends Exception implements DriverExceptionInterface
89 90 91 92 93
        {
            public function __construct()
            {
                parent::__construct('baz');
            }
94

95 96 97 98 99 100 101
            /**
             * {@inheritDoc}
             */
            public function getErrorCode()
            {
                return 'foo';
            }
102

103 104 105 106 107 108 109 110
            /**
             * {@inheritDoc}
             */
            public function getSQLState()
            {
                return 'bar';
            }
        };
111

Sergei Morozov's avatar
Sergei Morozov committed
112
        $data[] = [$driverException, self::EXCEPTION_DRIVER];
113 114 115 116 117

        $message = 'DBAL exception message';

        foreach ($data as $item) {
            /** @var $driverException \Doctrine\DBAL\Driver\DriverException */
Sergei Morozov's avatar
Sergei Morozov committed
118
            [$driverException, $convertedExceptionClassName] = $item;
119 120 121

            $convertedException = $this->driver->convertException($message, $driverException);

122
            self::assertSame($convertedExceptionClassName, get_class($convertedException));
123

124 125 126
            self::assertSame($driverException->getErrorCode(), $convertedException->getErrorCode());
            self::assertSame($driverException->getSQLState(), $convertedException->getSQLState());
            self::assertSame($message, $convertedException->getMessage());
127 128 129 130 131
        }
    }

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

        $data = $this->getDatabasePlatformsForVersions();

138 139 140 141
        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
142 143
                static::class . '::' . __FUNCTION__,
                static::class . '::getDatabasePlatformsForVersions'
144 145
            )
        );
146 147

        foreach ($data as $item) {
148 149 150 151 152
            $generatedVersion = get_class($this->driver->createDatabasePlatformForVersion($item[0]));

            self::assertSame(
                $item[1],
                $generatedVersion,
153
                sprintf(
154
                    'Expected platform for version "%s" should be "%s", "%s" given',
155 156
                    $item[0],
                    $item[1],
157 158 159
                    $generatedVersion
                )
            );
160 161 162 163 164
        }
    }

    public function testThrowsExceptionOnCreatingDatabasePlatformsForInvalidVersion()
    {
Sergei Morozov's avatar
Sergei Morozov committed
165
        if (! $this->driver instanceof VersionAwarePlatformDriver) {
166 167 168
            $this->markTestSkipped('This test is only intended for version aware platform drivers.');
        }

169
        $this->expectException(DBALException::class);
170 171 172 173 174
        $this->driver->createDatabasePlatformForVersion('foo');
    }

    public function testReturnsDatabaseName()
    {
Sergei Morozov's avatar
Sergei Morozov committed
175
        $params = [
176 177 178
            'user'     => 'foo',
            'password' => 'bar',
            'dbname'   => 'baz',
Sergei Morozov's avatar
Sergei Morozov committed
179
        ];
180 181 182 183 184 185 186

        $connection = $this->getConnectionMock();

        $connection->expects($this->once())
            ->method('getParams')
            ->will($this->returnValue($params));

187
        self::assertSame($params['dbname'], $this->driver->getDatabase($connection));
188 189 190 191
    }

    public function testReturnsDatabasePlatform()
    {
192
        self::assertEquals($this->createPlatform(), $this->driver->getDatabasePlatform());
193 194 195 196 197 198 199
    }

    public function testReturnsSchemaManager()
    {
        $connection    = $this->getConnectionMock();
        $schemaManager = $this->driver->getSchemaManager($connection);

200
        self::assertEquals($this->createSchemaManager($connection), $schemaManager);
201 202 203 204 205

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

        self::assertSame($connection, $re->getValue($schemaManager));
206 207
    }

208 209 210
    /**
     * Factory method for creating the driver instance under test.
     *
Sergei Morozov's avatar
Sergei Morozov committed
211
     * @return Driver
212
     */
213 214
    abstract protected function createDriver();

215 216 217 218 219 220
    /**
     * 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.
     *
Sergei Morozov's avatar
Sergei Morozov committed
221
     * @return AbstractPlatform
222
     */
223 224
    abstract protected function createPlatform();

225 226 227 228 229 230 231 232
    /**
     * 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.
     *
Sergei Morozov's avatar
Sergei Morozov committed
233
     * @return AbstractSchemaManager
234
     */
235 236 237 238
    abstract protected function createSchemaManager(Connection $connection);

    protected function getConnectionMock()
    {
Sergei Morozov's avatar
Sergei Morozov committed
239
        return $this->getMockBuilder(Connection::class)
240 241 242 243 244 245
            ->disableOriginalConstructor()
            ->getMock();
    }

    protected function getDatabasePlatformsForVersions()
    {
Sergei Morozov's avatar
Sergei Morozov committed
246
        return [];
247 248 249 250
    }

    protected function getExceptionConversionData()
    {
Sergei Morozov's avatar
Sergei Morozov committed
251
        return [];
252 253 254 255
    }

    private function getExceptionConversions()
    {
Sergei Morozov's avatar
Sergei Morozov committed
256
        $data = [];
257 258 259

        foreach ($this->getExceptionConversionData() as $convertedExceptionClassName => $errors) {
            foreach ($errors as $error) {
260
                $driverException = new class ($error[0], $error[1], $error[2])
Sergei Morozov's avatar
Sergei Morozov committed
261
                    extends Exception
Sergei Morozov's avatar
Sergei Morozov committed
262
                    implements DriverExceptionInterface
263
                {
Sergei Morozov's avatar
Sergei Morozov committed
264
                    /** @var mixed */
265 266
                    private $errorCode;

Sergei Morozov's avatar
Sergei Morozov committed
267
                    /** @var mixed */
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
                    private $sqlState;

                    public function __construct($errorCode, $sqlState, $message)
                    {
                        parent::__construct($message);

                        $this->errorCode = $errorCode;
                        $this->sqlState  = $sqlState;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public function getErrorCode()
                    {
                        return $this->errorCode;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public function getSQLState()
                    {
                        return $this->sqlState;
                    }
                };
294

Sergei Morozov's avatar
Sergei Morozov committed
295
                $data[] = [$driverException, $convertedExceptionClassName];
296 297 298 299 300 301
            }
        }

        return $data;
    }
}