Passed
Push — master ( 914087...c6011d )
by Alexander
11:22
created

TestConnectionTrait   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 462
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 20
eloc 258
c 2
b 0
f 0
dl 0
loc 462
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A testCacheKey() 0 4 1
A testNestedTransaction() 0 14 1
A testTransactionShortcutException() 0 14 1
A testTransactionShortcutCorrect() 0 16 1
A testExceptionContainsRawQuery() 0 33 2
A runExceptionTest() 0 36 3
A testTransaction() 0 32 1
A testRollbackTransactionsWithSavePoints() 0 29 1
A testSerialize() 0 15 1
A testPartialRollbackTransactionsWithSavePoints() 0 35 1
A testCommitTransactionsWithSavepoints() 0 33 1
A testNestedTransactionNotSupported() 0 10 1
B testEnableQueryLog() 0 93 3
A testOpenClose() 0 18 1
A testClone() 0 34 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\TestSupport;
6
7
use PDO;
8
use Yiisoft\Db\Connection\ConnectionInterface;
9
use Yiisoft\Db\Driver\PDO\ConnectionPDOInterface;
10
use Yiisoft\Db\Exception\Exception;
11
use Yiisoft\Db\Exception\NotSupportedException;
12
13
use function PHPUnit\Framework\assertEquals;
14
use function serialize;
15
use function unserialize;
16
17
trait TestConnectionTrait
18
{
19
    public function testCacheKey(): void
20
    {
21
        $db = $this->getConnection();
0 ignored issues
show
Bug introduced by
It seems like getConnection() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

21
        /** @scrutinizer ignore-call */ 
22
        $db = $this->getConnection();
Loading history...
22
        $this->assertEquals([$this->dsn, $this->username], $db->getCacheKey());
0 ignored issues
show
Bug introduced by
It seems like assertEquals() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

22
        $this->/** @scrutinizer ignore-call */ 
23
               assertEquals([$this->dsn, $this->username], $db->getCacheKey());
Loading history...
23
    }
24
25
    public function testOpenClose(): void
26
    {
27
        $db = $this->getConnection();
28
        $this->assertFalse($db->isActive());
0 ignored issues
show
Bug introduced by
It seems like assertFalse() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

28
        $this->/** @scrutinizer ignore-call */ 
29
               assertFalse($db->isActive());
Loading history...
29
        $this->assertNull($db->getPDO());
0 ignored issues
show
Bug introduced by
It seems like assertNull() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

29
        $this->/** @scrutinizer ignore-call */ 
30
               assertNull($db->getPDO());
Loading history...
30
31
        $db->open();
32
        $this->assertTrue($db->isActive());
0 ignored issues
show
Bug introduced by
It seems like assertTrue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

32
        $this->/** @scrutinizer ignore-call */ 
33
               assertTrue($db->isActive());
Loading history...
33
        $this->assertInstanceOf(PDO::class, $db->getPDO());
0 ignored issues
show
Bug introduced by
It seems like assertInstanceOf() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

33
        $this->/** @scrutinizer ignore-call */ 
34
               assertInstanceOf(PDO::class, $db->getPDO());
Loading history...
34
35
        $db->close();
36
        $this->assertFalse($db->isActive());
37
        $this->assertNull($db->getPDO());
38
39
        $db = $this->getConnection(false, 'unknown::memory:');
40
        $this->expectException(Exception::class);
0 ignored issues
show
Bug introduced by
It seems like expectException() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

40
        $this->/** @scrutinizer ignore-call */ 
41
               expectException(Exception::class);
Loading history...
41
        $this->expectExceptionMessage('could not find driver');
0 ignored issues
show
Bug introduced by
It seems like expectExceptionMessage() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

41
        $this->/** @scrutinizer ignore-call */ 
42
               expectExceptionMessage('could not find driver');
Loading history...
42
        $db->open();
43
    }
44
45
    public function testSerialize(): void
46
    {
47
        $db = $this->getConnection();
48
49
        $db->open();
50
51
        $serialized = serialize($db);
52
53
        $this->assertNotNull($db->getPDO());
0 ignored issues
show
Bug introduced by
It seems like assertNotNull() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

53
        $this->/** @scrutinizer ignore-call */ 
54
               assertNotNull($db->getPDO());
Loading history...
54
55
        $unserialized = unserialize($serialized);
56
57
        $this->assertInstanceOf(ConnectionPDOInterface::class, $unserialized);
58
        $this->assertNull($unserialized->getPDO());
59
        $this->assertEquals(123, $unserialized->createCommand('SELECT 123')->queryScalar());
60
    }
61
62
    public function testTransaction(): void
63
    {
64
        $db = $this->getConnection(true);
65
66
        $this->assertNull($db->getTransaction());
67
68
        $transaction = $db->beginTransaction();
69
70
        $this->assertNotNull($db->getTransaction());
71
        $this->assertTrue($transaction->isActive());
72
73
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
74
75
        $transaction->rollBack();
76
77
        $this->assertFalse($transaction->isActive());
78
        $this->assertNull($db->getTransaction());
79
        $this->assertEquals(0, $db->createCommand(
80
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
81
        )->queryScalar());
82
83
        $transaction = $db->beginTransaction();
84
85
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
86
87
        $transaction->commit();
88
89
        $this->assertFalse($transaction->isActive());
90
        $this->assertNull($db->getTransaction());
91
        $this->assertEquals(1, $db->createCommand(
92
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
93
        )->queryScalar());
94
    }
95
96
    public function testRollbackTransactionsWithSavePoints(): void
97
    {
98
        $db = $this->getConnection(true);
99
        $db->open();
100
101
        $transaction = $db->beginTransaction();
102
        assertEquals(1, $transaction->getLevel());
103
104
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
105
106
        $transaction->begin();
107
        assertEquals(2, $transaction->getLevel());
108
109
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
110
111
        $transaction->rollBack();
112
        assertEquals(1, $transaction->getLevel());
113
        $this->assertTrue($transaction->isActive());
114
115
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
116
117
        $transaction->rollBack();
118
        assertEquals(0, $transaction->getLevel());
119
120
        $this->assertFalse($transaction->isActive());
121
        $this->assertNull($db->getTransaction());
122
        $this->assertEquals(0, $db->createCommand(
123
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
124
        )->queryScalar());
125
    }
126
127
    public function testPartialRollbackTransactionsWithSavePoints(): void
128
    {
129
        $db = $this->getConnection(true);
130
        $db->open();
131
132
        $transaction = $db->beginTransaction();
133
        assertEquals(1, $transaction->getLevel());
134
135
        $db->createCommand()->insert('profile', ['description' => 'test transaction1'])->execute();
136
137
        $transaction->begin();
138
        assertEquals(2, $transaction->getLevel());
139
140
        $db->createCommand()->insert('profile', ['description' => 'test transaction2'])->execute();
141
142
        $transaction->rollBack();
143
        assertEquals(1, $transaction->getLevel());
144
        $this->assertTrue($transaction->isActive());
145
146
        $db->createCommand()->insert('profile', ['description' => 'test transaction3'])->execute();
147
148
        $transaction->commit();
149
        assertEquals(0, $transaction->getLevel());
150
151
        $this->assertFalse($transaction->isActive());
152
        $this->assertNull($db->getTransaction());
153
        $this->assertEquals(1, $db->createCommand(
154
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction1'"
155
        )->queryScalar());
156
        $this->assertEquals(0, $db->createCommand(
157
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction2'"
158
        )->queryScalar());
159
        $this->assertEquals(1, $db->createCommand(
160
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction3'"
161
        )->queryScalar());
162
    }
163
164
    public function testCommitTransactionsWithSavepoints(): void
165
    {
166
        $db = $this->getConnection(true);
167
168
        $transaction = $db->beginTransaction();
169
        assertEquals(1, $transaction->getLevel());
170
171
        $db->createCommand()->insert('profile', ['description' => 'test transaction1'])->execute();
172
173
        $transaction->begin();
174
        assertEquals(2, $transaction->getLevel());
175
176
        $db->createCommand()->insert('profile', ['description' => 'test transaction2'])->execute();
177
178
        $transaction->commit();
179
        assertEquals(1, $transaction->getLevel());
180
181
        $db->createCommand()->insert('profile', ['description' => 'test transaction3'])->execute();
182
183
        $transaction->commit();
184
        assertEquals(0, $transaction->getLevel());
185
186
        $this->assertFalse($transaction->isActive());
187
        $this->assertNull($db->getTransaction());
188
        $this->assertEquals(1, $db->createCommand(
189
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction1'"
190
        )->queryScalar());
191
        $this->assertEquals(1, $db->createCommand(
192
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction2'"
193
        )->queryScalar());
194
        $this->assertEquals(1, $db->createCommand(
195
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction3'"
196
        )->queryScalar());
197
    }
198
199
    public function testTransactionShortcutException(): void
200
    {
201
        $db = $this->getConnection(true);
202
203
        $this->expectException(Exception::class);
204
205
        $db->transaction(function () use ($db) {
206
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
207
            throw new Exception('Exception in transaction shortcut');
208
        });
209
        $profilesCount = $db->createCommand(
210
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction shortcut'"
211
        )->queryScalar();
212
        $this->assertEquals(0, $profilesCount, 'profile should not be inserted in transaction shortcut');
213
    }
214
215
    public function testTransactionShortcutCorrect(): void
216
    {
217
        $db = $this->getConnection(true);
218
219
        $result = $db->transaction(static function () use ($db) {
220
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
221
            return true;
222
        });
223
224
        $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
225
226
        $profilesCount = $db->createCommand(
227
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction shortcut'"
228
        )->queryScalar();
229
230
        $this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
231
    }
232
233
    /**
234
     * Tests nested transactions with partial rollback.
235
     *
236
     * {@see https://github.com/yiisoft/yii2/issues/9851}
237
     */
238
    public function testNestedTransaction(): void
239
    {
240
        $db = $this->getConnection();
241
242
        $db->transaction(function (ConnectionInterface $db) {
243
            $this->assertNotNull($db->getTransaction());
244
245
            $db->transaction(function (ConnectionInterface $db) {
246
                $transaction = $db->getTransaction();
247
                $this->assertNotNull($transaction);
248
                $transaction->rollBack();
249
            });
250
251
            $this->assertNotNull($db->getTransaction());
252
        });
253
    }
254
255
    public function testNestedTransactionNotSupported(): void
256
    {
257
        $db = $this->getConnection();
258
259
        $db->setEnableSavepoint(false);
260
261
        $db->transaction(function (ConnectionInterface $db) {
262
            $this->assertNotNull($db->getTransaction());
263
            $this->expectException(NotSupportedException::class);
264
            $db->beginTransaction();
265
        });
266
    }
267
268
    public function testEnableQueryLog(): void
269
    {
270
        $db = $this->getConnection();
271
272
        foreach (['qlog1', 'qlog2', 'qlog3', 'qlog4'] as $table) {
273
            if ($db->getTableSchema($table, true) !== null) {
274
                $db->createCommand()->dropTable($table)->execute();
275
            }
276
        }
277
278
        /* profiling and logging */
279
        $db->setLogger($this->logger);
280
        $db->setProfiler($this->profiler);
281
282
        $this->assertNotNull($this->logger);
283
        $this->assertNotNull($this->profiler);
284
285
        $this->logger->flush();
286
        $this->profiler->flush();
287
288
        $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
289
290
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
0 ignored issues
show
Bug introduced by
It seems like assertCount() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

290
        $this->/** @scrutinizer ignore-call */ 
291
               assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
Loading history...
Bug introduced by
It seems like getInaccessibleProperty() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

290
        $this->assertCount(1, $this->/** @scrutinizer ignore-call */ getInaccessibleProperty($this->logger, 'messages'));
Loading history...
291
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
292
        $this->assertNotNull($db->getTableSchema('qlog1', true));
293
294
        $this->logger->flush();
295
        $this->profiler->flush();
296
297
        $db->createCommand('SELECT * FROM {{qlog1}}')->queryAll();
298
299
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
300
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
301
302
        /* profiling only */
303
        $db->setLogger(null);
304
        $db->setProfiler($this->profiler);
305
306
        $this->logger->flush();
307
        $this->profiler->flush();
308
309
        $db->createCommand()->createTable('qlog2', ['id' => 'pk'])->execute();
310
311
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
312
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
313
        $this->assertNotNull($db->getTableSchema('qlog2', true));
314
315
        $this->logger->flush();
316
        $this->profiler->flush();
317
318
        $db->createCommand('SELECT * FROM {{qlog2}}')->queryAll();
319
320
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
321
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
322
323
        /* logging only */
324
        $db->setLogger($this->logger);
325
        $db->setProfiler(null);
326
327
        $this->logger->flush();
328
        $this->profiler->flush();
329
330
        $db->createCommand()->createTable('qlog3', ['id' => 'pk'])->execute();
331
332
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
333
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
334
        $this->assertNotNull($db->getTableSchema('qlog3', true));
335
336
        $this->logger->flush();
337
        $this->profiler->flush();
338
339
        $db->createCommand('SELECT * FROM {{qlog3}}')->queryAll();
340
341
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
342
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
343
344
        /* disabled */
345
        $db->setLogger(null);
346
        $db->setProfiler(null);
347
348
        $this->logger->flush();
349
        $this->profiler->flush();
350
351
        $db->createCommand()->createTable('qlog4', ['id' => 'pk'])->execute();
352
353
        $this->assertNotNull($db->getTableSchema('qlog4', true));
354
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
355
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
356
357
        $db->createCommand('SELECT * FROM {{qlog4}}')->queryAll();
358
359
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
360
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
361
    }
362
363
    public function testExceptionContainsRawQuery(): void
364
    {
365
        $db = $this->getConnection();
366
367
        if ($db->getTableSchema('qlog1', true) === null) {
368
            $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
369
        }
370
371
        $db->setEmulatePrepare(true);
372
373
        /* profiling and logging */
374
        $db->setLogger($this->logger);
375
        $db->setProfiler($this->profiler);
376
377
        $this->runExceptionTest($db);
378
379
        /* profiling only */
380
        $db->setLogger(null);
381
        $db->setProfiler($this->profiler);
382
383
        $this->runExceptionTest($db);
384
385
        /* logging only */
386
        $db->setLogger($this->logger);
387
        $db->setProfiler(null);
388
389
        $this->runExceptionTest($db);
390
391
        /* disabled */
392
        $db->setLogger(null);
393
        $db->setProfiler(null);
394
395
        $this->runExceptionTest($db);
396
    }
397
398
    /**
399
     * @param ConnectionInterface $db
400
     */
401
    private function runExceptionTest(ConnectionInterface $db): void
402
    {
403
        $thrown = false;
404
405
        try {
406
            $db->createCommand('INSERT INTO qlog1(a) VALUES(:a);', [':a' => 1])->execute();
407
        } catch (Exception $e) {
408
            $this->assertStringContainsString(
0 ignored issues
show
Bug introduced by
It seems like assertStringContainsString() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

408
            $this->/** @scrutinizer ignore-call */ 
409
                   assertStringContainsString(
Loading history...
409
                'INSERT INTO qlog1(a) VALUES(1);',
410
                $e->getMessage(),
411
                'Exceptions message should contain raw SQL query: ' . (string) $e
412
            );
413
414
            $thrown = true;
415
        }
416
417
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
418
419
        $thrown = false;
420
421
        try {
422
            $db->createCommand(
423
                'SELECT * FROM qlog1 WHERE id=:a ORDER BY nonexistingcolumn;',
424
                [':a' => 1]
425
            )->queryAll();
426
        } catch (Exception $e) {
427
            $this->assertStringContainsString(
428
                'SELECT * FROM qlog1 WHERE id=1 ORDER BY nonexistingcolumn;',
429
                $e->getMessage(),
430
                'Exceptions message should contain raw SQL query: ' . (string) $e
431
            );
432
433
            $thrown = true;
434
        }
435
436
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
437
    }
438
439
    /**
440
     * Ensure database connection is reset on when a connection is cloned.
441
     *
442
     * Make sure each connection element has its own PDO instance i.e. own connection to the DB.
443
     * Also transaction elements should not be shared between two connections.
444
     */
445
    public function testClone(): void
446
    {
447
        $db = $this->getConnection();
448
449
        $this->assertNull($db->getTransaction());
450
        $this->assertNull($db->getPDO());
451
452
        $db->open();
453
454
        $this->assertNull($db->getTransaction());
455
        $this->assertNotNull($db->getPDO());
456
457
        $conn2 = clone $db;
458
459
        $this->assertNull($db->getTransaction());
460
        $this->assertNotNull($db->getPDO());
461
462
        $this->assertNull($conn2->getTransaction());
463
        $this->assertNull($conn2->getPDO());
464
465
        $db->beginTransaction();
466
467
        $this->assertNotNull($db->getTransaction());
468
        $this->assertNotNull($db->getPDO());
469
470
        $this->assertNull($conn2->getTransaction());
471
        $this->assertNull($conn2->getPDO());
472
473
        $conn3 = clone $db;
474
475
        $this->assertNotNull($db->getTransaction());
476
        $this->assertNotNull($db->getPDO());
477
        $this->assertNull($conn3->getTransaction());
478
        $this->assertNull($conn3->getPDO());
479
    }
480
}
481