4 use Test::More tests => 73;
12 ok($Maypole::VERSION, 'defines $VERSION');
13 ok($INC{'Maypole/Config.pm'}, 'loads Maypole::Config');
14 ok($INC{'UNIVERSAL/require.pm'}, 'loads UNIVERSAL::require');
15 ok($INC{'Maypole/Constants.pm'}, 'loads Maypole::Constants');
16 ok($INC{'Maypole/Headers.pm'}, 'loads Maypole::Headers');
17 ok($INC{'Class/Accessor/Fast.pm'}, 'loads Class::Accessor::Fast');
18 ok($INC{'Class/Data/Inheritable.pm'}, 'loads Class::Data::Inheritable');
21 my $OK = Maypole::Constants::OK();
22 my $DECLINED = Maypole::Constants::DECLINED();
23 my $ERROR = Maypole::Constants::ERROR();
26 my @API = qw/ config init_done view_object params query objects model_class
27 template_args output path args action template error document_encoding
28 content_type table headers_in headers_out
29 is_applicable setup init handler handler_guts
30 call_authenticate call_exception additional_data
31 authenticate exception parse_path get_template_root get_request
32 parse_location send_output
35 can_ok(Maypole => @API);
37 ok(Maypole->config->isa('Maypole::Config'), 'config is a Maypole::Config object');
38 ok(! Maypole->init_done, '... which is false by default');
39 is(Maypole->view_object, undef, '... which is undefined');
41 # simple test class that inherits from Maypole
44 @MyDriver::ISA = 'Maypole';
45 @MyDriver::VERSION = 1;
48 # back to package main;
49 my $driver_class = 'MyDriver';
51 # subclass inherits API
52 can_ok($driver_class => @API);
54 # Mock the model class
55 my (%required, @db_args, @adopted);
56 my $model_class = 'Maypole::Model::CDBI';
57 my $table_class = $driver_class . '::One';
59 my $mock_model = Test::MockModule->new($model_class);
61 require => sub {$required{+shift} = 1},
62 setup_database => sub {
64 $_[1]->{classes} = ["$model_class\::One", "$model_class\::Two"];
65 $_[1]->{tables} = [qw(one two)];
67 adopt => sub {push @adopted, \@_},
73 # 2.11 - removed tests to check the installed handler was a different ref after setup().
74 # The handler tests were testing Maypole's old (pre 2.11) method of importing handler()
75 # into the subclass - it works via standard inheritance now, by setting the 'method'
76 # attribute on Maypole::handler(). The reason the handlers were different
77 # was because setup() would create a new anonymous ref to Maypole::handler(), and install
78 # that - i.e. it installed the same code, but in a different ref, so they tested unequal
79 # although they referred to the same code
81 $driver_class->setup('dbi:foo');
83 ok($required{$model_class}, '... requires model class');
84 is($driver_class->config->model(),
85 'Maypole::Model::CDBI', '... default model is CDBI');
86 is(@db_args, 1, '... calls model->setup_database');
87 like(join (' ', @{$db_args[0]}),
88 qr/$model_class Maypole::Config=\S* $driver_class dbi:foo/,
89 '... setup_database passed setup() args');
90 is(@adopted, 2, '... calls model->adopt foreach class in the model');
91 ok($adopted[0][0]->isa($model_class),
92 '... sets up model subclasses to inherit from model');
93 $driver_class->config->model('NonExistant::Model');
94 eval {$driver_class->setup};
95 like($@, qr/Couldn't load the model class/,
96 '... dies if unable to load model class');
100 $driver_class->config->model($model_class);
103 # Mock the view class
104 my $view_class = 'Maypole::View::TT';
105 my $mock_view = Test::MockModule->new($view_class);
107 new => sub {bless{}, shift},
108 require => sub {$required{+shift} = 1},
113 $driver_class->init();
114 ok($required{$view_class}, '... requires the view class');
115 is($driver_class->config->view, $view_class, '... the default view class is TT');
116 is(join(' ', @{$driver_class->config->display_tables}), 'one two',
117 '... config->display_tables defaults to all tables');
118 ok($driver_class->view_object->isa($view_class),
119 '... creates an instance of the view object');
120 ok($driver_class->init_done, '... sets init_done');
121 $driver_class->config->view('NonExistant::View');
122 eval {$driver_class->init};
123 like($@, qr/Couldn't load the view class/,
124 '... dies if unable to load view class');
128 $driver_class->config->view($view_class);
131 my ($r, $req); # request objects
139 my $mock_driver = Test::MockModule->new($driver_class, no_auto => 1);
141 init => sub {$init++; shift->init_done(1)},
142 get_request => sub {($r, $req) = @_; $called{get_request}++},
143 parse_location => sub {$called{parse_location}++},
144 handler_guts => sub {$called{handler_guts}++; $status},
145 send_output => sub {$called{send_output}++},
148 my $rv = $driver_class->handler();
150 ok($r && $r->isa($driver_class), '... created $r');
151 ok($called{get_request}, '... calls get_request()');
152 ok($called{parse_location}, '... calls parse_location');
153 ok($called{handler_guts}, '... calls handler_guts()');
154 ok($called{send_output}, '... call send_output');
155 is($rv, 0, '... return status (should be ok?)');
156 ok(!$init, "... doesn't call init() if init_done()");
158 ok($r->headers_out && $r->headers_out->isa('Maypole::Headers'),
159 '... populates headers_out() with a Maypole::Headers object');
161 # call again, testing other branches
162 $driver_class->init_done(0);
164 $rv = $driver_class->handler();
165 ok($called{handler_guts} == 2 && $called{send_output} == 1,
166 '... returns early if handler_guts failed');
167 is($rv, -1, '... returning the error code from handler_guts');
169 $driver_class->handler();
170 ok($init && $driver_class->init_done, "... init() called if !init_done()");
177 @{$table_class . "::ISA"} = $model_class;
180 my ($applicable, %called);
182 my $mock_driver = new Test::MockModule($driver_class, no_auto => 1);
183 my $mock_table = new Test::MockModule($table_class, no_auto => 1);
186 is_applicable => sub {push @{$called{applicable}},\@_; $applicable},
187 get_request => sub {($r, $req) = @_},
188 additional_data => sub {$called{additional_data}++},
192 table_process => sub {push @{$called{process}},\@_},
196 class_of => sub {push @{$called{class_of}},\@_; $table_class},
197 process => sub {push @{$called{model_process}}, \@_},
201 process => sub {push @{$called{view_process}}, \@_; $OK}
207 $r->{path} = '/table/action';
210 my $status = $r->handler_guts();
212 is($r->model_class, $table_class, '... sets model_class from table()');
213 ok($called{additional_data}, '... call additional_data()');
214 is($status, $OK, '... return status = OK');
215 ok($called{model_process},
216 '... if_applicable, call model_class->process');
221 $applicable = $DECLINED;
223 $r->{path} = '/table/action';
226 $status = $r->handler_guts();
228 is($r->template, $r->path,
229 '... if ! is_applicable set template() to path()');
230 ok(!$called{model_process},
231 '... !if_applicable, call model_class->process');
232 is_deeply($called{view_process}[0][1], $r,
233 ' ... view_object->process called');
234 is($status, $OK, '... return status = OK');
236 # pre-load some output
240 $r->{output} = 'test';
242 $status = $r->handler_guts();
244 ok(!$called{view_process},
245 '... unless output, call view_object->process to get output');
247 # fail authentication
248 $mock_driver->mock(call_authenticate => sub {$DECLINED});
249 $status = $r->handler_guts();
250 is($status, $DECLINED,
251 '... return DECLINED unless call_authenticate == OK');
253 # ... TODO authentication error handling
254 # ... TODO model error handling
255 # ... TODO view processing error handling
260 $r->config->display_tables([qw(one two)]);
261 $r->config->ok_tables(undef);
262 $r->model_class($table_class);
264 $r->action('unittest');
266 $mock_model->mock('is_public', sub {0});
267 my $status = $r->is_applicable;
268 is($status, $DECLINED,
269 '... return DECLINED unless model_class->is_public(action)');
270 $mock_model->mock('is_public', sub {$is_public = \@_; 1});
271 $status = $r->is_applicable;
272 is($status, $OK, '... returns OK if table is in ok_tables');
273 is_deeply($is_public, [$r->model_class, 'unittest'],
274 '... calls model_class->is_public with request action');
275 is_deeply($r->config->ok_tables, {one => 1, two => 1},
276 '... config->ok_tables defaults to config->display_tables');
277 delete $r->config->ok_tables->{one};
278 $status = $r->is_applicable;
279 is($status, $DECLINED, '... return DECLINED unless $r->table is in ok_tables');
282 my $mock_driver = new Test::MockModule($driver_class, no_auto => 1);
283 my $mock_table = new Test::MockModule($table_class, no_auto => 1);
285 # call_authenticate()
289 authenticate => sub {$auth_calls{model_auth} = \@_; $OK}
291 my $status = $r->call_authenticate;
292 is_deeply($auth_calls{model_auth}, [$table_class, $r],
293 '... calls model_class->authenticate if it exists');
294 is($status, $OK, '... and returns its status (OK)');
295 $mock_table->mock(authenticate => sub {$DECLINED});
296 $status = $r->call_authenticate;
297 is($status, $DECLINED, '... or DECLINED, as appropriate');
299 $mock_table->unmock('authenticate');
300 $mock_driver->mock(authenticate => sub {return $DECLINED});
301 $status = $r->call_authenticate;
302 is($status, $DECLINED, '... otherwise it calls authenticte()');
303 $mock_driver->unmock('authenticate');
304 $status = $r->call_authenticate;
305 is($status, $OK, '... the default authenticate is OK');
312 exception => sub {$ex_calls{model_exception} = \@_; $OK}
315 exception => sub {$ex_calls{driver_exception} = \@_; 'X'}
317 my $status = $r->call_exception('ERR');
318 is_deeply($ex_calls{model_exception}, [$table_class, $r, 'ERR'],
319 '... calls model_class->exception if it exists');
320 is($status, $OK, '... and returns its status (OK)');
321 $mock_table->mock(exception => sub {$DECLINED});
322 $status = $r->call_exception('ERR');
323 is_deeply($ex_calls{driver_exception}, [$r, 'ERR'],
324 '... or calls driver->exception if model returns !OK');
325 is($status, 'X', '... and returns the drivers status');
327 $mock_table->unmock('exception');
328 $mock_driver->unmock('exception');
329 $status = $r->call_exception('ERR');
330 is($status, $ERROR, '... the default exception is ERROR');
335 is(Maypole->authenticate(), $OK, '... returns OK');
340 is(Maypole->exception(), $ERROR, '... returns ERROR');
347 is($r->path, 'frontpage', '... path() defaults to "frontpage"');
351 is($r->table, 'table', '... parses "table" from the first part of path');
352 ok(@{$r->args} == 0, '... "args" default to empty list');
354 $r->path('/table/action');
356 ok($r->table eq 'table' && $r->action eq 'action',
357 '... action is parsed from second part of path');
359 $r->path('/table/action/arg1/arg2');
361 is_deeply($r->args, [qw(arg1 arg2)],
362 '... "args" are populated from remaning components');
364 # ... action defaults to index
367 is($r->action, 'index', '... action defaults to index');
370 # get_template_root()
372 is(Maypole->get_template_root(), '.', '... returns "."');
377 eval {Maypole->parse_location()};
378 like($@, qr/Do not use Maypole directly/, '... croaks - must be overriden');
383 eval {Maypole->send_output};
384 like($@, qr/Do not use Maypole directly/, '... croaks - must be overriden');