TPSGI.t 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #!/usr/bin/env perl
  2. use 5.040;
  3. use warnings FATAL => 'all';
  4. use re '/aa';
  5. use Test2::V0;
  6. use Test2::Plugin::NoWarnings;
  7. use File::Temp qw{tempdir};
  8. use Test::MockModule;
  9. use FindBin::libs;
  10. use TPSGI;
  11. # Every line the log gets, so a test can ask whether the reason for a 500 is
  12. # among them.
  13. package Test::Logger::Capture {
  14. use parent qw{Log::Dispatch::Output};
  15. our @lines;
  16. sub new {
  17. my ( $class, %p ) = @_;
  18. my $self = bless {}, $class;
  19. $self->_basic_init( name => 'capture', min_level => $p{min_level} );
  20. return $self;
  21. }
  22. sub log_message {
  23. my ( $self, %p ) = @_;
  24. push( @lines, $p{message} );
  25. return;
  26. }
  27. }
  28. local $INC{'Test/Logger/Capture.pm'} = __FILE__;
  29. my $dir = tempdir( CLEANUP => 1 );
  30. my %options = (
  31. log_name => "$dir/tpsgi.log",
  32. log_dir => $dir,
  33. verbose => 0,
  34. loggers => ['Test::Logger::Capture'],
  35. );
  36. sub fake_env {
  37. # The fake psgi.errors, which the caller keeps open.
  38. open( my $errors, '>', \my $written ) or die "Cannot open a scalar: $!"; ## no critic (RequireBriefOpen)
  39. return ( { 'psgi.errors' => $errors }, \$written );
  40. }
  41. # TPSGI builds its log once per process, so this has to run before anything
  42. # else here builds it: a logger that will not load is only a failure the first
  43. # time.
  44. subtest 'cannot_start, when building the log is what failed' => sub {
  45. my ( $env, $written ) = fake_env();
  46. my $res;
  47. ok(
  48. lives { $res = TPSGI::cannot_start( "Could not load Bogus.pm!\n", $env, %options, loggers => ['Bogus::Logger::That::Is::Not'] ) },
  49. 'a logger that will not load does not turn the 500 into an exception'
  50. ) or note $@;
  51. is( $res->[0], 500, 'it still answers 500' );
  52. like( $$written, qr/Could[ ]not[ ]load[ ]Bogus\.pm/xs, 'and psgi.errors still gets the reason' );
  53. };
  54. subtest 'cannot_start' => sub {
  55. local @Test::Logger::Capture::lines;
  56. my ( $env, $written ) = fake_env();
  57. my $res = TPSGI::cannot_start( "Could not load Bogus.pm!\nCan't locate Bogus/Dep.pm in \@INC\n", $env, %options );
  58. is( $res->[0], 500, 'answers 500' );
  59. like( $$written, qr/Can't[ ]locate[ ]Bogus\/Dep\.pm/xs, 'psgi.errors gets the whole reason' );
  60. my $reason = qr/Could[ ]not[ ]load[ ]Bogus[.]pm!/xs;
  61. like( \@Test::Logger::Capture::lines, bag { item qr/Could[ ]not[ ]start:[ ]$reason\nCan't[ ]locate/xs; etc() }, 'and so does the log' );
  62. ok( lives { TPSGI::cannot_start( "no env\n", undef, %options ) }, 'a missing env is not a second failure' ) or note $@;
  63. };
  64. subtest 'cannot_start retires the worker' => sub {
  65. my ($env) = fake_env();
  66. $env->{'psgix.harakiri'} = 1;
  67. TPSGI::cannot_start( "Could not load Bogus.pm!\n", $env, %options );
  68. ok( $env->{'psgix.harakiri.commit'}, 'a server that can retire the worker is told to' );
  69. ($env) = fake_env();
  70. TPSGI::cannot_start( "Could not load Bogus.pm!\n", $env, %options );
  71. ok( !exists $env->{'psgix.harakiri.commit'}, 'a server that cannot is not asked to' );
  72. };
  73. subtest 'cannot_start answers with a page' => sub {
  74. my $error = "Could not load Bogus.pm!\nCan't locate <Bogus/Dep.pm> in \@INC & \"elsewhere\"\n";
  75. my $res = TPSGI::cannot_start( $error, ( fake_env() )[0], %options );
  76. my %head = @{ $res->[1] };
  77. is( $head{'Content-type'}, 'text/html', 'in HTML' );
  78. like( $res->[2][0], qr/<h1>Internal[ ]Server[ ]Error<\/h1>/xs, 'saying what happened' );
  79. unlike( $res->[2][0], qr/Bogus/xs, 'and, without TPSGI_DEBUG, not why' );
  80. local $TPSGI::debug = 1;
  81. $res = TPSGI::cannot_start( $error, ( fake_env() )[0], %options );
  82. my $escaped = q{Can't locate &#60;Bogus/Dep.pm&#62; in @INC &#38; &#34;elsewhere&#34;};
  83. like( $res->[2][0], qr/\Q$escaped\E/xs, 'with TPSGI_DEBUG, the error, escaped' );
  84. unlike( $res->[2][0], qr/<Bogus/xs, 'none of it read as markup' );
  85. };
  86. subtest 'app, when the request dies' => sub {
  87. local @Test::Logger::Capture::lines;
  88. my ( $env, $written ) = fake_env();
  89. my $mock = Test::MockModule->new( 'TPSGI', no_auto => 1 );
  90. $mock->redefine( respond => sub { die "Can't locate Bogus/Dep.pm in \@INC\n" } );
  91. my $tpsgi = bless( { %options, ip => '0.0.0.0' }, 'TPSGI' );
  92. my $res = $tpsgi->app($env);
  93. is( $res->[0], 500, 'answers 500' );
  94. like( $$written, qr/Can't[ ]locate[ ]Bogus\/Dep\.pm/xs, 'psgi.errors gets the reason' );
  95. like( \@Test::Logger::Capture::lines, bag { item qr/Request[ ]failed:[ ]Can't[ ]locate[ ]Bogus\/Dep\.pm/xs; etc() }, 'and so does the log, not just that a 500 happened' );
  96. };
  97. # A router's pattern is matched as the router wrote it, whatever flags this
  98. # distribution's own regexes carry.
  99. subtest 'extract_query' => sub {
  100. my $tpsgi = bless( { %options, ip => '0.0.0.0' }, 'TPSGI' );
  101. my $env = { REQUEST_METHOD => 'GET' };
  102. my $query = $tpsgi->extract_query( "/post/caf\N{LATIN SMALL LETTER E WITH ACUTE}", { pattern => '/post/(\w+)', captures => ['slug'] }, $env );
  103. is( $query->{slug}, "caf\N{LATIN SMALL LETTER E WITH ACUTE}", 'a \\w in a route matches a letter outside ASCII' );
  104. $query = $tpsgi->extract_query( '/a b/c', { pattern => '/a b/(\w+)', captures => ['name'] }, $env );
  105. is( $query->{name}, 'c', 'and a space in a route is a space' );
  106. $query = $tpsgi->extract_query( "/note/x\ny", { pattern => '/note/(.+)', captures => ['text'] }, $env );
  107. is( $query->{text}, undef, 'and a dot does not match a newline' );
  108. };
  109. done_testing();