PAGI
view release on metacpan or search on metacpan
t/middleware/11-url-handling.t view on Meta::CPAN
scheme => 'http',
path => '/test',
headers => [['Host', 'example.com']],
);
my @events;
run_async { $wrapped->($scope, async sub { {} }, async sub {
my ($e) = @_; push @events, $e }) };
is $events[0]{status}, 301, 'redirect status';
my %headers = map { lc($_->[0]) => $_->[1] } @{$events[0]{headers}};
is $headers{location}, 'https://example.com/test', 'redirects to HTTPS';
};
subtest 'HTTPSRedirect - passes through HTTPS' => sub {
my $redirect = PAGI::Middleware::HTTPSRedirect->new();
my $captured_scope;
my $app = async sub {
my ($scope, $receive, $send) = @_;
$captured_scope = $scope;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => 'OK', more => 0 });
};
my $wrapped = $redirect->wrap($app);
my $scope = make_scope(scheme => 'https');
my @events;
run_async { $wrapped->($scope, async sub { {} }, async sub {
my ($e) = @_; push @events, $e }) };
is $events[0]{status}, 200, 'HTTPS passes through';
};
subtest 'HTTPSRedirect - excludes paths' => sub {
my $redirect = PAGI::Middleware::HTTPSRedirect->new(
exclude => ['/health'],
);
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => 'OK', more => 0 });
};
my $wrapped = $redirect->wrap($app);
my $scope = make_scope(scheme => 'http', path => '/health');
my @events;
run_async { $wrapped->($scope, async sub { {} }, async sub {
my ($e) = @_; push @events, $e }) };
is $events[0]{status}, 200, 'excluded path not redirected';
};
# ===================
# ReverseProxy Middleware Tests
# ===================
subtest 'ReverseProxy - updates client from X-Forwarded-For' => sub {
my $proxy = PAGI::Middleware::ReverseProxy->new(
trusted_proxies => ['127.0.0.1'],
);
my $captured_scope;
my $app = async sub {
my ($scope, $receive, $send) = @_;
$captured_scope = $scope;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => 'OK', more => 0 });
};
my $wrapped = $proxy->wrap($app);
my $scope = make_scope(
client => ['127.0.0.1', 12345],
headers => [['X-Forwarded-For', '203.0.113.50, 198.51.100.1']],
);
run_async { $wrapped->($scope, async sub { {} }, async sub { }) };
is $captured_scope->{client}[0], '203.0.113.50', 'client IP from X-Forwarded-For';
is $captured_scope->{original_client}[0], '127.0.0.1', 'original client preserved';
};
subtest 'ReverseProxy - updates scheme from X-Forwarded-Proto' => sub {
my $proxy = PAGI::Middleware::ReverseProxy->new(
trusted_proxies => ['127.0.0.1'],
);
my $captured_scope;
my $app = async sub {
my ($scope, $receive, $send) = @_;
$captured_scope = $scope;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => 'OK', more => 0 });
};
my $wrapped = $proxy->wrap($app);
my $scope = make_scope(
client => ['127.0.0.1', 12345],
scheme => 'http',
headers => [['X-Forwarded-Proto', 'https']],
);
run_async { $wrapped->($scope, async sub { {} }, async sub { }) };
is $captured_scope->{scheme}, 'https', 'scheme updated from header';
};
subtest 'ReverseProxy - ignores untrusted proxies' => sub {
my $proxy = PAGI::Middleware::ReverseProxy->new(
trusted_proxies => ['10.0.0.1'],
);
my $captured_scope;
my $app = async sub {
my ($scope, $receive, $send) = @_;
$captured_scope = $scope;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => 'OK', more => 0 });
};
my $wrapped = $proxy->wrap($app);
my $scope = make_scope(
client => ['192.168.1.100', 12345], # Not trusted
headers => [['X-Forwarded-For', '203.0.113.50']],
);
run_async { $wrapped->($scope, async sub { {} }, async sub { }) };
is $captured_scope->{client}[0], '192.168.1.100', 'client IP unchanged for untrusted proxy';
};
# ===================
# Healthcheck Middleware Tests
# ===================
subtest 'Healthcheck - returns health status' => sub {
my $health = PAGI::Middleware::Healthcheck->new(
path => '/health',
);
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => 'App', more => 0 });
};
my $wrapped = $health->wrap($app);
my $scope = make_scope(path => '/health');
my @events;
run_async { $wrapped->($scope, async sub { {} }, async sub {
my ($e) = @_; push @events, $e }) };
is $events[0]{status}, 200, 'health check returns 200';
my $body = JSON::MaybeXS::decode_json($events[1]{body});
is $body->{status}, 'ok', 'status is ok';
};
subtest 'Healthcheck - passes through other paths' => sub {
my $health = PAGI::Middleware::Healthcheck->new(
path => '/health',
);
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => 'App', more => 0 });
};
my $wrapped = $health->wrap($app);
my $scope = make_scope(path => '/api');
my @events;
run_async { $wrapped->($scope, async sub { {} }, async sub {
my ($e) = @_; push @events, $e }) };
is $events[1]{body}, 'App', 'non-health path passes through';
};
subtest 'Healthcheck - runs custom checks' => sub {
my $health = PAGI::Middleware::Healthcheck->new(
path => '/health',
checks => {
always_ok => sub { 1 },
( run in 2.363 seconds using v1.01-cache-2.11-cpan-5a3173703d6 )