Passed
Pull Request — master (#203)
by Wilmer
12:43
created

TestConnectionTrait::runExceptionTest()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 3

Importance

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

19
        /** @scrutinizer ignore-call */ 
20
        $db = $this->getConnection();
Loading history...
20
21 4
        $db->open();
22
23 4
        $serialized = serialize($db);
24
25 4
        $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

25
        $this->/** @scrutinizer ignore-call */ 
26
               assertNotNull($db->getPDO());
Loading history...
26
27 4
        $unserialized = unserialize($serialized);
28
29 4
        $this->assertInstanceOf(ConnectionInterface::class, $unserialized);
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

29
        $this->/** @scrutinizer ignore-call */ 
30
               assertInstanceOf(ConnectionInterface::class, $unserialized);
Loading history...
30 4
        $this->assertNull($unserialized->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

30
        $this->/** @scrutinizer ignore-call */ 
31
               assertNull($unserialized->getPDO());
Loading history...
31 4
        $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

31
        $this->/** @scrutinizer ignore-call */ 
32
               assertEquals(123, $unserialized->createCommand('SELECT 123')->queryScalar());
Loading history...
32 4
    }
33
34 4
    public function testTransaction(): void
35
    {
36 4
        $db = $this->getConnection();
37
38 4
        $this->assertNull($db->getTransaction());
39
40 4
        $transaction = $db->beginTransaction();
41
42 4
        $this->assertNotNull($db->getTransaction());
43 4
        $this->assertTrue($transaction->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

43
        $this->/** @scrutinizer ignore-call */ 
44
               assertTrue($transaction->isActive());
Loading history...
44
45 4
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
46
47 4
        $transaction->rollBack();
48
49 4
        $this->assertFalse($transaction->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

49
        $this->/** @scrutinizer ignore-call */ 
50
               assertFalse($transaction->isActive());
Loading history...
50 4
        $this->assertNull($db->getTransaction());
51 4
        $this->assertEquals(0, $db->createCommand(
52 4
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
53 4
        )->queryScalar());
54
55 4
        $transaction = $db->beginTransaction();
56
57 4
        $db->createCommand()->insert('profile', ['description' => 'test transaction'])->execute();
58
59 4
        $transaction->commit();
60
61 4
        $this->assertFalse($transaction->isActive());
62 4
        $this->assertNull($db->getTransaction());
63 4
        $this->assertEquals(1, $db->createCommand(
64 4
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction'"
65 4
        )->queryScalar());
66 4
    }
67
68 2
    public function testTransactionIsolation(): void
69
    {
70 2
        $db = $this->getConnection(true);
71
72 2
        $transaction = $db->beginTransaction(Transaction::READ_UNCOMMITTED);
73
74 2
        $transaction->commit();
75
76 2
        $transaction = $db->beginTransaction(Transaction::READ_COMMITTED);
77
78 2
        $transaction->commit();
79
80 2
        $transaction = $db->beginTransaction(Transaction::REPEATABLE_READ);
81
82 2
        $transaction->commit();
83
84 2
        $transaction = $db->beginTransaction(Transaction::SERIALIZABLE);
85
86 2
        $transaction->commit();
87
88
        /* should not be any exception so far */
89 2
        $this->assertTrue(true);
90 2
    }
91
92 4
    public function testTransactionShortcutException(): void
93
    {
94 4
        $db = $this->getConnection(true);
95
96 4
        $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

96
        $this->/** @scrutinizer ignore-call */ 
97
               expectException(Exception::class);
Loading history...
97 4
98 4
        $db->transaction(function () use ($db) {
99 4
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
100
            throw new Exception('Exception in transaction shortcut');
101 4
        });
102
        $profilesCount = $db->createCommand(
103 4
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction shortcut'"
104 4
        )->queryScalar();
105 4
        $this->assertEquals(0, $profilesCount, 'profile should not be inserted in transaction shortcut');
106
    }
107 4
108 4
    public function testTransactionShortcutCorrect(): void
109
    {
110 4
        $db = $this->getConnection(true);
111
112 4
        $result = $db->transaction(static function () use ($db) {
113
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
114 4
            return true;
115 4
        });
116 4
117 4
        $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
118
119 4
        $profilesCount = $db->createCommand(
120
            "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'test transaction shortcut'"
121 4
        )->queryScalar();
122 4
123 4
        $this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
124
    }
125 4
126 4
    public function testTransactionShortcutCustom(): void
127
    {
128 4
        $db = $this->getConnection(true);
129
130 4
        $result = $db->transaction(static function (ConnectionInterface $db) {
131
            $db->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
132 4
            return true;
133 4
        }, Transaction::READ_UNCOMMITTED);
134 4
135 4
        $this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
136
137 4
        $profilesCount = $db->createCommand(
138
            "SELECT COUNT(*) FROM profile WHERE description = 'test transaction shortcut';"
139 4
        )->queryScalar();
140 4
141 4
        $this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
142
    }
143 4
144 4
    /**
145
     * Tests nested transactions with partial rollback.
146
     *
147
     * {@see https://github.com/yiisoft/yii2/issues/9851}
148
     */
149
    public function testNestedTransaction(): void
150
    {
151 4
        $db = $this->getConnection();
152
153 4
        $db->transaction(function (ConnectionInterface $db) {
154
            $this->assertNotNull($db->getTransaction());
0 ignored issues
show
Bug introduced by
The method getTransaction() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

154
            $this->assertNotNull($db->/** @scrutinizer ignore-call */ getTransaction());
Loading history...
155 4
156 4
            $db->transaction(function (ConnectionInterface $db) {
0 ignored issues
show
Bug introduced by
The method transaction() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

156
            $db->/** @scrutinizer ignore-call */ 
157
                 transaction(function (ConnectionInterface $db) {
Loading history...
157
                $this->assertNotNull($db->getTransaction());
158 4
                $db->getTransaction()->rollBack();
159 4
            });
160 4
161 4
            $this->assertNotNull($db->getTransaction());
162
        });
163 4
    }
164 4
165 4
    public function testNestedTransactionNotSupported(): void
166
    {
167 4
        $db = $this->getConnection();
168
169 4
        $db->setEnableSavepoint(false);
170
171 4
        $db->transaction(function (ConnectionInterface $db) {
172
            $this->assertNotNull($db->getTransaction());
173 4
            $this->expectException(NotSupportedException::class);
174 4
            $db->beginTransaction();
0 ignored issues
show
Bug introduced by
The method beginTransaction() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

174
            $db->/** @scrutinizer ignore-call */ 
175
                 beginTransaction();
Loading history...
175 4
        });
176 4
    }
177 4
178
    public function testEnableQueryLog(): void
179
    {
180 4
        $db = $this->getConnection();
181
182 4
        foreach (['qlog1', 'qlog2', 'qlog3', 'qlog4'] as $table) {
183
            if ($db->getTableSchema($table, true) !== null) {
184 4
                $db->createCommand()->dropTable($table)->execute();
185 4
            }
186 1
        }
187
188
        /* profiling and logging */
189
        $db->setEnableLogging(true);
190
        $db->setEnableProfiling(true);
191 4
192 4
        $this->logger->flush();
193
        $this->profiler->flush();
194 4
195 4
        $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
196
197 4
        $message = $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

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

198
        $this->/** @scrutinizer ignore-call */ 
199
               assertCount(1, $message->all());
Loading history...
199 4
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
200 4
        $this->assertNotNull($db->getTableSchema('qlog1', true));
201 4
202
        $this->logger->flush();
203 4
        $this->profiler->flush();
204 4
205
        $db->createCommand('SELECT * FROM {{qlog1}}')->queryAll();
206 4
207
        $message = $this->getInaccessibleProperty($this->logger, 'messages');
208 4
        $this->assertCount(1, $message->all());
209 4
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
210
211
        /* profiling only */
212 4
        $db->setEnableLogging(false);
213 4
        $db->setEnableProfiling(true);
214
215 4
        $this->logger->flush();
216 4
        $this->profiler->flush();
217
218 4
        $db->createCommand()->createTable('qlog2', ['id' => 'pk'])->execute();
219
220 4
        $message = $this->getInaccessibleProperty($this->logger, 'messages');
221 4
        $this->assertCount(0, $message->all());
222 4
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
223
        $this->assertNotNull($db->getTableSchema('qlog2', true));
224 4
225 4
        $this->logger->flush();
226
        $this->profiler->flush();
227 4
228
        $db->createCommand('SELECT * FROM {{qlog2}}')->queryAll();
229 4
230 4
        $message = $this->getInaccessibleProperty($this->logger, 'messages');
231
        $this->assertCount(0, $message->all());
232
        $this->assertCount(1, $this->getInaccessibleProperty($this->profiler, 'messages'));
233 4
234 4
        /* logging only */
235
        $db->setEnableLogging(true);
236 4
        $db->setEnableProfiling(false);
237 4
238
        $this->logger->flush();
239 4
        $this->profiler->flush();
240
241 4
        $db->createCommand()->createTable('qlog3', ['id' => 'pk'])->execute();
242 4
243 4
        $message = $this->getInaccessibleProperty($this->logger, 'messages');
244
        $this->assertCount(1, $message->all());
245 4
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
246 4
        $this->assertNotNull($db->getTableSchema('qlog3', true));
247
248 4
        $this->logger->flush();
249
        $this->profiler->flush();
250 4
251 4
        $db->createCommand('SELECT * FROM {{qlog3}}')->queryAll();
252
253
        $message = $this->getInaccessibleProperty($this->logger, 'messages');
254 4
        $this->assertCount(1, $message->all());
255 4
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
256
257 4
        /* disabled */
258 4
        $db->setEnableLogging(false);
259
        $db->setEnableProfiling(false);
260 4
261
        $this->logger->flush();
262 4
        $this->profiler->flush();
263 4
264 4
        $db->createCommand()->createTable('qlog4', ['id' => 'pk'])->execute();
265
266 4
        $message = $this->getInaccessibleProperty($this->logger, 'messages');
267
        $this->assertNotNull($db->getTableSchema('qlog4', true));
268 4
        $this->assertCount(0, $message->all());
269 4
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
270 4
271
        $db->createCommand('SELECT * FROM {{qlog4}}')->queryAll();
272 3
273
        $message = $this->getInaccessibleProperty($this->logger, 'messages');
274 3
        $this->assertCount(0, $message->all());
275
        $this->assertCount(0, $this->getInaccessibleProperty($this->profiler, 'messages'));
276 3
    }
277
278
    public function testExceptionContainsRawQuery(): void
279
    {
280 3
        $db = $this->getConnection();
281
282
        if ($db->getTableSchema('qlog1', true) === null) {
283 3
            $db->createCommand()->createTable('qlog1', ['id' => 'pk'])->execute();
284 3
        }
285
286 3
        $db->setEmulatePrepare(true);
287
288
        /* profiling and logging */
289 3
        $db->setEnableLogging(true);
290 3
        $db->setEnableProfiling(true);
291
292 3
        $this->runExceptionTest($db);
293
294
        /* profiling only */
295 3
        $db->setEnableLogging(false);
296 3
        $db->setEnableProfiling(true);
297
298 3
        $this->runExceptionTest($db);
299
300
        /* logging only */
301 3
        $db->setEnableLogging(true);
302 3
        $db->setEnableProfiling(false);
303
304 3
        $this->runExceptionTest($db);
305 3
306
        /* disabled */
307
        $db->setEnableLogging(false);
308
        $db->setEnableProfiling(false);
309
310 3
        $this->runExceptionTest($db);
311
    }
312 3
313
    /**
314
     * @param ConnectionInterface $db
315 3
     */
316 3
    private function runExceptionTest(ConnectionInterface $db): void
317 3
    {
318 3
        $thrown = false;
319 3
320 3
        try {
321
            $db->createCommand('INSERT INTO qlog1(a) VALUES(:a);', [':a' => 1])->execute();
322
        } catch (Exception $e) {
323 3
            $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

323
            $this->/** @scrutinizer ignore-call */ 
324
                   assertStringContainsString(
Loading history...
324
                'INSERT INTO qlog1(a) VALUES(1);',
325
                $e->getMessage(),
326 3
                'Exceptions message should contain raw SQL query: ' . (string) $e
327
            );
328 3
329
            $thrown = true;
330
        }
331 3
332 3
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
333 3
334 3
        $thrown = false;
335 3
336 3
        try {
337 3
            $db->createCommand(
338 3
                'SELECT * FROM qlog1 WHERE id=:a ORDER BY nonexistingcolumn;',
339 3
                [':a' => 1]
340
            )->queryAll();
341
        } catch (Exception $e) {
342 3
            $this->assertStringContainsString(
343
                'SELECT * FROM qlog1 WHERE id=1 ORDER BY nonexistingcolumn;',
344
                $e->getMessage(),
345 3
                'Exceptions message should contain raw SQL query: ' . (string) $e
346 3
            );
347
348
            $thrown = true;
349
        }
350
351
        $this->assertTrue($thrown, 'An exception should have been thrown by the command.');
352
    }
353
354 4
    /**
355
     * Ensure database connection is reset on when a connection is cloned.
356 4
     *
357
     * Make sure each connection element has its own PDO instance i.e. own connection to the DB.
358 4
     * Also transaction elements should not be shared between two connections.
359 4
     */
360
    public function testClone(): void
361 4
    {
362
        $db = $this->getConnection();
363 4
364 4
        $this->assertNull($db->getTransaction());
365
        $this->assertNull($db->getPDO());
366 4
367
        $db->open();
368 4
369 4
        $this->assertNull($db->getTransaction());
370
        $this->assertNotNull($db->getPDO());
371 4
372 4
        $conn2 = clone $db;
373
374 4
        $this->assertNull($db->getTransaction());
375
        $this->assertNotNull($db->getPDO());
376 4
377 4
        $this->assertNull($conn2->getTransaction());
378
        $this->assertNull($conn2->getPDO());
379 4
380 4
        $db->beginTransaction();
381
382 4
        $this->assertNotNull($db->getTransaction());
383
        $this->assertNotNull($db->getPDO());
384 4
385 4
        $this->assertNull($conn2->getTransaction());
386 4
        $this->assertNull($conn2->getPDO());
387 4
388 4
        $conn3 = clone $db;
389
390
        $this->assertNotNull($db->getTransaction());
391
        $this->assertNotNull($db->getPDO());
392
        $this->assertNull($conn3->getTransaction());
393
        $this->assertNull($conn3->getPDO());
394
    }
395
}
396