Failed Conditions
Pull Request — develop (#3348)
by Sergei
60:53
created

ExceptionTest::getConnectionParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\DBAL\Functional;
6
7
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
8
use Doctrine\DBAL\DriverManager;
9
use Doctrine\DBAL\Exception;
10
use Doctrine\DBAL\Schema\Schema;
11
use Doctrine\DBAL\Schema\Table;
12
use Doctrine\Tests\DbalFunctionalTestCase;
13
use Throwable;
14
use const PHP_OS;
15
use function array_merge;
16
use function chmod;
17
use function defined;
18
use function exec;
19
use function file_exists;
20
use function posix_geteuid;
21
use function posix_getpwuid;
22
use function sprintf;
23
use function sys_get_temp_dir;
24
use function touch;
25
use function unlink;
26
27
class ExceptionTest extends DbalFunctionalTestCase
28
{
29
    protected function setUp() : void
30
    {
31
        parent::setUp();
32
33
        if ($this->connection->getDriver() instanceof ExceptionConverterDriver) {
34
            return;
35
        }
36
37
        $this->markTestSkipped('Driver does not support special exception handling.');
38
    }
39
40
    public function testPrimaryConstraintViolationException() : void
41
    {
42
        $table = new Table('duplicatekey_table');
43
        $table->addColumn('id', 'integer', []);
44
        $table->setPrimaryKey(['id']);
45
46
        $this->connection->getSchemaManager()->createTable($table);
47
48
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
49
50
        $this->expectException(Exception\UniqueConstraintViolationException::class);
51
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
52
    }
53
54
    public function testTableNotFoundException() : void
55
    {
56
        $sql = 'SELECT * FROM unknown_table';
57
58
        $this->expectException(Exception\TableNotFoundException::class);
59
        $this->connection->executeQuery($sql);
60
    }
61
62
    public function testTableExistsException() : void
63
    {
64
        $schemaManager = $this->connection->getSchemaManager();
65
        $table         = new Table('alreadyexist_table');
66
        $table->addColumn('id', 'integer', []);
67
        $table->setPrimaryKey(['id']);
68
69
        $this->expectException(Exception\TableExistsException::class);
70
        $schemaManager->createTable($table);
71
        $schemaManager->createTable($table);
72
    }
73
74
    public function testForeignKeyConstraintViolationExceptionOnInsert() : void
75
    {
76
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
77
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
78
        }
79
80
        $this->setUpForeignKeyConstraintViolationExceptionTest();
81
82
        try {
83
            $this->connection->insert('constraint_error_table', ['id' => 1]);
84
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
85
        } catch (Throwable $exception) {
86
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
87
88
            throw $exception;
89
        }
90
91
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
92
93
        try {
94
            $this->connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]);
95
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
96
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
97
98
            throw $exception;
99
        } catch (Throwable $exception) {
100
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
101
102
            throw $exception;
103
        }
104
105
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
106
    }
107
108
    public function testForeignKeyConstraintViolationExceptionOnUpdate() : void
109
    {
110
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
111
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
112
        }
113
114
        $this->setUpForeignKeyConstraintViolationExceptionTest();
115
116
        try {
117
            $this->connection->insert('constraint_error_table', ['id' => 1]);
118
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
119
        } catch (Throwable $exception) {
120
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
121
122
            throw $exception;
123
        }
124
125
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
126
127
        try {
128
            $this->connection->update('constraint_error_table', ['id' => 2], ['id' => 1]);
129
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
130
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
131
132
            throw $exception;
133
        } catch (Throwable $exception) {
134
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
135
136
            throw $exception;
137
        }
138
139
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
140
    }
141
142
    public function testForeignKeyConstraintViolationExceptionOnDelete() : void
143
    {
144
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
145
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
146
        }
147
148
        $this->setUpForeignKeyConstraintViolationExceptionTest();
149
150
        try {
151
            $this->connection->insert('constraint_error_table', ['id' => 1]);
152
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
153
        } catch (Throwable $exception) {
154
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
155
156
            throw $exception;
157
        }
158
159
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
160
161
        try {
162
            $this->connection->delete('constraint_error_table', ['id' => 1]);
163
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
164
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
165
166
            throw $exception;
167
        } catch (Throwable $exception) {
168
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
169
170
            throw $exception;
171
        }
172
173
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
174
    }
175
176
    public function testForeignKeyConstraintViolationExceptionOnTruncate() : void
177
    {
178
        $platform = $this->connection->getDatabasePlatform();
179
180
        if (! $platform->supportsForeignKeyConstraints()) {
181
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
182
        }
183
184
        $this->setUpForeignKeyConstraintViolationExceptionTest();
185
186
        try {
187
            $this->connection->insert('constraint_error_table', ['id' => 1]);
188
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
189
        } catch (Throwable $exception) {
190
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
191
192
            throw $exception;
193
        }
194
195
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
196
197
        try {
198
            $this->connection->executeUpdate($platform->getTruncateTableSQL('constraint_error_table'));
199
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
200
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
201
202
            throw $exception;
203
        } catch (Throwable $exception) {
204
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
205
206
            throw $exception;
207
        }
208
209
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
210
    }
211
212
    public function testNotNullConstraintViolationException() : void
213
    {
214
        $schema = new Schema();
215
216
        $table = $schema->createTable('notnull_table');
217
        $table->addColumn('id', 'integer', []);
218
        $table->addColumn('value', 'integer', ['notnull' => true]);
219
        $table->setPrimaryKey(['id']);
220
221
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
222
            $this->connection->exec($sql);
223
        }
224
225
        $this->expectException(Exception\NotNullConstraintViolationException::class);
226
        $this->connection->insert('notnull_table', ['id' => 1, 'value' => null]);
227
    }
228
229
    public function testInvalidFieldNameException() : void
230
    {
231
        $schema = new Schema();
232
233
        $table = $schema->createTable('bad_fieldname_table');
234
        $table->addColumn('id', 'integer', []);
235
236
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
237
            $this->connection->exec($sql);
238
        }
239
240
        $this->expectException(Exception\InvalidFieldNameException::class);
241
        $this->connection->insert('bad_fieldname_table', ['name' => 5]);
242
    }
243
244
    public function testNonUniqueFieldNameException() : void
245
    {
246
        $schema = new Schema();
247
248
        $table = $schema->createTable('ambiguous_list_table');
249
        $table->addColumn('id', 'integer');
250
251
        $table2 = $schema->createTable('ambiguous_list_table_2');
252
        $table2->addColumn('id', 'integer');
253
254
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
255
            $this->connection->exec($sql);
256
        }
257
258
        $sql = 'SELECT id FROM ambiguous_list_table, ambiguous_list_table_2';
259
        $this->expectException(Exception\NonUniqueFieldNameException::class);
260
        $this->connection->executeQuery($sql);
261
    }
262
263
    public function testUniqueConstraintViolationException() : void
264
    {
265
        $schema = new Schema();
266
267
        $table = $schema->createTable('unique_field_table');
268
        $table->addColumn('id', 'integer');
269
        $table->addUniqueIndex(['id']);
270
271
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
272
            $this->connection->exec($sql);
273
        }
274
275
        $this->connection->insert('unique_field_table', ['id' => 5]);
276
        $this->expectException(Exception\UniqueConstraintViolationException::class);
277
        $this->connection->insert('unique_field_table', ['id' => 5]);
278
    }
279
280
    public function testSyntaxErrorException() : void
281
    {
282
        $table = new Table('syntax_error_table');
283
        $table->addColumn('id', 'integer', []);
284
        $table->setPrimaryKey(['id']);
285
286
        $this->connection->getSchemaManager()->createTable($table);
287
288
        $sql = 'SELECT id FRO syntax_error_table';
289
        $this->expectException(Exception\SyntaxErrorException::class);
290
        $this->connection->executeQuery($sql);
291
    }
292
293
    /**
294
     * @dataProvider getSqLiteOpenConnection
295
     */
296
    public function testConnectionExceptionSqLite(int $mode, string $exceptionClass) : void
0 ignored issues
show
Unused Code introduced by
The parameter $mode is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

296
    public function testConnectionExceptionSqLite(/** @scrutinizer ignore-unused */ int $mode, string $exceptionClass) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $exceptionClass is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

296
    public function testConnectionExceptionSqLite(int $mode, /** @scrutinizer ignore-unused */ string $exceptionClass) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
297
    {
298
        if ($this->connection->getDatabasePlatform()->getName() !== 'sqlite') {
299
            $this->markTestSkipped('Only fails this way on sqlite');
300
        }
301
302
        // mode 0 is considered read-only on Windows
303
        $mode = PHP_OS === 'Linux' ? 0444 : 0000;
304
305
        $filename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_failed_connection_' . $mode . '.db');
306
307
        if (file_exists($filename)) {
308
            $this->cleanupReadOnlyFile($filename);
309
        }
310
311
        touch($filename);
312
        chmod($filename, $mode);
313
314
        if ($this->isLinuxRoot()) {
315
            exec(sprintf('chattr +i %s', $filename));
316
        }
317
318
        $params = [
319
            'driver' => 'pdo_sqlite',
320
            'path'   => $filename,
321
        ];
322
        $conn   = DriverManager::getConnection($params);
323
324
        $schema = new Schema();
325
        $table  = $schema->createTable('no_connection');
326
        $table->addColumn('id', 'integer');
327
328
        $this->expectException(Exception\ReadOnlyException::class);
329
        $this->expectExceptionMessage(<<<EOT
330
An exception occurred while executing "CREATE TABLE no_connection (id INTEGER NOT NULL)":
331
332
SQLSTATE[HY000]: General error: 8 attempt to write a readonly database
333
EOT
334
        );
335
336
        try {
337
            foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
338
                $conn->exec($sql);
339
            }
340
        } finally {
341
            $this->cleanupReadOnlyFile($filename);
342
        }
343
    }
344
345
    /**
346
     * @return mixed[][]
347
     */
348
    public static function getSqLiteOpenConnection() : iterable
349
    {
350
        return [
351
            // mode 0 is considered read-only on Windows
352
            [0000, defined('PHP_WINDOWS_VERSION_BUILD') ? Exception\ReadOnlyException::class : Exception\ConnectionException::class],
353
            [0444, Exception\ReadOnlyException::class],
354
        ];
355
    }
356
357
    /**
358
     * @param mixed[] $params
359
     *
360
     * @dataProvider getConnectionParams
361
     */
362
    public function testConnectionException(array $params) : void
363
    {
364
        if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') {
365
            $this->markTestSkipped('Only skipped if platform is not sqlite');
366
        }
367
368
        if ($this->connection->getDatabasePlatform()->getName() === 'postgresql' && isset($params['password'])) {
369
            $this->markTestSkipped('Does not work on Travis');
370
        }
371
372
        $defaultParams = $this->connection->getParams();
373
        $params        = array_merge($defaultParams, $params);
374
375
        $conn = DriverManager::getConnection($params);
376
377
        $schema = new Schema();
378
        $table  = $schema->createTable('no_connection');
379
        $table->addColumn('id', 'integer');
380
381
        $this->expectException(Exception\ConnectionException::class);
382
383
        foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
384
            $conn->exec($sql);
385
        }
386
    }
387
388
    /**
389
     * @return mixed[][]
390
     */
391
    public static function getConnectionParams() : iterable
392
    {
393
        return [
394
            [['user' => 'not_existing']],
395
            [['password' => 'really_not']],
396
            [['host' => 'localnope']],
397
        ];
398
    }
399
400
    private function setUpForeignKeyConstraintViolationExceptionTest() : void
401
    {
402
        $schemaManager = $this->connection->getSchemaManager();
403
404
        $table = new Table('constraint_error_table');
405
        $table->addColumn('id', 'integer', []);
406
        $table->setPrimaryKey(['id']);
407
408
        $owningTable = new Table('owning_table');
409
        $owningTable->addColumn('id', 'integer', []);
410
        $owningTable->addColumn('constraint_id', 'integer', []);
411
        $owningTable->setPrimaryKey(['id']);
412
        $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']);
413
414
        $schemaManager->createTable($table);
415
        $schemaManager->createTable($owningTable);
416
    }
417
418
    private function tearDownForeignKeyConstraintViolationExceptionTest() : void
419
    {
420
        $schemaManager = $this->connection->getSchemaManager();
421
422
        $schemaManager->dropTable('owning_table');
423
        $schemaManager->dropTable('constraint_error_table');
424
    }
425
426
    private function isLinuxRoot() : bool
427
    {
428
        return PHP_OS === 'Linux' && posix_getpwuid(posix_geteuid())['name'] === 'root';
429
    }
430
431
    private function cleanupReadOnlyFile(string $filename) : void
432
    {
433
        if ($this->isLinuxRoot()) {
434
            exec(sprintf('chattr -i %s', $filename));
435
        }
436
437
        chmod($filename, 0200); // make the file writable again, so it can be removed on Windows
438
        unlink($filename);
439
    }
440
}
441