The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Test::Foo;
use Mojo::Base 'Mojolicious::Controller';

sub bar  {1}
sub home {1}

package Test::Controller;
use Mojo::Base 'Mojolicious::Controller';

has 'render_called';

sub new {
  shift->SUPER::new(@_)->tap(sub { $_->app->log->level('fatal') });
}

sub render { shift->render_called(1) }

package main;
use Mojo::Base -strict;

use Test::More;
use Mojo::Upload;
use Mojolicious;

# Fresh controller
my $app = Mojolicious->new(secrets => ['works']);
my $c = $app->build_controller;
is $c->url_for('/'), '/', 'routes are working';

# Set
$c->stash(foo => 'bar');
is $c->stash('foo'), 'bar', 'set and return a stash value';

# Ref value
my $stash = $c->stash;
is ref $stash, 'HASH', 'return a hash reference';
is $stash->{foo}, 'bar', 'right value';

# Replace
$c->stash(foo => 'baz');
is $c->stash('foo'), 'baz', 'replace and return a stash value';

# Set 0
$c->stash(zero => 0);
is $c->stash('zero'), 0, 'set and return 0 value';

# Replace with 0
$c->stash(foo => 0);
is $c->stash('foo'), 0, 'replace and return 0 value';

# Use 0 as key
$c->stash(0 => 'boo');
is $c->stash('0'), 'boo', 'set and get with 0 as key';

# Delete
$stash = $c->stash;
delete $stash->{foo};
delete $stash->{0};
delete $stash->{zero};
is $c->stash->{foo}, undef, 'element has been deleted';
is $c->stash->{0}, undef, 'element has been deleted';
is $c->stash->{zero}, undef, 'element has been deleted';
$c->stash('foo' => 'zoo');
delete $c->stash->{foo};
is $c->stash->{foo}, undef, 'element has been deleted';

# Set via hash
$c->stash({a => 1, b => 2});
$stash = $c->stash;
is $c->stash->{a}, 1, 'right value';
is $c->stash->{b}, 2, 'right value';

# Override captures
is $c->param('foo'), undef, 'no value';
is $c->param(foo => 'works')->param('foo'), 'works', 'right value';
is $c->param(foo => 'too')->param('foo'),   'too',   'right value';
is $c->param(foo => qw(just works))->param('foo'), 'works', 'right value';
is_deeply $c->every_param('foo'), [qw(just works)], 'right values';
is_deeply $c->every_param('bar'), [], 'no values';
is $c->param(foo => undef)->param('foo'), undef, 'no value';
is $c->param(foo => Mojo::Upload->new(name => 'bar'))->param('foo')->name,
  'bar', 'right value';
is $c->param(foo => ['ba;r', 'baz'])->param('foo'), 'baz', 'right value';
is_deeply $c->every_param('foo'), ['ba;r', 'baz'], 'right values';

# Reserved stash values are hidden
$c = $app->build_controller;
is $c->param(action => 'test')->param('action'), undef, 'value is reserved';
is $c->param(app    => 'test')->param('app'),    undef, 'value is reserved';
is $c->param(cb     => 'test')->param('cb'),     undef, 'value is reserved';
is $c->param(controller => 'test')->param('controller'), undef,
  'value is reserved';
is $c->param(data    => 'test')->param('data'),    undef, 'value is reserved';
is $c->param(extends => 'test')->param('extends'), undef, 'value is reserved';
is $c->param(format  => 'test')->param('format'),  undef, 'value is reserved';
is $c->param(handler => 'test')->param('handler'), undef, 'value is reserved';
is $c->param(inline  => 'test')->param('inline'),  undef, 'value is reserved';
is $c->param(json    => 'test')->param('json'),    undef, 'value is reserved';
is $c->param(layout  => 'test')->param('layout'),  undef, 'value is reserved';
is $c->param(namespace => 'test')->param('namespace'), undef,
  'value is reserved';
is $c->param(path     => 'test')->param('path'),     undef, 'value is reserved';
is $c->param(status   => 'test')->param('status'),   undef, 'value is reserved';
is $c->param(template => 'test')->param('template'), undef, 'value is reserved';
is $c->param(text     => 'test')->param('text'),     undef, 'value is reserved';
is $c->param(variant  => 'test')->param('variant'),  undef, 'value is reserved';

# Controller with application and routes
$c = $app->controller_class('Test::Controller')->build_controller;
my $d = $c->app->routes;
ok $d, 'initialized';
$d->namespaces(['Test']);
$d->route('/')->over([])->to(controller => 'foo', action => 'home');
$d->route('/foo/(capture)')->to(controller => 'foo', action => 'bar');

# Cache
$c = $app->build_controller;
$c->req->method('GET');
$c->req->url->parse('/');
ok $d->dispatch($c), 'dispatched';
is $c->stash->{controller}, 'foo',  'right value';
is $c->stash->{action},     'home', 'right value';
is $c->match->stack->[0]{controller}, 'foo',  'right value';
is $c->match->stack->[0]{action},     'home', 'right value';
ok $c->render_called, 'rendered';
my $cache = $d->cache->get('GET:/:0');
ok $cache, 'route has been cached';
$c = $app->build_controller;
$c->req->method('GET');
$c->req->url->parse('/');
$d->match($c);
is $c->stash->{controller}, undef, 'no value';
is $c->stash->{action},     undef, 'no value';
is $c->match->stack->[0]{controller}, 'foo',  'right value';
is $c->match->stack->[0]{action},     'home', 'right value';
ok !$c->render_called, 'not rendered';
$c = $app->build_controller;
$c->req->method('GET');
$c->req->url->parse('/');
ok $d->dispatch($c), 'dispatched';
is $c->stash->{controller}, 'foo',  'right value';
is $c->stash->{action},     'home', 'right value';
is $c->match->stack->[0]{controller}, 'foo',  'right value';
is $c->match->stack->[0]{action},     'home', 'right value';
ok $c->render_called, 'rendered';
is_deeply $d->cache->get('GET:/:0'), $cache, 'cached route has been reused';

# 404 clean stash
$c = $app->build_controller;
$c->req->method('GET');
$c->req->url->parse('/not_found');
ok !$d->dispatch($c), 'not dispatched';
ok !$c->render_called, 'nothing rendered';

# No escaping
$c = $app->build_controller;
$c->req->method('POST');
$c->req->url->parse('/foo/hello');
$c->stash(test => 23);
ok $d->dispatch($c), 'dispatched';
is $c->stash->{controller}, 'foo',   'right value';
is $c->stash->{action},     'bar',   'right value';
is $c->stash->{capture},    'hello', 'right value';
is $c->stash->{test},       23,      'right value';
is $c->param('controller'), undef,   'no value';
is $c->param('action'),     undef,   'no value';
is $c->param('capture'),    'hello', 'right value';
$c->param(capture => 'bye');
is $c->param('controller'), undef, 'no value';
is $c->param('action'),     undef, 'no value';
is $c->param('capture'),    'bye', 'right value';
$c->param(capture => undef);
is $c->param('controller'), undef, 'no value';
is $c->param('action'),     undef, 'no value';
is $c->param('capture'),    undef, 'no value';
$c->req->param(foo => 'bar');
is $c->param('controller'), undef, 'no value';
is $c->param('action'),     undef, 'no value';
is $c->param('capture'),    undef, 'no value';
is $c->param('foo'),        'bar', 'right value';
$c->req->param(bar => 'baz');
is $c->param('controller'), undef, 'no value';
is $c->param('action'),     undef, 'no value';
is $c->param('capture'),    undef, 'no value';
is $c->param('foo'),        'bar', 'right value';
is $c->param('bar'),        'baz', 'right value';
$c->req->param(action => 'baz');
is $c->param('controller'), undef, 'no value';
is $c->param('action'),     'baz', 'no value';
is $c->param('capture'),    undef, 'no value';
is $c->param('foo'),        'bar', 'right value';
is $c->param('bar'),        'baz', 'right value';
ok $c->render_called, 'rendered';

# Escaping
$c = $app->build_controller;
$c->req->method('GET');
$c->req->url->parse('/foo/hello%20there');
ok $d->dispatch($c), 'dispatched';
is $c->stash->{controller}, 'foo',         'right value';
is $c->stash->{action},     'bar',         'right value';
is $c->stash->{capture},    'hello there', 'right value';
is $c->param('controller'), undef,         'no value';
is $c->param('action'),     undef,         'no value';
is $c->param('capture'),    'hello there', 'right value';
ok $c->render_called, 'rendered';

# Escaping UTF-8
$c = $app->build_controller;
$c->req->method('GET');
$c->req->url->parse('/foo/%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82');
ok $d->dispatch($c), 'dispatched';
is $c->stash->{controller}, 'foo',          'right value';
is $c->stash->{action},     'bar',          'right value';
is $c->stash->{capture},    'привет', 'right value';
is $c->param('controller'), undef,          'no value';
is $c->param('action'),     undef,          'no value';
is $c->param('capture'),    'привет', 'right value';
ok $c->render_called, 'rendered';

# Not a WebSocket transaction
eval { $c->send('test') };
like $@, qr/^No WebSocket connection to send message to/, 'right error';

done_testing();