Passed
Push — int-types ( b1a342...f67018 )
by Sam
06:52
created

TransactionTest::testNestedTransaction()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 18
nc 4
nop 0
dl 0
loc 28
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SilverStripe\ORM\DB;
6
use SilverStripe\ORM\DataObject;
7
use SilverStripe\Dev\SapphireTest;
8
use SilverStripe\ORM\Tests\TransactionTest\TestObject;
9
10
class TransactionTest extends SapphireTest
11
{
12
    protected $usesDatabase = true;
13
14
    protected $usesTransactions = false;
15
16
    protected static $extra_dataobjects = [
17
        TransactionTest\TestObject::class,
18
    ];
19
20
    public static function setUpBeforeClass()
21
    {
22
        parent::setUpBeforeClass();
23
        if (!DB::get_conn()->supportsTransactions()) {
24
            static::markTestSkipped('Current database does not support transactions');
25
        }
26
    }
27
28
    public function testTransactions()
29
    {
30
        $conn = DB::get_conn();
31
        if (!$conn->supportsTransactions()) {
32
            $this->markTestSkipped("DB Doesn't support transactions");
33
            return;
34
        }
35
36
        // Test that successful transactions are comitted
37
        $obj = new TestObject();
38
        $failed = false;
39
        $conn->withTransaction(
40
            function () use (&$obj) {
41
                $obj->Title = 'Save 1';
42
                $obj->write();
43
            },
44
            function () use (&$failed) {
45
                $failed = true;
46
            }
47
        );
48
        $this->assertEquals('Save 1', TestObject::get()->first()->Title);
49
        $this->assertFalse($failed);
50
51
        // Test failed transactions are rolled back
52
        $ex = null;
53
        $failed = false;
54
        try {
55
            $conn->withTransaction(
56
                function () use (&$obj) {
57
                    $obj->Title = 'Save 2';
58
                    $obj->write();
59
                    throw new \Exception("error");
60
                },
61
                function () use (&$failed) {
62
                    $failed = true;
63
                }
64
            );
65
        } catch (\Exception $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
66
        }
67
        $this->assertTrue($failed);
68
        $this->assertEquals('Save 1', TestObject::get()->first()->Title);
69
        $this->assertInstanceOf('Exception', $ex);
70
        $this->assertEquals('error', $ex->getMessage());
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on null. ( Ignorable by Annotation )

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

70
        $this->assertEquals('error', $ex->/** @scrutinizer ignore-call */ getMessage());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
71
    }
72
73
    public function testNestedTransaction()
74
    {
75
        if (!DB::get_conn()->supportsSavepoints()) {
0 ignored issues
show
Bug introduced by
The method supportsSavepoints() does not exist on SilverStripe\ORM\Connect\Database. Since it exists in all sub-types, consider adding an abstract or default implementation to SilverStripe\ORM\Connect\Database. ( Ignorable by Annotation )

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

75
        if (!DB::get_conn()->/** @scrutinizer ignore-call */ supportsSavepoints()) {
Loading history...
76
            static::markTestSkipped('Current database does not support savepoints');
77
        }
78
79
        $this->assertCount(0, TestObject::get());
80
        try {
81
            DB::get_conn()->withTransaction(function () {
82
                $obj = TransactionTest\TestObject::create();
83
                $obj->Title = 'Test';
84
                $obj->write();
85
86
                $this->assertCount(1, TestObject::get());
87
88
                DB::get_conn()->withTransaction(function () {
89
                    $obj = TransactionTest\TestObject::create();
90
                    $obj->Title = 'Test2';
91
                    $obj->write();
92
                    $this->assertCount(2, TestObject::get());
93
                });
94
95
                throw new \Exception('roll back transaction');
96
            });
97
        } catch (\Exception $e) {
98
            $this->assertEquals('roll back transaction', $e->getMessage());
99
        }
100
        $this->assertCount(0, TestObject::get());
101
    }
102
103
    public function testCreateWithTransaction()
104
    {
105
        // First/Second in a successful transaction
106
        DB::get_conn()->transactionStart();
107
        $obj = new TransactionTest\TestObject();
108
        $obj->Title = 'First page';
109
        $obj->write();
110
111
        $obj = new TransactionTest\TestObject();
112
        $obj->Title = 'Second page';
113
        $obj->write();
114
        DB::get_conn()->transactionEnd();
115
116
        // Third/Fourth in a rolled back transaction
117
        DB::get_conn()->transactionStart();
118
        $obj = new TransactionTest\TestObject();
119
        $obj->Title = 'Third page';
120
        $obj->write();
121
122
        $obj = new TransactionTest\TestObject();
123
        $obj->Title = 'Fourth page';
124
        $obj->write();
125
        DB::get_conn()->transactionRollback();
126
127
128
        $first = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='First page'");
129
        $second = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Second page'");
130
        $third = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Third page'");
131
        $fourth = DataObject::get(TransactionTest\TestObject::class, "\"Title\"='Fourth page'");
132
133
        //These pages should be in the system
134
        $this->assertTrue(is_object($first) && $first->exists());
135
        $this->assertTrue(is_object($second) && $second->exists());
136
137
        //These pages should NOT exist, we rolled back
138
        $this->assertFalse(is_object($third) && $third->exists());
139
        $this->assertFalse(is_object($fourth) && $fourth->exists());
140
    }
141
142
    public function testReadOnlyTransaction()
143
    {
144
        if (!DB::get_conn()->supportsTransactions()) {
145
            $this->markTestSkipped('Current database is doesn\'t support transactions');
146
            return;
147
        }
148
149
        $page = new TestObject();
150
        $page->Title = 'Read only success';
151
        $page->write();
152
153
        DB::get_conn()->transactionStart('READ ONLY');
154
155
        try {
156
            $page = new TestObject();
157
            $page->Title = 'Read only page failed';
158
            $page->write();
159
            DB::get_conn()->transactionEnd();
160
161
        } catch (\Exception $e) {
162
            //could not write this record
163
            //We need to do a rollback or a commit otherwise we'll get error messages
164
            DB::get_conn()->transactionRollback();
165
        }
166
167
        DataObject::flush_and_destroy_cache();
168
169
        $success = DataObject::get_one(TestObject::class, "\"Title\"='Read only success'");
170
        $fail = DataObject::get_one(TestObject::class, "\"Title\"='Read only page failed'");
171
172
        //This page should be in the system
173
        $this->assertInternalType('object', $success);
174
        $this->assertTrue($success->exists());
175
176
        //This page should NOT exist, we had 'read only' permissions
177
        $this->assertNull($fail);
178
    }
179
}
180