new config option to DBICTest to let you set an alternative storage type, start on...
[dbsrgits/DBIx-Class.git] / t / 81transactions.t
CommitLineData
70350518 1use strict;
2use warnings;
3
4use Test::More;
3b7f3eac 5use Test::Exception;
70350518 6use lib qw(t/lib);
7use DBICTest;
8
a47e1233 9my $schema = DBICTest->init_schema();
70350518 10
3b7f3eac 11plan tests => 67;
a62cf8d4 12
13my $code = sub {
14 my ($artist, @cd_titles) = @_;
15
16 $artist->create_related('cds', {
17 title => $_,
18 year => 2006,
19 }) foreach (@cd_titles);
20
21 return $artist->cds->all;
22};
23
171dadd7 24# Test checking of parameters
25{
171dadd7 26 eval {
27 (ref $schema)->txn_do(sub{});
28 };
f32eb113 29 like($@, qr/storage/, "can't call txn_do without storage");
171dadd7 30 eval {
31 $schema->txn_do('');
32 };
33 like($@, qr/must be a CODE reference/, '$coderef parameter check ok');
34}
35
a62cf8d4 36# Test successful txn_do() - scalar context
37{
57c18b65 38 is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
39
a62cf8d4 40 my @titles = map {'txn_do test CD ' . $_} (1..5);
41 my $artist = $schema->resultset('Artist')->find(1);
42 my $count_before = $artist->cds->count;
43 my $count_after = $schema->txn_do($code, $artist, @titles);
44 is($count_after, $count_before+5, 'successful txn added 5 cds');
45 is($artist->cds({
46 title => "txn_do test CD $_",
47 })->first->year, 2006, "new CD $_ year correct") for (1..5);
57c18b65 48
49 is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
a62cf8d4 50}
51
52# Test successful txn_do() - list context
53{
57c18b65 54 is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
55
a62cf8d4 56 my @titles = map {'txn_do test CD ' . $_} (6..10);
57 my $artist = $schema->resultset('Artist')->find(1);
58 my $count_before = $artist->cds->count;
59 my @cds = $schema->txn_do($code, $artist, @titles);
60 is(scalar @cds, $count_before+5, 'added 5 CDs and returned in list context');
61 is($artist->cds({
62 title => "txn_do test CD $_",
63 })->first->year, 2006, "new CD $_ year correct") for (6..10);
57c18b65 64
65 is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
a62cf8d4 66}
67
68# Test nested successful txn_do()
69{
57c18b65 70 is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
71
a62cf8d4 72 my $nested_code = sub {
73 my ($schema, $artist, $code) = @_;
74
75 my @titles1 = map {'nested txn_do test CD ' . $_} (1..5);
76 my @titles2 = map {'nested txn_do test CD ' . $_} (6..10);
77
78 $schema->txn_do($code, $artist, @titles1);
79 $schema->txn_do($code, $artist, @titles2);
80 };
81
82 my $artist = $schema->resultset('Artist')->find(2);
83 my $count_before = $artist->cds->count;
84
85 eval {
86 $schema->txn_do($nested_code, $schema, $artist, $code);
87 };
88
89 my $error = $@;
90
91 ok(!$error, 'nested txn_do succeeded');
92 is($artist->cds({
93 title => 'nested txn_do test CD '.$_,
94 })->first->year, 2006, qq{nested txn_do CD$_ year ok}) for (1..10);
95 is($artist->cds->count, $count_before+10, 'nested txn_do added all CDs');
57c18b65 96
97 is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
a62cf8d4 98}
99
100my $fail_code = sub {
101 my ($artist) = @_;
102 $artist->create_related('cds', {
103 title => 'this should not exist',
104 year => 2005,
105 });
106 die "the sky is falling";
107};
108
109# Test failed txn_do()
110{
57c18b65 111
112 is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
113
114 my $artist = $schema->resultset('Artist')->find(3);
115
116 eval {
117 $schema->txn_do($fail_code, $artist);
118 };
119
120 my $error = $@;
121
122 like($error, qr/the sky is falling/, 'failed txn_do threw an exception');
123 my $cd = $artist->cds({
124 title => 'this should not exist',
125 year => 2005,
126 })->first;
127 ok(!defined($cd), q{failed txn_do didn't change the cds table});
128
129 is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
130}
131
132# do the same transaction again
133{
134 is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
135
a62cf8d4 136 my $artist = $schema->resultset('Artist')->find(3);
137
138 eval {
139 $schema->txn_do($fail_code, $artist);
140 };
141
142 my $error = $@;
143
144 like($error, qr/the sky is falling/, 'failed txn_do threw an exception');
145 my $cd = $artist->cds({
146 title => 'this should not exist',
147 year => 2005,
148 })->first;
149 ok(!defined($cd), q{failed txn_do didn't change the cds table});
57c18b65 150
151 is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
a62cf8d4 152}
153
154# Test failed txn_do() with failed rollback
155{
57c18b65 156 is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
157
a62cf8d4 158 my $artist = $schema->resultset('Artist')->find(3);
159
160 # Force txn_rollback() to throw an exception
161 no warnings 'redefine';
58d387fe 162 no strict 'refs';
57c18b65 163
164 # die in rollback, but maintain sanity for further tests ...
165 local *{"DBIx::Class::Storage::DBI::SQLite::txn_rollback"} = sub{
166 my $storage = shift;
167 $storage->{transaction_depth}--;
168 die 'FAILED';
169 };
a62cf8d4 170
171 eval {
172 $schema->txn_do($fail_code, $artist);
173 };
174
175 my $error = $@;
176
177 like($error, qr/Rollback failed/, 'failed txn_do with a failed '.
178 'txn_rollback threw a rollback exception');
179 like($error, qr/the sky is falling/, 'failed txn_do with a failed '.
180 'txn_rollback included the original exception');
181
182 my $cd = $artist->cds({
183 title => 'this should not exist',
184 year => 2005,
185 })->first;
186 isa_ok($cd, 'DBICTest::CD', q{failed txn_do with a failed txn_rollback }.
187 q{changed the cds table});
188 $cd->delete; # Rollback failed
189 $cd = $artist->cds({
190 title => 'this should not exist',
191 year => 2005,
192 })->first;
193 ok(!defined($cd), q{deleted the failed txn's cd});
57c18b65 194 $schema->storage->_dbh->rollback;
a62cf8d4 195}
196
197# Test nested failed txn_do()
198{
57c18b65 199 is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
200
a62cf8d4 201 my $nested_fail_code = sub {
202 my ($schema, $artist, $code1, $code2) = @_;
203
204 my @titles = map {'nested txn_do test CD ' . $_} (1..5);
205
206 $schema->txn_do($code1, $artist, @titles); # successful txn
207 $schema->txn_do($code2, $artist); # failed txn
208 };
209
210 my $artist = $schema->resultset('Artist')->find(3);
211
212 eval {
213 $schema->txn_do($nested_fail_code, $schema, $artist, $code, $fail_code);
214 };
215
216 my $error = $@;
217
218 like($error, qr/the sky is falling/, 'nested failed txn_do threw exception');
219 ok(!defined($artist->cds({
220 title => 'nested txn_do test CD '.$_,
221 year => 2006,
222 })->first), qq{failed txn_do didn't add first txn's cd $_}) for (1..5);
223 my $cd = $artist->cds({
224 title => 'this should not exist',
225 year => 2005,
226 })->first;
227 ok(!defined($cd), q{failed txn_do didn't add failed txn's cd});
228}
a62cf8d4 229
291bf95f 230# Grab a new schema to test txn before connect
231{
232 my $schema2 = DBICTest->init_schema(no_deploy => 1);
233 eval {
234 $schema2->txn_begin();
235 $schema2->txn_begin();
236 };
237 my $err = $@;
238 ok(($err eq ''), 'Pre-connection nested transactions.');
239}
3b7f3eac 240
241# Test txn_rollback with nested
242{
243 local $TODO = "Work out how this should work";
244 my $local_schema = DBICTest->init_schema();
245
246 my $artist_rs = $local_schema->resultset('Artist');
247 throws_ok {
248
249 $local_schema->txn_begin;
250 $artist_rs->create({ name => 'Test artist rollback 1'});
251 $local_schema->txn_begin;
252 is($local_schema->storage->transaction_depth, 2, "Correct transaction depth");
253 $artist_rs->create({ name => 'Test artist rollback 2'});
254 $local_schema->txn_rollback;
255 } qr/Not sure what this should be.... something tho/, "Rolled back okay";
256 is($local_schema->storage->transaction_depth, 0, "Correct transaction depth");
257
258 ok(!$artist_rs->find({ name => 'Test artist rollback 1'}), "Test Artist not created")
259 || $artist_rs->find({ name => 'Test artist rollback 1'})->delete;
260}
261
262# Test txn_scope_guard
263{
264 local $TODO = "Work out how this should work";
265 my $schema = DBICTest->init_schema();
266
267 is($schema->storage->transaction_depth, 0, "Correct transaction depth");
268 my $artist_rs = $schema->resultset('Artist');
269 throws_ok {
270 my $guard = $schema->txn_scope_guard;
271
272
273 $artist_rs->create({
274 name => 'Death Cab for Cutie',
275 made_up_column => 1,
276 });
277
278 $guard->commit;
279 } qr/No such column made_up_column.*?line 16/, "Error propogated okay";
280
281 ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created");
282
283 my $inner_exception;
284 eval {
285 outer($schema, 1);
286 };
287 is($@, $inner_exception, "Nested exceptions propogated");
288
289 ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created");
290
291
292 eval {
293 # The 0 arg says done die, just let the scope guard go out of scope
294 # forcing a txn_rollback to happen
295 outer($schema, 0);
296 };
297 is($@, "Not sure what we want here, but something", "Rollback okay");
298
299 ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created");
300
301 sub outer {
302 my ($schema) = @_;
303
304 my $guard = $schema->txn_scope_guard;
305 $schema->resultset('Artist')->create({
306 name => 'Death Cab for Cutie',
307 });
308 inner(@_);
309 $guard->commit;
310 }
311
312 sub inner {
313 my ($schema, $fatal) = @_;
314 my $guard = $schema->txn_scope_guard;
315
316 my $artist = $artist_rs->find({ name => 'Death Cab for Cutie' });
317
318 is($schema->storage->transaction_depth, 2, "Correct transaction depth");
319 undef $@;
320 eval {
321 $artist->cds->create({
322 title => 'Plans',
323 year => 2005,
324 $fatal ? ( foo => 'bar' ) : ()
325 });
326 };
327 if ($@) {
328 # Record what got thrown so we can test it propgates out properly.
329 $inner_exception = $@;
330 die $@;
331 }
332
333 # See what happens if we dont $guard->commit;
334 }
335}