some reorganization and cleanup of pg-specific tests
[dbsrgits/DBIx-Class-Historic.git] / t / 81transactions.t
1 use strict;
2 use warnings;  
3
4 use Test::More;
5 use Test::Exception;
6 use lib qw(t/lib);
7 use DBICTest;
8
9 my $schema = DBICTest->init_schema();
10
11 plan tests => 64;
12
13 my $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
24 # Test checking of parameters
25 {
26   eval {
27     (ref $schema)->txn_do(sub{});
28   };
29   like($@, qr/storage/, "can't call txn_do without storage");
30   eval {
31     $schema->txn_do('');
32   };
33   like($@, qr/must be a CODE reference/, '$coderef parameter check ok');
34 }
35
36 # Test successful txn_do() - scalar context
37 {
38   is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
39
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);
48
49   is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
50 }
51
52 # Test successful txn_do() - list context
53 {
54   is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
55
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);
64
65   is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
66 }
67
68 # Test nested successful txn_do()
69 {
70   is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
71
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');
96
97   is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
98 }
99
100 my $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 {
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
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});
150
151   is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset');
152 }
153
154 # Test failed txn_do() with failed rollback
155 {
156   is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
157
158   my $artist = $schema->resultset('Artist')->find(3);
159
160   # Force txn_rollback() to throw an exception
161   no warnings 'redefine';
162   no strict 'refs';
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   };
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});
194   $schema->storage->_dbh->rollback;
195 }
196
197 # Test nested failed txn_do()
198 {
199   is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
200
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 }
229
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, 'Pre-connection nested transactions.');
239
240     # although not connected DBI would still warn about rolling back at disconnect
241     $schema2->txn_rollback;
242     $schema2->txn_rollback;
243     $schema2->storage->disconnect;
244 }
245 $schema->storage->disconnect;
246
247 # Test txn_scope_guard
248 {
249   my $schema = DBICTest->init_schema();
250
251   is($schema->storage->transaction_depth, 0, "Correct transaction depth");
252   my $artist_rs = $schema->resultset('Artist');
253   throws_ok {
254    my $guard = $schema->txn_scope_guard;
255
256
257     $artist_rs->create({
258       name => 'Death Cab for Cutie',
259       made_up_column => 1,
260     });
261     
262    $guard->commit;
263   } qr/No such column made_up_column .*? at .*?81transactions.t line \d+/s, "Error propogated okay";
264
265   ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created");
266
267   my $inner_exception;
268   eval {
269     outer($schema, 1);
270   };
271   is($@, $inner_exception, "Nested exceptions propogated");
272
273   ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created");
274
275   lives_ok (sub {
276     my $w;
277     local $SIG{__WARN__} = sub { $w = shift };
278
279     # The 0 arg says don't die, just let the scope guard go out of scope 
280     # forcing a txn_rollback to happen
281     outer($schema, 0);
282
283     like ($w, qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or an error/, 'Out of scope warning detected');
284     ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created");
285   }, 'rollback successful withot exception');
286
287   sub outer {
288     my ($schema) = @_;
289
290     my $guard = $schema->txn_scope_guard;
291     $schema->resultset('Artist')->create({
292       name => 'Death Cab for Cutie',
293     });
294     inner(@_);
295   }
296
297   sub inner {
298     my ($schema, $fatal) = @_;
299
300     my $inner_guard = $schema->txn_scope_guard;
301     is($schema->storage->transaction_depth, 2, "Correct transaction depth");
302
303     my $artist = $artist_rs->find({ name => 'Death Cab for Cutie' });
304
305     eval {
306       $artist->cds->create({ 
307         title => 'Plans',
308         year => 2005, 
309         $fatal ? ( foo => 'bar' ) : ()
310       });
311     };
312     if ($@) {
313       # Record what got thrown so we can test it propgates out properly.
314       $inner_exception = $@;
315       die $@;
316     }
317
318     # inner guard should commit without consequences
319     $inner_guard->commit;
320   }
321 }