The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use Plack::Test;

{
    package Foo::Controller;
    use Moose;

    sub index    { "index for foo" }
    sub foo      { "/foo/foo" }
    sub bar      { "/foo/bar" }
    sub specific { "/foo/specific" }

    sub get      { "GET on foo" }
}

{
    package Bar::Controller;
    use Moose;

    sub index          { "index for bar" }
    sub thing          { "/bar/thing" }
    sub other_thing    { "/bar/other_thing" }
    sub specific_thing { "specific thing for bar" }

    sub any            { "any method on bar" }
}

{
    package Baz::Controller;
    use Moose;

    sub index { "index for baz" }
    sub foo   { "/baz/foo" }

    our $AUTOLOAD;
    sub AUTOLOAD {
        my $self = shift;
        (my $meth = $AUTOLOAD) =~ s/.*:://;
        "got autoloaded method $meth for baz";
    }
    sub can { 1 }
}

{
    package Specific::Action;
    use Moose;

    sub get  { "specific GET" }
    sub post { "specific POST" }
    sub any  { "specific any method" }
}

{
    package Foo;
    use OX;
    use Moose::Util::TypeConstraints qw(enum);

    has foo => (
        is  => 'ro',
        isa => 'Foo::Controller',
    );

    has bar => (
        is  => 'ro',
        isa => 'Bar::Controller',
    );

    has baz => (
        is  => 'ro',
        isa => 'Baz::Controller',
    );

    has specific_action => (
        is  => 'ro',
        isa => 'Specific::Action',
    );

    router as {

        route '/foo/:action'  => 'foo._';
        route '/foo/specific' => sub { "got a specific path under /foo" };

        route '/bar/:thing' => sub {
            my ($req, $thing) = @_;
            return "got $thing for bar";
        }, (thing => { isa => enum(['thing', 'other_thing']) });
        route '/bar/thing'  => 'bar.specific_thing';

        route '/baz' => 'baz.index';

        route '/controller/:controller'         => '_.index';
        route '/controller/:controller/:action' => '_._';

        route '/action/:action' => '_';
        route '/action/specific' => 'specific_action';
    };
}

my %expected = (
    '/foo/foo'                    => '/foo/foo',
    '/foo/bar'                    => '/foo/bar',
    '/foo/specific'               => 'got a specific path under /foo',
    '/bar/thing'                  => 'specific thing for bar',
    '/bar/other_thing'            => 'got other_thing for bar',
    '/baz'                        => 'index for baz',
    '/controller/foo'             => 'index for foo',
    '/controller/bar'             => 'index for bar',
    '/controller/baz'             => 'index for baz',
    '/controller/foo/foo'         => '/foo/foo',
    '/controller/foo/specific'    => '/foo/specific',
    '/controller/bar/thing'       => '/bar/thing',
    '/controller/bar/other_thing' => '/bar/other_thing',
    '/controller/baz/a'           => 'got autoloaded method a for baz',
    '/controller/baz/foo'         => '/baz/foo',
    '/action/foo'                 => 'GET on foo',
    '/action/bar'                 => 'any method on bar',
    '/action/specific'            => 'specific GET',
    'POST:/action/specific'       => 'specific POST',
    'PUT:/action/specific'        => 'specific any method',
);

test_psgi
    app    => Foo->new->to_app,
    client => sub {
        my $cb = shift;
        for my $r (keys %expected) {
            my $meth = 'GET';
            my $path = $r;
            if ($path =~ s/^(.*)://) {
                $meth = $1;
            }
            my $req = HTTP::Request->new($meth => "http://localhost$path");
            my $res = $cb->($req);
            is($res->content, $expected{$r},
               "right content for $meth $path");
        }
    };

done_testing;