Passed
Push — programming-is-horrible ( 2233ca...483908 )
by Sam
08:19
created

TransactionTest::testReadOnlyTransaction()   A

Complexity

Conditions 3
Paths 5

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 21
nc 5
nop 0
dl 0
loc 38
rs 9.584
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\Dev\Deprecation;
9
use SilverStripe\ORM\Tests\TransactionTest\TestObject;
10
11
class TransactionTest extends SapphireTest
12
{
13
    protected $usesDatabase = true;
14
15
    protected $usesTransactions = false;
16
17
    protected static $extra_dataobjects = [
18
        TransactionTest\TestObject::class,
19
    ];
20
21
    private static $originalVersionInfo;
22
23
    protected function setUp()
24
    {
25
        parent::setUp();
26
        self::$originalVersionInfo = Deprecation::dump_settings();
27
    }
28
29
    protected function tearDown()
30
    {
31
        Deprecation::restore_settings(self::$originalVersionInfo);
32
        parent::tearDown();
33
    }
34
35
    public static function setUpBeforeClass()
36
    {
37
        parent::setUpBeforeClass();
38
        if (!DB::get_conn()->supportsTransactions()) {
39
            static::markTestSkipped('Current database does not support transactions');
40
        }
41
    }
42
43
    public function testTransactions()
44
    {
45
        $conn = DB::get_conn();
46
        if (!$conn->supportsTransactions()) {
47
            $this->markTestSkipped("DB Doesn't support transactions");
48
            return;
49
        }
50
51
        // Test that successful transactions are comitted
52
        $obj = new TestObject();
53
        $failed = false;
54
        $conn->withTransaction(
55
            function () use (&$obj) {
56
                $obj->Title = 'Save 1';
57
                $obj->write();
58
            },
59
            function () use (&$failed) {
60
                $failed = true;
61
            }
62
        );
63
        $this->assertEquals('Save 1', TestObject::get()->first()->Title);
64
        $this->assertFalse($failed);
65
66
        // Test failed transactions are rolled back
67
        $ex = null;
68
        $failed = false;
69
        try {
70
            $conn->withTransaction(
71
                function () use (&$obj) {
72
                    $obj->Title = 'Save 2';
73
                    $obj->write();
74
                    throw new \Exception("error");
75
                },
76
                function () use (&$failed) {
77
                    $failed = true;
78
                }
79
            );
80
        } catch (\Exception $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
81
        }
82
        $this->assertTrue($failed);
83
        $this->assertEquals('Save 1', TestObject::get()->first()->Title);
84
        $this->assertInstanceOf('Exception', $ex);
85
        $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

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