summaryrefslogtreecommitdiff
path: root/t/36_hooks.t
diff options
context:
space:
mode:
Diffstat (limited to 't/36_hooks.t')
-rw-r--r--t/36_hooks.t153
1 files changed, 153 insertions, 0 deletions
diff --git a/t/36_hooks.t b/t/36_hooks.t
new file mode 100644
index 0000000..c97a6c6
--- /dev/null
+++ b/t/36_hooks.t
@@ -0,0 +1,153 @@
+#!/usr/bin/perl
+
+use strict;
+BEGIN {
+ $| = 1;
+ $^W = 1;
+}
+
+use t::lib::Test qw/connect_ok @CALL_FUNCS/;
+use Test::More;
+use Test::NoWarnings qw/had_no_warnings clear_warnings/;
+
+use DBD::SQLite;
+
+plan tests => 24 * @CALL_FUNCS + 1;
+
+# hooks : just count the commits / rollbacks / updates
+my ($n_commits, $n_rollbacks, $n_updates, @update_args);
+sub commit_hook { $n_commits += 1; return 0; }
+sub rollback_hook { $n_rollbacks += 1; return 0; }
+sub update_hook { $n_updates += 1;
+ @update_args = @_; }
+
+my $sql_count_rows = "SELECT COUNT(foo) FROM hook_test";
+
+foreach my $call_func (@CALL_FUNCS) {
+
+ # connect
+ my $dbh = connect_ok( RaiseError => 1 );
+ $dbh->do( 'CREATE TEMP TABLE hook_test ( foo )' );
+
+ # register the hooks
+ my $previous_commit_hook = $dbh->$call_func(\&commit_hook,
+ "commit_hook");
+ my $previous_rollback_hook = $dbh->$call_func(\&rollback_hook,
+ "rollback_hook");
+ my $previous_update_hook = $dbh->$call_func(\&update_hook,
+ "update_hook");
+ ok(!$previous_commit_hook, "initial commit hook was undef");
+ ok(!$previous_rollback_hook, "initial rollback hook was undef");
+ ok(!$previous_update_hook, "initial update hook was undef");
+
+ # a couple of transactions
+ do_transaction($dbh) for 1..3;
+
+ # commit hook should have been called three times
+ is($n_commits, 3, "3 commits");
+
+ # update hook should have been called 30 times
+ is($n_updates, 30, "30 updates");
+
+ # check args transmitted to update hook;
+ is($update_args[0], DBD::SQLite::INSERT, 'update hook arg 0: INSERT');
+ is($update_args[1], 'temp', 'update hook arg 1: database');
+ is($update_args[2], 'hook_test', 'update hook arg 2: table');
+ ok($update_args[3], 'update hook arg 3: rowid');
+
+ # unregister the commit and update hooks, check if previous hooks are returned
+ $previous_commit_hook = $dbh->$call_func(undef, "commit_hook");
+ ok($previous_commit_hook eq \&commit_hook,
+ "previous commit hook correctly returned");
+ $previous_update_hook = $dbh->$call_func(undef, "update_hook");
+ ok($previous_update_hook eq \&update_hook,
+ "previous update hook correctly returned");
+
+ # some more transactions .. commit and update hook should not be called
+ $n_commits = 0;
+ $n_updates = 0;
+ do_transaction($dbh) for 1..3;
+ is($n_commits, 0, "commit hook unregistered");
+ is($n_updates, 0, "update hook unregistered");
+
+ # check here explicitly for warnings, before we clear them
+ had_no_warnings();
+
+ # remember how many rows we had so far
+ my ($n_rows) = $dbh->selectrow_array($sql_count_rows);
+
+ # a commit hook that rejects the transaction
+ $dbh->$call_func(sub {return 1}, "commit_hook");
+ eval {do_transaction($dbh)}; # in eval() because of RaiseError
+ ok ($@, "transaction was rejected: $@" );
+
+ # no explicit rollback, because SQLite already did it
+ # eval {$dbh->rollback;};
+ # ok (!$@, "rollback OK $@");
+
+ # rollback hook should have been called
+ is($n_rollbacks, 1, "1 rollback");
+
+ # unregister the rollback hook, check if previous hook is returned
+ $previous_rollback_hook = $dbh->$call_func(undef, "rollback_hook");
+ ok($previous_rollback_hook eq \&rollback_hook,
+ "previous hook correctly returned");
+
+ # try transaction again .. rollback hook should not be called
+ $n_rollbacks = 0;
+ eval {do_transaction($dbh)};
+ is($n_rollbacks, 0, "rollback hook unregistered");
+
+ # check that the rollbacks did really occur
+ my ($n_rows_after) = $dbh->selectrow_array($sql_count_rows);
+ is($n_rows, $n_rows_after, "no rows added" );
+
+ # unregister commit hook, register an authorizer that forbids delete ops
+ $dbh->$call_func(undef, "commit_hook");
+ my @authorizer_args;
+ my $authorizer = sub {
+ @authorizer_args = @_;
+ my $action_code = shift;
+ my $retval = $action_code == DBD::SQLite::DELETE ? DBD::SQLite::DENY
+ : DBD::SQLite::OK;
+ return $retval;
+ };
+ $dbh->$call_func($authorizer, "set_authorizer");
+
+ # try an insert (should be authorized) and check authorizer args
+ $dbh->do("INSERT INTO hook_test VALUES ('auth_test')");
+ is_deeply(\@authorizer_args,
+ [DBD::SQLite::INSERT, 'hook_test', undef, 'temp', undef],
+ "args to authorizer (INSERT)");
+
+ # try a delete (should be unauthorized)
+ eval {$dbh->do("DELETE FROM hook_test WHERE foo = 'auth_test'")};
+ ok($@, "delete was rejected with message $@");
+ is_deeply(\@authorizer_args,
+ [DBD::SQLite::DELETE, 'hook_test', undef, 'temp', undef],
+ "args to authorizer (DELETE)");
+
+
+ # unregister the authorizer ... now DELETE should be authorized
+ $dbh->$call_func(undef, "set_authorizer");
+ eval {$dbh->do("DELETE FROM hook_test WHERE foo = 'auth_test'")};
+ ok(!$@, "delete was accepted");
+
+
+ # sqlite3 did warn in tests above, so avoid complains from Test::Warnings
+ # (would be better to turn off warnings from sqlite3, but I didn't find
+ # any way to do that)
+ clear_warnings();
+}
+
+
+sub do_transaction {
+ my $dbh = shift;
+
+ $dbh->begin_work;
+ for my $count (1 .. 10) {
+ my $rand = rand;
+ $dbh->do( "INSERT INTO hook_test(foo) VALUES ( $rand )" );
+ }
+ $dbh->commit;
+}