DBIx-QuickORM
view release on metacpan or search on metacpan
worktrees/audit-fixes-master/t/AI/transaction_audit.t view on Meta::CPAN
sub done { $_[0]->{done} }
sub set_done { $_[0]->{done} = $_[1] }
}
subtest failed_commit_is_recoverable => sub {
my $con = connect_orm();
my $txn = $con->txn();
$con->handle('things')->insert({name => 'wedge'});
my $fake = My::Test::FakeAsync->new(done => 0);
$con->{in_async} = $fake;
my $err = dies { $txn->commit };
like($err, qr/Cannot stop a transaction while there is an active async query/, "commit during an active async query throws");
ok(!$txn->complete, "transaction is still open after the failed commit");
is(scalar(@{$con->transactions}), 1, "transaction is still on the stack");
$fake->set_done(1);
ok(lives { $txn->rollback }, "rollback still works after the async query completes") or diag $@;
ok($txn->complete, "transaction is complete after rollback");
ok($txn->rolled_back, "transaction rolled back");
is(scalar(@{$con->transactions}), 0, "transaction stack is clean");
ok(!$con->dialect->in_txn, "no transaction left open on the database");
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
my ($count) = $dbh->selectrow_array("SELECT COUNT(*) FROM things WHERE name = 'wedge'");
$dbh->disconnect;
is($count, 0, "the insert really was rolled back");
};
subtest failed_rollback_does_not_leak_a_silent_commit => sub {
my $con = connect_orm();
my $txn = $con->txn();
$con->handle('things')->insert({name => 'sticky'});
my $fake = My::Test::FakeAsync->new(done => 0);
$con->{in_async} = $fake;
my $err = dies { $txn->rollback };
like($err, qr/Cannot stop a transaction while there is an active async query/, "rollback during an active async query throws");
ok(!$txn->complete, "transaction is still open after the failed rollback");
$fake->set_done(1);
# The failed rollback left the transaction aborted-but-recoverable. A commit
# must not silently issue a ROLLBACK and report success; it croaks so the
# caller is not misled into thinking the data committed.
my $cerr = dies { $txn->commit };
like($cerr, qr/already been rolled back/, "commit after a failed rollback croaks instead of silently rolling back");
ok(!$txn->complete, "transaction is still recoverable after the refused commit");
ok(lives { $txn->rollback }, "an explicit rollback still resolves the transaction") or diag $@;
ok($txn->rolled_back, "transaction rolled back");
is(scalar(@{$con->transactions}), 0, "transaction stack is clean");
};
subtest on_parent_callbacks => sub {
my $con = connect_orm();
my %fired;
my $ok = eval {
$con->txn(
on_parent_fail => sub { $fired{parent_fail}++ },
on_parent_completion => sub { $fired{parent_completion}++ },
on_root_fail => sub { $fired{root_fail}++ },
on_root_completion => sub { $fired{root_completion}++ },
action => sub { die "boom\n" },
);
1;
};
ok(!$ok, "root transaction failed");
is(\%fired, {root_fail => 1, root_completion => 1}, "on_parent_* are no-ops without a parent, on_root_* fire on self");
%fired = ();
$con->txn(sub {
$con->txn(
on_parent_success => sub { $fired{parent_success}++ },
on_parent_completion => sub { $fired{parent_completion}++ },
action => sub { 1 },
);
is(\%fired, {}, "parent callbacks have not fired before the parent completes");
});
is(\%fired, {parent_success => 1, parent_completion => 1}, "on_parent_* attach to the real parent when nested");
};
subtest already_complete_croaks => sub {
my $con = connect_orm();
my $txn = $con->txn();
$txn->commit;
ok($txn->complete, "transaction completed");
my $err = dies { $txn->commit };
like($err, qr/Transaction is already complete/, "second commit croaks instead of 'Label not found'");
$err = dies { $txn->rollback };
like($err, qr/Transaction is already complete/, "rollback after completion croaks too");
};
subtest savepoint_metadata_survives_completion => sub {
my $con = connect_orm();
my $saw;
$con->txn(sub {
$con->txn(
on_completion => sub { my $t = shift; $saw = $t->is_savepoint },
action => sub { 1 },
);
});
is($saw, 1, "post-completion callbacks still see is_savepoint true for a savepoint txn");
};
subtest cannot_resolve_outer_txn_from_nested_action => sub {
my $con = connect_orm();
# Committing/rolling back an OUTER transaction object from inside a nested
# action used to unwind (via last QORM_TRANSACTION) to the innermost label
# and resolve the wrong (inner) transaction. It now croaks, and the whole
# structure rolls back cleanly.
my $err = dies {
$con->txn(sub {
my $outer = shift;
$con->txn(sub { $outer->commit });
$con->handle('things')->insert({name => 'should_not_persist'});
});
};
like($err, qr/outer transaction from within a nested transaction/, "committing an outer txn from a nested action croaks");
my $err2 = dies {
$con->txn(sub {
my $outer = shift;
$con->txn(sub { $outer->rollback });
});
};
like($err2, qr/outer transaction from within a nested transaction/, "rolling back an outer txn from a nested action croaks");
ok(!$con->in_txn, "no lingering transaction after the refusals");
ok(lives {
$con->txn(sub {
$con->txn(sub { $con->handle('things')->insert({name => 'nested_ok'}) });
});
}, "ordinary nested transactions still work");
my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1, PrintError => 0});
my ($bad) = $dbh->selectrow_array("SELECT COUNT(*) FROM things WHERE name = 'should_not_persist'");
my ($good) = $dbh->selectrow_array("SELECT COUNT(*) FROM things WHERE name = 'nested_ok'");
$dbh->disconnect;
is($bad, 0, "the refused transaction did not persist anything");
is($good, 1, "the ordinary nested transaction persisted");
};
done_testing;
( run in 1.073 second using v1.01-cache-2.11-cpan-85f18b9d64f )