Failed Conditions
Push — master ( 945b27...e4978c )
by Jonathan
24s queued 13s
created

ExceptionTest::cleanupReadOnlyFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Doctrine\Tests\DBAL\Functional;
4
5
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
6
use Doctrine\DBAL\DriverManager;
7
use Doctrine\DBAL\Exception;
8
use Doctrine\DBAL\Schema\Schema;
9
use Doctrine\DBAL\Schema\Table;
10
use Doctrine\Tests\DbalFunctionalTestCase;
11
use Throwable;
12
use const PHP_OS;
13
use function array_merge;
14
use function chmod;
15
use function exec;
16
use function file_exists;
17
use function posix_geteuid;
18
use function posix_getpwuid;
19
use function sprintf;
20
use function sys_get_temp_dir;
21
use function touch;
22
use function unlink;
23
24
class ExceptionTest extends DbalFunctionalTestCase
25
{
26
    protected function setUp() : void
27
    {
28
        parent::setUp();
29
30
        if ($this->connection->getDriver() instanceof ExceptionConverterDriver) {
31
            return;
32
        }
33
34
        $this->markTestSkipped('Driver does not support special exception handling.');
35
    }
36
37
    public function testPrimaryConstraintViolationException()
38
    {
39
        $table = new Table('duplicatekey_table');
40
        $table->addColumn('id', 'integer', []);
41
        $table->setPrimaryKey(['id']);
42
43
        $this->connection->getSchemaManager()->createTable($table);
44
45
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
46
47
        $this->expectException(Exception\UniqueConstraintViolationException::class);
48
        $this->connection->insert('duplicatekey_table', ['id' => 1]);
49
    }
50
51
    public function testTableNotFoundException()
52
    {
53
        $sql = 'SELECT * FROM unknown_table';
54
55
        $this->expectException(Exception\TableNotFoundException::class);
56
        $this->connection->executeQuery($sql);
57
    }
58
59
    public function testTableExistsException()
60
    {
61
        $schemaManager = $this->connection->getSchemaManager();
62
        $table         = new Table('alreadyexist_table');
63
        $table->addColumn('id', 'integer', []);
64
        $table->setPrimaryKey(['id']);
65
66
        $this->expectException(Exception\TableExistsException::class);
67
        $schemaManager->createTable($table);
68
        $schemaManager->createTable($table);
69
    }
70
71
    public function testForeignKeyConstraintViolationExceptionOnInsert()
72
    {
73
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
74
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
75
        }
76
77
        $this->setUpForeignKeyConstraintViolationExceptionTest();
78
79
        try {
80
            $this->connection->insert('constraint_error_table', ['id' => 1]);
81
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
82
        } catch (Throwable $exception) {
83
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
84
85
            throw $exception;
86
        }
87
88
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
89
90
        try {
91
            $this->connection->insert('owning_table', ['id' => 2, 'constraint_id' => 2]);
92
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
93
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
94
95
            throw $exception;
96
        } catch (Throwable $exception) {
97
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
98
99
            throw $exception;
100
        }
101
102
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
103
    }
104
105
    public function testForeignKeyConstraintViolationExceptionOnUpdate()
106
    {
107
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
108
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
109
        }
110
111
        $this->setUpForeignKeyConstraintViolationExceptionTest();
112
113
        try {
114
            $this->connection->insert('constraint_error_table', ['id' => 1]);
115
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
116
        } catch (Throwable $exception) {
117
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
118
119
            throw $exception;
120
        }
121
122
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
123
124
        try {
125
            $this->connection->update('constraint_error_table', ['id' => 2], ['id' => 1]);
126
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
127
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
128
129
            throw $exception;
130
        } catch (Throwable $exception) {
131
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
132
133
            throw $exception;
134
        }
135
136
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
137
    }
138
139
    public function testForeignKeyConstraintViolationExceptionOnDelete()
140
    {
141
        if (! $this->connection->getDatabasePlatform()->supportsForeignKeyConstraints()) {
142
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
143
        }
144
145
        $this->setUpForeignKeyConstraintViolationExceptionTest();
146
147
        try {
148
            $this->connection->insert('constraint_error_table', ['id' => 1]);
149
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
150
        } catch (Throwable $exception) {
151
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
152
153
            throw $exception;
154
        }
155
156
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
157
158
        try {
159
            $this->connection->delete('constraint_error_table', ['id' => 1]);
160
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
161
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
162
163
            throw $exception;
164
        } catch (Throwable $exception) {
165
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
166
167
            throw $exception;
168
        }
169
170
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
171
    }
172
173
    public function testForeignKeyConstraintViolationExceptionOnTruncate()
174
    {
175
        $platform = $this->connection->getDatabasePlatform();
176
177
        if (! $platform->supportsForeignKeyConstraints()) {
178
            $this->markTestSkipped('Only fails on platforms with foreign key constraints.');
179
        }
180
181
        $this->setUpForeignKeyConstraintViolationExceptionTest();
182
183
        try {
184
            $this->connection->insert('constraint_error_table', ['id' => 1]);
185
            $this->connection->insert('owning_table', ['id' => 1, 'constraint_id' => 1]);
186
        } catch (Throwable $exception) {
187
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
188
189
            throw $exception;
190
        }
191
192
        $this->expectException(Exception\ForeignKeyConstraintViolationException::class);
193
194
        try {
195
            $this->connection->executeUpdate($platform->getTruncateTableSQL('constraint_error_table'));
196
        } catch (Exception\ForeignKeyConstraintViolationException $exception) {
197
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
198
199
            throw $exception;
200
        } catch (Throwable $exception) {
201
            $this->tearDownForeignKeyConstraintViolationExceptionTest();
202
203
            throw $exception;
204
        }
205
206
        $this->tearDownForeignKeyConstraintViolationExceptionTest();
207
    }
208
209
    public function testNotNullConstraintViolationException()
210
    {
211
        $schema = new Schema();
212
213
        $table = $schema->createTable('notnull_table');
214
        $table->addColumn('id', 'integer', []);
215
        $table->addColumn('value', 'integer', ['notnull' => true]);
216
        $table->setPrimaryKey(['id']);
217
218
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
219
            $this->connection->exec($sql);
220
        }
221
222
        $this->expectException(Exception\NotNullConstraintViolationException::class);
223
        $this->connection->insert('notnull_table', ['id' => 1, 'value' => null]);
224
    }
225
226
    public function testInvalidFieldNameException()
227
    {
228
        $schema = new Schema();
229
230
        $table = $schema->createTable('bad_fieldname_table');
231
        $table->addColumn('id', 'integer', []);
232
233
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
234
            $this->connection->exec($sql);
235
        }
236
237
        $this->expectException(Exception\InvalidFieldNameException::class);
238
        $this->connection->insert('bad_fieldname_table', ['name' => 5]);
239
    }
240
241
    public function testNonUniqueFieldNameException()
242
    {
243
        $schema = new Schema();
244
245
        $table = $schema->createTable('ambiguous_list_table');
246
        $table->addColumn('id', 'integer');
247
248
        $table2 = $schema->createTable('ambiguous_list_table_2');
249
        $table2->addColumn('id', 'integer');
250
251
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
252
            $this->connection->exec($sql);
253
        }
254
255
        $sql = 'SELECT id FROM ambiguous_list_table, ambiguous_list_table_2';
256
        $this->expectException(Exception\NonUniqueFieldNameException::class);
257
        $this->connection->executeQuery($sql);
258
    }
259
260
    public function testUniqueConstraintViolationException()
261
    {
262
        $schema = new Schema();
263
264
        $table = $schema->createTable('unique_field_table');
265
        $table->addColumn('id', 'integer');
266
        $table->addUniqueIndex(['id']);
267
268
        foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
269
            $this->connection->exec($sql);
270
        }
271
272
        $this->connection->insert('unique_field_table', ['id' => 5]);
273
        $this->expectException(Exception\UniqueConstraintViolationException::class);
274
        $this->connection->insert('unique_field_table', ['id' => 5]);
275
    }
276
277
    public function testSyntaxErrorException()
278
    {
279
        $table = new Table('syntax_error_table');
280
        $table->addColumn('id', 'integer', []);
281
        $table->setPrimaryKey(['id']);
282
283
        $this->connection->getSchemaManager()->createTable($table);
284
285
        $sql = 'SELECT id FRO syntax_error_table';
286
        $this->expectException(Exception\SyntaxErrorException::class);
287
        $this->connection->executeQuery($sql);
288
    }
289
290
    public function testConnectionExceptionSqLite()
291
    {
292
        if ($this->connection->getDatabasePlatform()->getName() !== 'sqlite') {
293
            $this->markTestSkipped('Only fails this way on sqlite');
294
        }
295
296
        // mode 0 is considered read-only on Windows
297
        $mode = PHP_OS === 'Linux' ? 0444 : 0000;
298
299
        $filename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_failed_connection_' . $mode . '.db');
300
301
        if (file_exists($filename)) {
302
            $this->cleanupReadOnlyFile($filename);
303
        }
304
305
        touch($filename);
306
        chmod($filename, $mode);
307
308
        if ($this->isLinuxRoot()) {
309
            exec(sprintf('chattr +i %s', $filename));
310
        }
311
312
        $params = [
313
            'driver' => 'pdo_sqlite',
314
            'path'   => $filename,
315
        ];
316
        $conn   = DriverManager::getConnection($params);
317
318
        $schema = new Schema();
319
        $table  = $schema->createTable('no_connection');
320
        $table->addColumn('id', 'integer');
321
322
        $this->expectException(Exception\ReadOnlyException::class);
323
        $this->expectExceptionMessage(<<<EOT
324
An exception occurred while executing 'CREATE TABLE no_connection (id INTEGER NOT NULL)':
325
326
SQLSTATE[HY000]: General error: 8 attempt to write a readonly database
327
EOT
328
        );
329
330
        try {
331
            foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
332
                $conn->exec($sql);
333
            }
334
        } finally {
335
            $this->cleanupReadOnlyFile($filename);
336
        }
337
    }
338
339
    /**
340
     * @dataProvider getConnectionParams
341
     */
342
    public function testConnectionException($params)
343
    {
344
        if ($this->connection->getDatabasePlatform()->getName() === 'sqlite') {
345
            $this->markTestSkipped('Only skipped if platform is not sqlite');
346
        }
347
348
        if ($this->connection->getDatabasePlatform()->getName() === 'drizzle') {
349
            $this->markTestSkipped('Drizzle does not always support authentication');
350
        }
351
352
        if ($this->connection->getDatabasePlatform()->getName() === 'postgresql' && isset($params['password'])) {
353
            $this->markTestSkipped('Does not work on Travis');
354
        }
355
356
        $defaultParams = $this->connection->getParams();
357
        $params        = array_merge($defaultParams, $params);
358
359
        $conn = DriverManager::getConnection($params);
360
361
        $schema = new Schema();
362
        $table  = $schema->createTable('no_connection');
363
        $table->addColumn('id', 'integer');
364
365
        $this->expectException(Exception\ConnectionException::class);
366
367
        foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
368
            $conn->exec($sql);
369
        }
370
    }
371
372
    public function getConnectionParams()
373
    {
374
        return [
375
            [['user' => 'not_existing']],
376
            [['password' => 'really_not']],
377
            [['host' => 'localnope']],
378
        ];
379
    }
380
381
    private function setUpForeignKeyConstraintViolationExceptionTest()
382
    {
383
        $schemaManager = $this->connection->getSchemaManager();
384
385
        $table = new Table('constraint_error_table');
386
        $table->addColumn('id', 'integer', []);
387
        $table->setPrimaryKey(['id']);
388
389
        $owningTable = new Table('owning_table');
390
        $owningTable->addColumn('id', 'integer', []);
391
        $owningTable->addColumn('constraint_id', 'integer', []);
392
        $owningTable->setPrimaryKey(['id']);
393
        $owningTable->addForeignKeyConstraint($table, ['constraint_id'], ['id']);
394
395
        $schemaManager->createTable($table);
396
        $schemaManager->createTable($owningTable);
397
    }
398
399
    private function tearDownForeignKeyConstraintViolationExceptionTest()
400
    {
401
        $schemaManager = $this->connection->getSchemaManager();
402
403
        $schemaManager->dropTable('owning_table');
404
        $schemaManager->dropTable('constraint_error_table');
405
    }
406
407
    private function isLinuxRoot() : bool
408
    {
409
        return PHP_OS === 'Linux' && posix_getpwuid(posix_geteuid())['name'] === 'root';
410
    }
411
412
    private function cleanupReadOnlyFile(string $filename) : void
413
    {
414
        if ($this->isLinuxRoot()) {
415
            exec(sprintf('chattr -i %s', $filename));
416
        }
417
418
        chmod($filename, 0200); // make the file writable again, so it can be removed on Windows
419
        unlink($filename);
420
    }
421
}
422