PAGI-Tools
view release on metacpan or search on metacpan
t/app/02-routing.t view on Meta::CPAN
$urlmap->mount('/api' => $inner);
my $app = $urlmap->to_app;
run_async(async sub {
await $app->(
{ type => 'http', path => '/api/users/123' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; },
);
});
is $received_path, '/users/123', 'path adjusted (prefix removed)';
};
subtest 'returns 404 for unmatched path' => sub {
my $urlmap = PAGI::App::URLMap->new;
$urlmap->mount('/api' => make_response_app(200, 'API'));
my $app = $urlmap->to_app;
my @sent;
run_async(async sub {
await $app->(
{ type => 'http', path => '/unknown' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; push @sent, $event },
);
});
is $sent[0]{status}, 404, 'returns 404';
};
subtest 'longest prefix wins' => sub {
my $urlmap = PAGI::App::URLMap->new;
$urlmap->mount('/api' => make_response_app(200, 'API'));
$urlmap->mount('/api/v2' => make_response_app(200, 'API-V2'));
my $app = $urlmap->to_app;
my @sent;
run_async(async sub {
await $app->(
{ type => 'http', path => '/api/v2/users' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; push @sent, $event },
);
});
is $sent[1]{body}, 'API-V2', 'longer prefix matched';
};
};
# =============================================================================
# Test: PAGI::App::Cascade
# =============================================================================
subtest 'App::Cascade tries apps in sequence' => sub {
subtest 'returns first non-404 response' => sub {
my $cascade = PAGI::App::Cascade->new(
apps => [
make_response_app(404, 'Not Found'),
make_response_app(200, 'Found'),
make_response_app(200, 'Never Reached'),
],
);
my $app = $cascade->to_app;
my @sent;
run_async(async sub {
await $app->(
{ type => 'http', path => '/test' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; push @sent, $event },
);
});
is $sent[0]{status}, 200, 'returns 200';
is $sent[1]{body}, 'Found', 'correct app responded';
};
subtest 'returns 404 if all apps return 404' => sub {
my $cascade = PAGI::App::Cascade->new(
apps => [
make_response_app(404, 'Not Found 1'),
make_response_app(404, 'Not Found 2'),
],
);
my $app = $cascade->to_app;
my @sent;
run_async(async sub {
await $app->(
{ type => 'http', path => '/test' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; push @sent, $event },
);
});
is $sent[0]{status}, 404, 'returns 404 when all fail';
};
subtest 'custom catch codes' => sub {
my $cascade = PAGI::App::Cascade->new(
apps => [
make_response_app(403, 'Forbidden'),
make_response_app(200, 'Success'),
],
catch => [403, 404],
);
my $app = $cascade->to_app;
my @sent;
run_async(async sub {
await $app->(
{ type => 'http', path => '/test' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; push @sent, $event },
);
});
is $sent[0]{status}, 200, 'catches 403 and tries next';
};
};
# =============================================================================
# Test: PAGI::App::NotFound
# =============================================================================
subtest 'App::NotFound returns 404' => sub {
subtest 'default 404 response' => sub {
my $app = PAGI::App::NotFound->new->to_app;
my @sent;
run_async(async sub {
await $app->(
{ type => 'http', path => '/anything' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; push @sent, $event },
);
});
is $sent[0]{status}, 404, 'returns 404';
like $sent[1]{body}, qr/Not Found/i, 'default body';
};
subtest 'custom body' => sub {
my $app = PAGI::App::NotFound->new(body => 'Custom 404')->to_app;
my @sent;
run_async(async sub {
await $app->(
{ type => 'http', path => '/anything' },
async sub { { type => 'http.disconnect' } },
async sub {
my ($event) = @_; push @sent, $event },
);
});
is $sent[1]{body}, 'Custom 404', 'custom body';
};
};
# =============================================================================
# Test: PAGI::App::Redirect
# =============================================================================
subtest 'App::Redirect returns redirects' => sub {
t/app/02-routing.t view on Meta::CPAN
await $send->({
type => 'http.response.start',
status => 200,
headers => [['content-type', 'text/plain']],
});
await $send->({ type => 'http.response.body', body => 'ok', more => 0 });
};
my $map = PAGI::App::URLMap->new;
$map->mount('/api' => $inner);
my $app = $map->to_app;
my @sent;
my $send = sub { my ($msg) = @_; push @sent, $msg; Future->done };
$app->({ type => 'http', method => 'GET', path => '/api/users' },
sub { Future->done }, $send)->get;
is $coercion_calls[0]{root_path}, '/api', 'root_path set to mount prefix';
is $coercion_calls[0]{path}, '/users', 'path has prefix stripped';
ok !exists $coercion_calls[0]{script_name}, 'off-spec script_name key is gone';
@sent = ();
@coercion_calls = ();
$app->({ type => 'http', method => 'GET', path => '/api/users', root_path => '/outer' },
sub { Future->done }, $send)->get;
is $coercion_calls[0]{root_path}, '/outer/api', 'root_path appends to existing (nested mounts)';
};
subtest 'URLMap coerces components, class names, and default' => sub {
require TestApps::Component;
my $map = PAGI::App::URLMap->new(
default => TestApps::Component->new(body => 'fallback'),
);
$map->mount('/c' => TestApps::Component->new(body => 'mounted'));
$map->mount('/s' => 'TestApps::Component');
my $app = $map->to_app;
my @sent;
my $send = sub { my ($msg) = @_; push @sent, $msg; Future->done };
$app->({ type => 'http', method => 'GET', path => '/c/x' },
sub { Future->done }, $send)->get;
is $sent[1]{body}, 'mounted', 'component object mounted directly';
@sent = ();
$app->({ type => 'http', method => 'GET', path => '/s/x' },
sub { Future->done }, $send)->get;
is $sent[1]{body}, 'component', 'class name mounted directly';
@sent = ();
$app->({ type => 'http', method => 'GET', path => '/nomatch' },
sub { Future->done }, $send)->get;
is $sent[1]{body}, 'fallback', 'default coerced too';
};
subtest 'Cascade coerces apps list and add()' => sub {
require TestApps::Component;
require PAGI::App::NotFound;
my $cascade = PAGI::App::Cascade->new(
apps => [ PAGI::App::NotFound->new ], # always 404s -> falls through
);
$cascade->add(TestApps::Component->new(body => 'second'));
my $app = $cascade->to_app;
my @sent;
my $send = sub { my ($msg) = @_; push @sent, $msg; Future->done };
$app->({ type => 'http', method => 'GET', path => '/x' },
sub { Future->done }, $send)->get;
is $sent[0]{status}, 200, 'fell through the 404 component';
is $sent[1]{body}, 'second', 'component object added without ->to_app';
};
subtest 'Redirect builds a response value (static + dynamic + query)' => sub {
require PAGI::App::Redirect;
my $run = sub {
my ($app, $scope) = @_;
my @sent;
my $send = sub { my ($m) = @_; push @sent, $m; Future->done };
$app->($scope, sub { Future->done }, $send)->get;
return \@sent;
};
my $sent = $run->(
PAGI::App::Redirect->new(to => '/new', status => 301)->to_app,
{ type => 'http', method => 'GET', path => '/old' },
);
is $sent->[0]{status}, 301, 'status';
my %h = map { lc($_->[0]) => $_->[1] } @{$sent->[0]{headers}};
is $h{location}, '/new', 'location';
is $h{'content-type'}, 'text/plain', 'content-type preserved';
is $h{'content-length'}, 0, 'content-length 0';
is $sent->[1]{body}, '', 'empty body';
$sent = $run->(
PAGI::App::Redirect->new(to => sub { my ($s) = @_; "/from$s->{path}" })->to_app,
{ type => 'http', method => 'GET', path => '/p', query_string => 'a=1' },
);
%h = map { lc($_->[0]) => $_->[1] } @{$sent->[0]{headers}};
is $h{location}, '/from/p?a=1', 'coderef receives scope; query preserved';
is $sent->[0]{status}, 302, 'default status';
$sent = $run->(
PAGI::App::Redirect->new(to => '/x', preserve_query => 0)->to_app,
{ type => 'http', method => 'GET', path => '/y', query_string => 'a=1' },
);
%h = map { lc($_->[0]) => $_->[1] } @{$sent->[0]{headers}};
is $h{location}, '/x', 'preserve_query => 0 suppresses query append';
};
subtest 'NotFound builds a response value (static + coderef body)' => sub {
require PAGI::App::NotFound;
my $run = sub {
my ($app, $scope) = @_;
my @sent;
my $send = sub { my ($m) = @_; push @sent, $m; Future->done };
$app->($scope, sub { Future->done }, $send)->get;
return \@sent;
};
# defaults
( run in 1.504 second using v1.01-cache-2.11-cpan-9581c071862 )