summaryrefslogtreecommitdiff
path: root/t/36_hooks.t
blob: c97a6c61f94f0ecdf726ffc41b76d53d7dfa7e4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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;
}