]> git.decadent.org.uk Git - maypole.git/blob - t/maypole.t
removed CGI::Untaint::Maypole, 29 tests now passing
[maypole.git] / t / maypole.t
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use Test::More tests => 84;
5 use Test::MockModule;
6
7 # module compilation
8 # Test 1
9 require_ok('Maypole');
10
11 # loaded modules 
12 # Tests 2 - 8
13 {
14     ok($Maypole::VERSION, 'defines $VERSION');
15     ok($INC{'Maypole/Config.pm'}, 'loads Maypole::Config');
16     ok($INC{'UNIVERSAL/require.pm'}, 'loads UNIVERSAL::require');
17     ok($INC{'Maypole/Constants.pm'}, 'loads Maypole::Constants');
18     ok($INC{'Maypole/Headers.pm'}, 'loads Maypole::Headers');
19     ok($INC{'Class/Accessor/Fast.pm'}, 'loads Class::Accessor::Fast');
20     ok($INC{'Class/Data/Inheritable.pm'}, 'loads Class::Data::Inheritable');
21 }
22
23 my $OK       = Maypole::Constants::OK();
24 my $DECLINED = Maypole::Constants::DECLINED();
25 my $ERROR    = Maypole::Constants::ERROR();
26
27 # Maypole API
28 my @API = qw/ config init_done view_object params query param objects model_class
29               template_args output path args action template error document_encoding
30               content_type table headers_in headers_out 
31               is_model_applicable setup setup_model init handler handler_guts
32               call_authenticate call_exception additional_data
33               authenticate exception parse_path make_path
34               make_uri get_template_root get_request
35               parse_location send_output
36               start_request_hook
37               get_session
38           get_user
39               /;
40
41 # Tests 9 to 13                
42 can_ok(Maypole => @API);
43 ok( UNIVERSAL::can(Maypole => 'is_applicable'), 'is_applicable() method' ); # added is_applicable back in
44 ok(Maypole->config->isa('Maypole::Config'), 'config is a Maypole::Config object');
45 ok(! Maypole->init_done, '... which is false by default');
46 is(Maypole->view_object, undef, '... which is undefined');
47
48 # simple test class that inherits from Maypole
49 {
50     package MyDriver;
51     @MyDriver::ISA = 'Maypole';
52     @MyDriver::VERSION = 1;
53 }
54
55 # back to package main;
56 my $driver_class = 'MyDriver';
57
58 # Test 14
59 # subclass inherits API
60 can_ok($driver_class => @API);
61
62 # Mock the model class
63 my (%required, @db_args, @adopted);
64 my $model_class = 'Maypole::Model::CDBI';
65 my $table_class = $driver_class . '::One';
66
67 my $mock_model = Test::MockModule->new($model_class);
68 $mock_model->mock(
69     require        => sub {$required{+shift} = 1},
70     setup_database => sub {
71         push @db_args, \@_;
72         $_[1]->{classes} = ["$model_class\::One", "$model_class\::Two"];
73         $_[1]->{tables}  = [qw(one two)];
74     },
75     adopt          => sub {push @adopted, \@_},
76 );
77
78
79 # Tests 15 - 21
80 warn "Tests 15 to 21\n\n";
81 # setup
82 {
83     # 2.11 - removed tests to check the installed handler was a different ref after setup().
84     # The handler tests were testing Maypole's old (pre 2.11) method of importing handler() 
85     # into the subclass - it works via standard inheritance now, by setting the 'method' 
86     # attribute on Maypole::handler(). The reason the handlers were different 
87     # was because setup() would create a new anonymous ref to Maypole::handler(), and install 
88     # that - i.e. it installed the same code, but in a different ref, so they tested unequal
89     # although they referred to the same code
90
91     $driver_class->setup('dbi:foo'); 
92     
93     ok($required{$model_class}, '... requires model class');
94     is($driver_class->config->model(),
95         'Maypole::Model::CDBI', '... default model is CDBI');
96     is(@db_args, 1, '... calls model->setup_database');
97     like(join (' ', @{$db_args[0]}),
98         qr/$model_class Maypole::Config=\S* $driver_class dbi:foo/,
99         '... setup_database passed setup() args');
100     is(@adopted, 2, '... calls model->adopt foreach class in the model');
101     ok($adopted[0][0]->isa($model_class),
102     '... sets up model subclasses to inherit from model');
103     $driver_class->config->model('NonExistant::Model');
104     eval {$driver_class->setup};
105     like($@, qr/Couldn't load the model class/,
106         '... dies if unable to load model class');
107     
108     # cleanup
109     $@ = undef; 
110     $driver_class->config->model($model_class);
111 }
112
113
114 # Tests 22 - 27
115 warn "Tests 22 to 27\n\n";
116 # Mock the view class
117 my $view_class = 'Maypole::View::TT';
118 my $mock_view = Test::MockModule->new($view_class);
119 $mock_view->mock(
120     new     => sub {bless{}, shift},
121     require => sub {$required{+shift} = 1},
122 );
123
124 # init()
125 {
126     $driver_class->init();
127     ok($required{$view_class}, '... requires the view class');
128     is($driver_class->config->view, $view_class, '... the default view class is TT');
129     is(join(' ', @{$driver_class->config->display_tables}), 'one two',
130         '... config->display_tables defaults to all tables');
131     ok($driver_class->view_object->isa($view_class),
132         '... creates an instance of the view object');
133     ok($driver_class->init_done, '... sets init_done');
134     $driver_class->config->view('NonExistant::View');
135     eval {$driver_class->init};
136     like($@, qr/Couldn't load the view class/,
137         '... dies if unable to load view class');
138         
139     # cleanup
140     $@ = undef; 
141     $driver_class->config->view($view_class);
142 }
143
144 my ($r, $req); # request objects
145
146 # Tests 28 - 38
147 warn "tests 28 to 38\n\n";
148 # handler()
149 {
150     my $init = 0;
151     my $status = 0;
152     my %called;
153     
154     my $mock_driver = Test::MockModule->new($driver_class, no_auto => 1);
155     $mock_driver->mock(
156         init           => sub {$init++; shift->init_done(1)},
157         get_request    => sub {($r, $req) = @_; $called{get_request}++},
158         parse_location => sub {$called{parse_location}++},
159         handler_guts   => sub {$called{handler_guts}++; $status},
160         send_output    => sub {$called{send_output}++},
161     );
162
163     my $rv = $driver_class->handler();
164     
165     ok($r && $r->isa($driver_class), '... created $r');
166     ok($called{get_request}, '... calls get_request()');
167     ok($called{parse_location}, '... calls parse_location');
168     ok($called{handler_guts}, '... calls handler_guts()');
169     ok($called{send_output}, '... call send_output');
170     is($rv, 0, '... return status (should be ok?)');
171     ok(!$init, "... doesn't call init() if init_done()");
172     
173     ok($r->headers_out && $r->headers_out->isa('Maypole::Headers'),
174        '... populates headers_out() with a Maypole::Headers object');
175        
176     # call again, testing other branches
177     $driver_class->init_done(0);
178     $status = -1;
179     $rv = $driver_class->handler();
180     ok($called{handler_guts} == 2 && $called{send_output} == 1,
181        '... returns early if handler_guts failed');
182     is($rv, -1, '... returning the error code from handler_guts');
183     
184     $driver_class->handler();
185     ok($init && $driver_class->init_done, "... init() called if !init_done()");
186 }
187
188
189 # Tests 39 - 48
190 warn "Tests 39 - 48\n\n";
191 # Testing handler_guts
192 {
193     # handler_guts()
194     {
195         no strict 'refs';
196         @{$table_class . "::ISA"} = $model_class;
197     }
198
199     my ($applicable, %called);
200     
201     my $mock_driver = new Test::MockModule($driver_class, no_auto => 1);
202     my $mock_table  = new Test::MockModule($table_class, no_auto => 1);
203     
204     $mock_driver->mock(
205         is_applicable   => sub {push @{$called{applicable}},\@_; $applicable},
206         is_model_applicable   => 
207             sub {push @{$called{applicable}},\@_; $applicable},
208         get_request     => sub {($r, $req) = @_},
209         additional_data => sub {$called{additional_data}++},
210     );
211     
212     $mock_table->mock(
213         table_process   => sub {push @{$called{process}},\@_},
214     );
215     
216     $mock_model->mock(
217         class_of        => sub {push @{$called{class_of}},\@_; $table_class},
218         process         => sub {push @{$called{model_process}}, \@_},
219     );
220     
221     $mock_view->mock(
222         process         => sub {push @{$called{view_process}}, \@_; $OK}
223     );
224     
225     # allow request
226     $applicable = 1;
227     
228     $r->{path} = '/table/action';
229     $r->parse_path;
230     
231     my $status = $r->handler_guts();
232
233     warn "model class ", $r->model_class, "table class : $table_class\n";
234
235     is($r->model_class, $table_class, '... sets model_class from table()');
236     ok($called{additional_data}, '... call additional_data()');
237     is($status, $OK, '... return status = OK');
238     ok($called{model_process},
239        '... if_applicable, call model_class->process');
240
241     # decline request
242     %called = ();
243     
244     $applicable = 0;
245     
246     $r->{path} = '/table/action';
247     $r->parse_path;
248     
249     $status = $r->handler_guts();
250     
251     is($r->template, $r->path,
252        '... if ! is_applicable set template() to path()');
253     ok(!$called{model_process},
254        '... !if_applicable, call model_class->process');
255     is_deeply($called{view_process}[0][1], $r,
256               ' ... view_object->process called');
257     is($status, $OK, '... return status = OK');
258
259     # pre-load some output
260     %called = ();
261     
262     $r->parse_path;
263     $r->{output} = 'test';
264     
265     $status = $r->handler_guts();
266     
267     ok(!$called{view_process},
268        '... unless output, call view_object->process to get output');
269
270     # fail authentication
271     $mock_driver->mock(call_authenticate => sub {$DECLINED});
272     $status = $r->handler_guts();
273     is($status, $DECLINED,
274        '... return DECLINED unless call_authenticate == OK');
275
276     # ... TODO authentication error handling
277     # ... TODO model error handling
278     # ... TODO view processing error handling
279 }
280
281 # Tests 49 - 53
282 warn "Tests 49 to 53\n\n";
283 # is_model_applicable()
284 {
285     $r->config->display_tables([qw(one two)]);
286     $r->config->ok_tables(undef);
287     $r->model_class($table_class);
288     $r->table('one');
289     $r->action('unittest');
290     my $is_public;
291     $mock_model->mock('is_public', sub {0});
292     my $true_false = $r->is_model_applicable;
293     is($true_false, 0,
294     '... returns 0 unless model_class->is_public(action)');
295     $mock_model->mock('is_public', sub {$is_public = \@_; 1});
296     $true_false = $r->is_model_applicable;
297     is($true_false, 1, '... returns 1 if table is in ok_tables');
298     is_deeply($is_public, [$r->model_class, 'unittest'],
299             '... calls model_class->is_public with request action');
300     is_deeply($r->config->ok_tables, {one => 1, two => 1},
301             '... config->ok_tables defaults to config->display_tables');
302     delete $r->config->ok_tables->{one};
303     $true_false = $r->is_model_applicable;
304     is($true_false, 0, '... returns 0 unless $r->table is in ok_tables');
305 }
306
307
308 # Tests 54 - 58
309 warn "Tests 54 to 58\n\n";
310 my $mock_driver = new Test::MockModule($driver_class, no_auto => 1);
311 my $mock_table  = new Test::MockModule($table_class, no_auto => 1);
312 # call_authenticate()
313 {
314     my %auth_calls;
315     $mock_table->mock(
316         authenticate => sub {$auth_calls{model_auth} = \@_; $OK}
317     );
318     my $status = $r->call_authenticate;
319     is_deeply($auth_calls{model_auth}, [$table_class, $r],
320             '... calls model_class->authenticate if it exists'); # 54
321     is($status, $OK, '... and returns its status (OK)'); # 55
322     $mock_table->mock(authenticate => sub {$DECLINED});
323     $status = $r->call_authenticate;
324     is($status, $DECLINED, '... or DECLINED, as appropriate'); # 56
325     
326     $mock_table->unmock('authenticate');
327     $mock_driver->mock(authenticate => sub {return $DECLINED});
328     $status = $r->call_authenticate;
329     is($status, $DECLINED, '... otherwise it calls authenticte()'); # 57
330     $mock_driver->unmock('authenticate');
331     $status = $r->call_authenticate;
332     is($status, $OK, '... the default authenticate is OK'); # 58
333 }
334
335 # Tests 59 - 63
336 warn "Tests 59 to 63\n\n";
337 # call_exception()
338 {
339     my %ex_calls;
340     $mock_table->mock(
341         exception => sub {$ex_calls{model_exception} = \@_; $OK}
342     );
343     $mock_driver->mock(
344         exception => sub {$ex_calls{driver_exception} = \@_; 'X'}
345     );
346     my $status = $r->call_exception('ERR');
347     is_deeply($ex_calls{model_exception}, [$table_class, $r, 'ERR'],
348             '... calls model_class->exception if it exists');
349     is($status, $OK, '... and returns its status (OK)');
350     $mock_table->mock(exception => sub {$DECLINED});
351     $status = $r->call_exception('ERR');
352     is_deeply($ex_calls{driver_exception}, [$r, 'ERR'],
353             '... or calls driver->exception if model returns !OK');
354     is($status, 'X', '... and returns the drivers status');
355     
356     $mock_table->unmock('exception');
357     $mock_driver->unmock('exception');
358     $status = $r->call_exception('ERR');
359     is($status, $ERROR, '... the default exception is ERROR');
360 }
361
362 # Test 64
363 # authenticate()
364 {
365     is(Maypole->authenticate(), $OK, '... returns OK');
366 }
367
368 # Test 65
369 # exception()
370 {
371     is(Maypole->exception(), $ERROR, '... returns ERROR');
372 }
373
374 # Tests 66 to 71
375 warn "Tests 66 to 71\n\n";
376 # parse_path()
377 {
378     $r->path(undef);
379     
380     $r->parse_path;
381     is($r->path, 'frontpage', '... path() defaults to "frontpage"');
382     
383     $r->path('/table');
384     $r->parse_path;
385     is($r->table, 'table', '... parses "table" from the first part of path');
386     ok(@{$r->args} == 0, '... "args" default to empty list');
387     
388     $r->path('/table/action');
389     $r->parse_path;
390     ok($r->table eq 'table' && $r->action eq 'action',
391     '... action is parsed from second part of path');
392     
393     $r->path('/table/action/arg1/arg2');
394     $r->parse_path;
395     is_deeply($r->args, [qw(arg1 arg2)],
396     '... "args" are populated from remaning components');
397     
398     # ... action defaults to index
399     $r->path('/table');
400     $r->parse_path;
401     is($r->action, 'index', '... action defaults to index');
402 }
403
404 # make_uri() and make_path() - see pathtools.t
405
406 # Test 72
407 # get_template_root()
408 {
409     is(Maypole->get_template_root(), '.', '... returns "."');
410 }
411
412 # Test 73
413 # parse_location()
414 {
415     eval {Maypole->parse_location()};
416     like($@, qr/Do not use Maypole directly/, '... croaks - must be overriden');
417 }
418
419 # Test 74
420 # send_output()
421 {
422     eval {Maypole->send_output};
423     like($@, qr/Do not use Maypole directly/, '... croaks - must be overriden');
424 }
425
426 # Tests 75 - 84
427 warn "Tests 75 to 84\n\n";
428 # param()
429 {
430         my $p = { foo => 'bar', 
431                   quux => [ qw/one two three/ ],
432                   buz => undef,
433                   num => 3,
434                   zero => 0,
435                   };
436                   
437         $r->{params} = $p;
438         
439         is_deeply( [keys %$p], [$r->param] ); # 75
440         
441         cmp_ok( $r->param('foo'), eq => 'bar' ); # 76
442         cmp_ok( $r->param('num'), '==' => 3 ); # 77
443         cmp_ok( $r->param('zero'), '==' => 0 ); # 78
444         
445         ok( ! defined $r->param('buz') ); # 79
446         
447         # scalar context returns the 1st value, not a ref
448         cmp_ok( scalar $r->param('quux'), eq => 'one' ); # 80
449         is_deeply( [$r->param('quux')], [ qw/one two three/ ] ); # 81
450         
451         $r->param(foo => 'booze');
452         cmp_ok( $r->param('foo'), 'eq', 'booze' ); # 82
453         
454         $r->param(foo => undef);
455         ok( ! defined $r->param('foo') ); # 83
456         
457         # cannot introduce new keys
458         $r->param(new => 'sox');
459         ok( ! defined $r->param('new') ); # 84
460 }
461