Passed
Pull Request — master (#163)
by Wilmer
10:25
created

TestConnectionTrait::testSerialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 15
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\TestUtility;
6
7
use PDO;
8
use Yiisoft\Db\Connection\Connection;
9
use Yiisoft\Db\Exception\Exception;
10
use Yiisoft\Db\Exception\NotSupportedException;
11
use Yiisoft\Db\Transaction\Transaction;
12
13
use function serialize;
14
use function unserialize;
15
16
trait TestConnectionTrait
17
{
18
    public function testOpenClose(): void
19
    {
20
        $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

20
        /** @scrutinizer ignore-call */ 
21
        $db = $this->getConnection();
Loading history...
21
22
        $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

22
        $this->/** @scrutinizer ignore-call */ 
23
               assertFalse($db->isActive());
Loading history...
23
        $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

23
        $this->/** @scrutinizer ignore-call */ 
24
               assertNull($db->getPDO());
Loading history...
24
25
        $db->open();
26
27
        $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

27
        $this->/** @scrutinizer ignore-call */ 
28
               assertTrue($db->isActive());
Loading history...
28
        $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

28
        $this->/** @scrutinizer ignore-call */ 
29
               assertInstanceOf(PDO::class, $db->getPDO());
Loading history...
29
30
        $db->close();
31
32
        $this->assertFalse($db->isActive());
33
        $this->assertNull($db->getPDO());
34
35
        $db = new Connection($this->cache, $this->logger, $this->profiler, 'unknown::memory:');
36
37
        $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

37
        $this->/** @scrutinizer ignore-call */ 
38
               expectException(Exception::class);
Loading history...
38
        $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

38
        $this->/** @scrutinizer ignore-call */ 
39
               expectExceptionMessage('could not find driver');
Loading history...
39
40
        $db->open();
41
    }
42
43
    public function testSerialize(): void
44
    {
45
        $db = $this->getConnection();
46
47
        $db->open();
48
49
        $serialized = serialize($db);
50
51
        $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

51
        $this->/** @scrutinizer ignore-call */ 
52
               assertNotNull($db->getPDO());
Loading history...
52
53
        $unserialized = unserialize($serialized);
54
55
        $this->assertInstanceOf(Connection::class, $unserialized);
56
        $this->assertNull($unserialized->getPDO());
57
        $this->assertEquals(123, $unserialized->createCommand('SELECT 123')->queryScalar());
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

57
        $this->/** @scrutinizer ignore-call */ 
58
               assertEquals(123, $unserialized->createCommand('SELECT 123')->queryScalar());
Loading history...
58
    }
59
60
    public function testTransaction(): void
61
    {
62
        $db = $this->getConnection();
63
64
        $this->assertNull($db->getTransaction());
65
66
        $transaction = $db->beginTransaction();
67
68
        $this->assertNotNull($db->getTransaction());
69
        $this->assertTrue($transaction->isActive());
70
71
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
72
73
        $transaction->rollBack();
74
75
        $this->assertFalse($transaction->isActive());
76
        $this->assertNull($db->getTransaction());
77
        $this->assertEquals(0, $db->createCommand(
78
            "SELECT COUNT(*) FROM profile WHERE description = 'test transaction';"
79
        )->queryScalar());
80
81
        $transaction = $db->beginTransaction();
82
83
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
84
85
        $transaction->commit();
86
87
        $this->assertFalse($transaction->isActive());
88
        $this->assertNull($db->getTransaction());
89
        $this->assertEquals(1, $db->createCommand(
90
            "SELECT COUNT(*) FROM profile WHERE description = 'test transaction';"
91
        )->queryScalar());
92
    }
93
94
    public function testTransactionIsolation(): void
95
    {
96
        $db = $this->getConnection(true);
97
98
        $transaction = $db->beginTransaction(Transaction::READ_UNCOMMITTED);
99
100
        $transaction->commit();
101
102
        $transaction = $db->beginTransaction(Transaction::READ_COMMITTED);
103
104
        $transaction->commit();
105
106
        $transaction = $db->beginTransaction(Transaction::REPEATABLE_READ);
107
108
        $transaction->commit();
109
110
        $transaction = $db->beginTransaction(Transaction::SERIALIZABLE);
111
112
        $transaction->commit();
113
114
        /* should not be any exception so far */
115
        $this->assertTrue(true);
116
    }
117
118
    public function testTransactionShortcutException(): void
119
    {
120
        $db = $this->getConnection();
121
122
        $result = $db->transaction(static function (Connection $db) {
123
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
124
            return true;
125
        }, Transaction::READ_UNCOMMITTED);
126
127
        $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
128
129
        $profilesCount = $db->createCommand(
130
            "SELECT COUNT(*) FROM profile WHERE description = 'test transaction shortcut';"
131
        )->queryScalar();
132
133
        $this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
134
    }
135
136
    public function testTransactionShortcutCorrect(): void
137
    {
138
        $db = $this->getConnection(true);
139
140
        $result = $db->transaction(static function () use ($db) {
141
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
142
            return true;
143
        });
144
145
        $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
146
147
        $profilesCount = $db->createCommand(
148
            "SELECT COUNT(*) FROM profile WHERE description = 'test transaction shortcut';"
149
        )->queryScalar();
150
151
        $this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
152
    }
153
154
    public function testTransactionShortcutCustom(): void
155
    {
156
        $db = $this->getConnection(true);
157
158
        $result = $db->transaction(static function (Connection $db) {
159
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
160
            return true;
161
        }, Transaction::READ_UNCOMMITTED);
162
163
        $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
164
165
        $profilesCount = $db->createCommand(
166
            "SELECT COUNT(*) FROM profile WHERE description = 'test transaction shortcut';"
167
        )->queryScalar();
168
169
        $this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
170
    }
171
172
    /**
173
     * Tests nested transactions with partial rollback.
174
     *
175
     * {@see https://github.com/yiisoft/yii2/issues/9851}
176
     */
177
    public function testNestedTransaction(): void
178
    {
179
        $db = $this->getConnection();
180
181
        $db->transaction(function (Connection $db) {
182
            $this->assertNotNull($db->getTransaction());
183
184
            $db->transaction(function (Connection $db) {
185
                $this->assertNotNull($db->getTransaction());
186
                $db->getTransaction()->rollBack();
187
            });
188
189
            $this->assertNotNull($db->getTransaction());
190
        });
191
    }
192
193
    public function testNestedTransactionNotSupported(): void
194
    {
195
        $db = $this->getConnection();
196
197
        $db->setEnableSavepoint(false);
198
199
        $db->transaction(function (Connection $db) {
200
            $this->assertNotNull($db->getTransaction());
201
            $this->expectException(NotSupportedException::class);
202
            $db->beginTransaction();
203
        });
204
    }
205
206
    public function testEnableQueryLog(): void
207
    {
208
        $db = $this->getConnection();
209
210
        foreach (['qlog1', 'qlog2', 'qlog3', 'qlog4'] as $table) {
211
            if ($db->getTableSchema($table, true) !== null) {
212
                $db->createCommand()->dropTable($table)->execute();
213
            }
214
        }
215
216
        /* profiling and logging */
217
        $db->setEnableLogging(true);
218
        $db->setEnableProfiling(true);
219
220
        $this->logger->flush();
221
        $this->profiler->flush();
222
223
        $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
224
225
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
0 ignored issues
show
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

225
        $this->assertCount(1, $this->/** @scrutinizer ignore-call */ getInaccessibleProperty($this->logger, 'messages'));
Loading history...
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

225
        $this->/** @scrutinizer ignore-call */ 
226
               assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
Loading history...
226
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
227
        $this->assertNotNull($db->getTableSchema('qlog1', true));
228
229
        $this->logger->flush();
230
        $this->profiler->flush();
231
232
        $db->createCommand('SELECT * FROM qlog1')->queryAll();
233
234
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
235
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
236
237
        /* profiling only */
238
        $db->setEnableLogging(false);
239
        $db->setEnableProfiling(true);
240
241
        $this->logger->flush();
242
        $this->profiler->flush();
243
244
        $db->createCommand()->createTable('qlog2', ['id' => 'pk'])->execute();
245
246
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
247
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
248
        $this->assertNotNull($db->getTableSchema('qlog2', true));
249
250
        $this->logger->flush();
251
        $this->profiler->flush();
252
253
        $db->createCommand('SELECT * FROM qlog2')->queryAll();
254
255
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
256
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
257
258
        /* logging only */
259
        $db->setEnableLogging(true);
260
        $db->setEnableProfiling(false);
261
262
        $this->logger->flush();
263
        $this->profiler->flush();
264
265
        $db->createCommand()->createTable('qlog3', ['id' => 'pk'])->execute();
266
267
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
268
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
269
        $this->assertNotNull($db->getTableSchema('qlog3', true));
270
271
        $this->logger->flush();
272
        $this->profiler->flush();
273
274
        $db->createCommand('SELECT * FROM qlog3')->queryAll();
275
276
        $this->assertCount(1, $this->getInaccessibleProperty($this->logger, 'messages'));
277
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
278
279
        /* disabled */
280
        $db->setEnableLogging(false);
281
        $db->setEnableProfiling(false);
282
283
        $this->logger->flush();
284
        $this->profiler->flush();
285
286
        $db->createCommand()->createTable('qlog4', ['id' => 'pk'])->execute();
287
288
        $this->assertNotNull($db->getTableSchema('qlog4', true));
289
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
290
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
291
292
        $db->createCommand('SELECT * FROM qlog4')->queryAll();
293
294
        $this->assertCount(0, $this->getInaccessibleProperty($this->logger, 'messages'));
295
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
296
    }
297
298
    public function testExceptionContainsRawQuery(): void
299
    {
300
        $db = $this->getConnection();
301
302
        if ($db->getTableSchema('qlog1', true) === null) {
303
            $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
304
        }
305
306
        $db->setEmulatePrepare(true);
307
308
        /* profiling and logging */
309
        $db->setEnableLogging(true);
310
        $db->setEnableProfiling(true);
311
312
        $this->runExceptionTest($db);
313
314
        /* profiling only */
315
        $db->setEnableLogging(false);
316
        $db->setEnableProfiling(true);
317
318
        $this->runExceptionTest($db);
319
320
        /* logging only */
321
        $db->setEnableLogging(true);
322
        $db->setEnableProfiling(false);
323
324
        $this->runExceptionTest($db);
325
326
        /* disabled */
327
        $db->setEnableLogging(false);
328
        $db->setEnableProfiling(false);
329
330
        $this->runExceptionTest($db);
331
    }
332
333
    /**
334
     * @param Connection $db
335
     */
336
    private function runExceptionTest(Connection $db): void
337
    {
338
        $thrown = false;
339
340
        try {
341
            $db->createCommand('INSERT INTO qlog1(a) VALUES(:a);', [':a' => 1])->execute();
342
        } catch (Exception $e) {
343
            $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

343
            $this->/** @scrutinizer ignore-call */ 
344
                   assertStringContainsString(
Loading history...
344
                'INSERT INTO qlog1(a) VALUES(1);',
345
                $e->getMessage(),
346
                'Exceptions message should contain raw SQL query: ' . (string) $e
347
            );
348
349
            $thrown = true;
350
        }
351
352
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
353
354
        $thrown = false;
355
356
        try {
357
            $db->createCommand(
358
                'SELECT * FROM qlog1 WHERE id=:a ORDER BY nonexistingcolumn;',
359
                [':a' => 1]
360
            )->queryAll();
361
        } catch (Exception $e) {
362
            $this->assertStringContainsString(
363
                'SELECT * FROM qlog1 WHERE id=1 ORDER BY nonexistingcolumn;',
364
                $e->getMessage(),
365
                'Exceptions message should contain raw SQL query: ' . (string) $e
366
            );
367
368
            $thrown = true;
369
        }
370
371
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
372
    }
373
374
    /**
375
     * Ensure database connection is reset on when a connection is cloned.
376
     *
377
     * Make sure each connection element has its own PDO instance i.e. own connection to the DB.
378
     * Also transaction elements should not be shared between two connections.
379
     */
380
    public function testClone(): void
381
    {
382
        $db = $this->getConnection();
383
384
        $this->assertNull($db->getTransaction());
385
        $this->assertNull($db->getPDO());
386
387
        $db->open();
388
389
        $this->assertNull($db->getTransaction());
390
        $this->assertNotNull($db->getPDO());
391
392
        $conn2 = clone $db;
393
394
        $this->assertNull($db->getTransaction());
395
        $this->assertNotNull($db->getPDO());
396
397
        $this->assertNull($conn2->getTransaction());
398
        $this->assertNull($conn2->getPDO());
399
400
        $db->beginTransaction();
401
402
        $this->assertNotNull($db->getTransaction());
403
        $this->assertNotNull($db->getPDO());
404
405
        $this->assertNull($conn2->getTransaction());
406
        $this->assertNull($conn2->getPDO());
407
408
        $conn3 = clone $db;
409
410
        $this->assertNotNull($db->getTransaction());
411
        $this->assertNotNull($db->getPDO());
412
        $this->assertNull($conn3->getTransaction());
413
        $this->assertNull($conn3->getPDO());
414
    }
415
}
416