Developer-Dashboard
view release on metacpan or search on metacpan
t/17-web-server-ssl.t view on Meta::CPAN
use strict;
use warnings;
# Suppress warnings from external libraries while testing
BEGIN {
$SIG{__WARN__} = sub {
my ($msg) = @_;
return if $msg =~ m{Plack/Runner\.pm|Getopt/Long\.pm};
warn $msg;
};
}
use Capture::Tiny qw(capture);
use File::Path qw(make_path remove_tree);
use File::Spec;
use File::Temp qw(tempdir tempfile);
use IO::Socket::INET;
use IO::Socket::SSL ();
use HTTP::Request::Common qw(GET);
use LWP::UserAgent;
use Plack::Test;
use Socket qw(AF_UNIX PF_UNSPEC SOCK_STREAM);
use Test::More;
use Time::HiRes qw(sleep);
use lib 'lib';
use Developer::Dashboard::Web::Server;
use Developer::Dashboard::Web::DancerApp;
sub _openssl_cert_text {
my ($cert_file) = @_;
my ( $stdout, $stderr, $exit ) = capture {
system( 'openssl', 'x509', '-in', $cert_file, '-noout', '-text' );
};
die "Unable to inspect certificate $cert_file: $stderr$stdout" if $exit != 0;
return $stdout;
}
sub _openssl_verify_certificate_name {
my ( $cert_file, $name ) = @_;
my @cmd = ( 'openssl', 'verify', '-CAfile', $cert_file );
if ( defined $name && $name =~ /\A(?:\d{1,3}\.){3}\d{1,3}\z/ || ( defined $name && $name =~ /:/ ) ) {
push @cmd, '-verify_ip', $name;
}
else {
push @cmd, '-verify_hostname', $name;
}
push @cmd, $cert_file;
my ( $stdout, $stderr, $exit ) = capture {
system(@cmd);
};
return {
stdout => $stdout,
stderr => $stderr,
exit => $exit,
};
}
sub _mode_octal {
my ($path) = @_;
my @stat = stat($path);
return undef if !@stat;
return sprintf '%04o', $stat[2] & 07777;
}
sub _generate_legacy_cert_fixture {
my ( $cert_file, $key_file ) = @_;
my ( $config_fh, $config_file ) = tempfile( 'dd-legacy-openssl-XXXXXX', SUFFIX => '.cnf' );
print {$config_fh} <<'OPENSSL_CONFIG' or die "Unable to write legacy OpenSSL config $config_file: $!";
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = legacy_req
[ dn ]
t/17-web-server-ssl.t view on Meta::CPAN
# Test 13: Live SSL frontend redirects plain HTTP and still serves HTTPS on the public port
{
my $temp_home = tempdir(CLEANUP => 1);
local $ENV{HOME} = $temp_home;
my $listener = IO::Socket::INET->new(
LocalAddr => '127.0.0.1',
LocalPort => 0,
Proto => 'tcp',
ReuseAddr => 1,
Listen => 5,
) or die "Unable to reserve live SSL test port: $!";
my $port = $listener->sockport;
close $listener or die "Unable to close reserved live SSL test port: $!";
my $server = Developer::Dashboard::Web::Server->new(
app => Local::SSLTestApp->new,
host => '127.0.0.1',
port => $port,
workers => 1,
ssl => 1,
);
my $pid = fork();
die "Unable to fork live SSL server test: $!" if !defined $pid;
if ( !$pid ) {
$server->run;
exit 0;
}
my $ready = 0;
for ( 1 .. 50 ) {
my $probe = IO::Socket::INET->new(
PeerAddr => '127.0.0.1',
PeerPort => $port,
Proto => 'tcp',
);
if ($probe) {
close $probe;
$ready = 1;
last;
}
sleep 0.1;
}
ok( $ready, 'live SSL frontend became reachable on the public port' );
my $http = LWP::UserAgent->new(
max_redirect => 0,
timeout => 5,
);
my $http_response = $http->get("http://127.0.0.1:$port/live-check?mode=http");
is( $http_response->code, 307, 'live SSL frontend redirects plain HTTP on the public port' );
is(
$http_response->header('Location'),
"https://127.0.0.1:$port/live-check?mode=http",
'live SSL frontend redirects plain HTTP to the equivalent HTTPS URL on the same public port'
);
my $https_socket;
for ( 1 .. 50 ) {
$https_socket = IO::Socket::SSL->new(
PeerHost => '127.0.0.1',
PeerPort => $port,
SSL_verify_mode => 0,
Timeout => 5,
);
last if $https_socket;
sleep 0.1;
}
ok( $https_socket, 'live SSL frontend accepts a direct TLS client on the public port' );
my $https_raw = '';
if ($https_socket) {
print {$https_socket} "GET / HTTP/1.1\r\nHost: 127.0.0.1:$port\r\nConnection: close\r\n\r\n"
or die "Unable to write HTTPS test request: $!";
$https_raw = do {
local $/;
<$https_socket>;
};
close $https_socket;
like( $https_raw, qr/^HTTP\/1\.1 200 OK\r\n/, 'live SSL frontend still serves HTTPS on the public port' );
like( $https_raw, qr/\r\n\r\nOK\z/s, 'live SSL frontend preserves the HTTPS app response body' );
}
else {
diag( 'IO::Socket::SSL connect error: ' . ( IO::Socket::SSL::errstr() || 'unknown SSL error' ) );
fail('live SSL frontend still serves HTTPS on the public port');
fail('live SSL frontend preserves the HTTPS app response body');
}
kill 'TERM', $pid;
waitpid( $pid, 0 );
}
done_testing();
__END__
=head1 NAME
t/17-web-server-ssl.t - SSL support tests for Developer Dashboard web server
=head1 DESCRIPTION
Tests self-signed certificate generation, SSL flag handling, HTTPS URL generation,
Starman SSL configuration, and HTTP->HTTPS redirect middleware.
=for comment FULL-POD-DOC START
=head1 PURPOSE
This test is the executable regression contract for HTTPS serving, certificates, and browser-facing SSL behavior. Read it when you need to understand the real fixture setup, assertions, and failure modes for this slice of the repository instead of gu...
=head1 WHY IT EXISTS
It exists because HTTPS serving, certificates, and browser-facing SSL behavior has enough moving parts that a code-only review can miss real regressions. Keeping those expectations in a dedicated test file makes the TDD loop, coverage loop, and relea...
=head1 WHEN TO USE
Use this file when changing HTTPS serving, certificates, and browser-facing SSL behavior, when a focused CI failure points here, or when you want a faster regression loop than running the entire suite.
=head1 HOW TO USE
Run it directly with C<prove -lv t/17-web-server-ssl.t> while iterating, then keep it green under C<prove -lr t> and the coverage runs before release.
=head1 WHAT USES IT
Developers during TDD, the full C<prove -lr t> suite, the coverage gates, and the release verification loop all rely on this file to keep this behavior from drifting.
=head1 EXAMPLES
Example 1:
prove -lv t/17-web-server-ssl.t
Run the focused regression test by itself while you are changing the behavior it owns.
Example 2:
HARNESS_PERL_SWITCHES=-MDevel::Cover prove -lv t/17-web-server-ssl.t
Exercise the same focused test while collecting coverage for the library code it reaches.
Example 3:
prove -lr t
( run in 0.521 second using v1.01-cache-2.11-cpan-39bf76dae61 )