diff --git a/t/mojolicious/longpolling_lite_app.t b/t/mojolicious/longpolling_lite_app.t index 455f5ce546..858791edf2 100644 --- a/t/mojolicious/longpolling_lite_app.t +++ b/t/mojolicious/longpolling_lite_app.t @@ -159,132 +159,147 @@ get '/too_long' => sub { my $t = Test::Mojo->new; -# Stream without delay and finish -$t->app->log->level('debug')->unsubscribe('message'); -my $log = ''; -my $cb = $t->app->log->on(message => sub { $log .= pop }); -my $stash; -$t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$t->get_ok('/write')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_type_is('text/plain') - ->content_is('this was short.'); -Mojo::IOLoop->one_tick until $stash->{finished}; -ok !$t->tx->kept_alive, 'connection was not kept alive'; -ok !$t->tx->keep_alive, 'connection will not be kept alive'; -is $stash->{finished}, 1, 'finish event has been emitted once'; -ok $stash->{destroyed}, 'controller has been destroyed'; -unlike $log, qr/Nothing has been rendered, expecting delayed response/, 'right message'; -$t->app->log->unsubscribe(message => $cb); - -# Stream without delay and content length -$t->get_ok('/write/length')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_type_is('text/plain') - ->content_is('this was short and plain.'); -ok !$t->tx->kept_alive, 'connection was not kept alive'; -ok $t->tx->keep_alive, 'connection will be kept alive'; - -# Stream without delay and empty write -$t->get_ok('/write/nolength')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') - ->header_is('Content-Length' => undef)->content_type_is('text/plain') - ->content_is('this was short and had no length.'); -ok $t->tx->kept_alive, 'connection was kept alive'; -ok !$t->tx->keep_alive, 'connection will not be kept alive'; - -# Chunked response with delay -$stash = undef; -$t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$t->get_ok('/longpoll/chunked')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') - ->content_type_is('text/plain')->content_is('hi there, whats up?'); -Mojo::IOLoop->one_tick until $stash->{finished}; -ok !$t->tx->kept_alive, 'connection was not kept alive'; -ok $t->tx->keep_alive, 'connection will be kept alive'; -is $stash->{finished}, 1, 'finish event has been emitted once'; -ok $stash->{destroyed}, 'controller has been destroyed'; - -# Interrupted by closing the connection -$stash = undef; -$t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -my $port = $t->ua->server->url->port; -Mojo::IOLoop->client( - {port => $port} => sub { - my ($loop, $err, $stream) = @_; - $stream->on(read => sub { shift->close }); - $stream->write("GET /longpoll/chunked HTTP/1.1\x0d\x0a\x0d\x0a"); - } -); -Mojo::IOLoop->one_tick until $stash->{finished}; -is $stash->{finished}, 1, 'finish event has been emitted once'; -ok $stash->{destroyed}, 'controller has been destroyed'; - -# Interrupted by raising an error -my $tx = $t->ua->build_tx(GET => '/longpoll/chunked'); -my $buffer = ''; -$tx->res->content->unsubscribe('read')->on( - read => sub { - my ($content, $chunk) = @_; - $buffer .= $chunk; - $tx->res->error({message => 'Interrupted'}) if length $buffer == 3; - } -); -$t->ua->start($tx); -is $tx->res->code, 200, 'right status'; -is $tx->res->error->{message}, 'Interrupted', 'right error'; -is $buffer, 'hi ', 'right content'; - -# Stream with delay and content length -$stash = undef; -$t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$t->get_ok('/longpoll/length')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') - ->content_type_is('text/plain')->content_is('hi there plain, whats up?'); -is $stash->{drain}, 1, 'drain event has been emitted once'; - -# Stream with delay and finish -$t->get_ok('/longpoll/nolength')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') - ->header_is('Content-Length' => undef)->content_type_is('text/plain')->content_is('hi there, what length?'); -ok !$t->tx->keep_alive, 'connection will not be kept alive'; - -# The drain event should be emitted on the next reactor tick -$stash = undef; -$t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$t->get_ok('/longpoll/order')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') - ->content_is('First, second, third!'); -is $stash->{order}, 1, 'the drain event was emitted on the next reactor tick'; - -# Static file with cookies and session -$log = ''; -$cb = $t->app->log->on(message => sub { $log .= pop }); -$t->get_ok('/longpoll/static')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') - ->header_like('Set-Cookie' => qr/bar=baz/)->header_like('Set-Cookie' => qr/mojolicious=/) - ->content_type_is('text/plain;charset=UTF-8')->content_is("Hello Mojo from a static file!\n"); -like $log, qr/Nothing has been rendered, expecting delayed response/, 'right message'; -$t->app->log->unsubscribe(message => $cb); - -# Custom response -$t->get_ok('/longpoll/dynamic')->status_is(201)->header_is(Server => 'Mojolicious (Perl)') - ->header_like('Set-Cookie' => qr/baz=yada/)->content_is('Dynamic!'); - -# Chunked response streaming with drain event -$stash = undef; -$t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$t->get_ok('/stream')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_is('0123456789'); -is $stash->{subscribers}, 0, 'no leaking subscribers'; -ok $stash->{destroyed}, 'controller has been destroyed'; - -# Rendering of template -$stash = undef; -$t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$t->get_ok('/render')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_is('Finish!'); -Mojo::IOLoop->one_tick until $stash->{destroyed}; -ok !$stash->{writing}, 'finish event timing is right'; -ok $stash->{destroyed}, 'controller has been destroyed'; - -# Request timeout -$tx = $t->ua->request_timeout(0.5)->get('/too_long'); -is $tx->error->{message}, 'Request timeout', 'right error'; -$t->ua->request_timeout(0); - -# Inactivity timeout -$tx = $t->ua->inactivity_timeout(0.5)->get('/too_long'); -is $tx->error->{message}, 'Inactivity timeout', 'right error'; -$t->ua->inactivity_timeout(20); +subtest 'Stream without delay and finish' => sub { + $t->app->log->level('debug')->unsubscribe('message'); + my $log = ''; + my $cb = $t->app->log->on(message => sub { $log .= pop }); + my $stash; + $t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $t->get_ok('/write')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_type_is('text/plain') + ->content_is('this was short.'); + Mojo::IOLoop->one_tick until $stash->{finished}; + ok !$t->tx->kept_alive, 'connection was not kept alive'; + ok !$t->tx->keep_alive, 'connection will not be kept alive'; + is $stash->{finished}, 1, 'finish event has been emitted once'; + ok $stash->{destroyed}, 'controller has been destroyed'; + unlike $log, qr/Nothing has been rendered, expecting delayed response/, 'right message'; + $t->app->log->unsubscribe(message => $cb); +}; + +subtest 'Stream without delay and content length' => sub { + $t->get_ok('/write/length')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_type_is('text/plain') + ->content_is('this was short and plain.'); + ok !$t->tx->kept_alive, 'connection was not kept alive'; + ok $t->tx->keep_alive, 'connection will be kept alive'; +}; + +subtest 'Stream without delay and empty write' => sub { + $t->get_ok('/write/nolength')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') + ->header_is('Content-Length' => undef)->content_type_is('text/plain') + ->content_is('this was short and had no length.'); + ok $t->tx->kept_alive, 'connection was kept alive'; + ok !$t->tx->keep_alive, 'connection will not be kept alive'; +}; + +subtest 'Chunked response with delay' => sub { + my $stash = undef; + $t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $t->get_ok('/longpoll/chunked')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') + ->content_type_is('text/plain')->content_is('hi there, whats up?'); + Mojo::IOLoop->one_tick until $stash->{finished}; + ok !$t->tx->kept_alive, 'connection was not kept alive'; + ok $t->tx->keep_alive, 'connection will be kept alive'; + is $stash->{finished}, 1, 'finish event has been emitted once'; + ok $stash->{destroyed}, 'controller has been destroyed'; +}; + +subtest 'Interrupted by closing the connection' => sub { + my $stash = undef; + $t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + my $port = $t->ua->server->url->port; + Mojo::IOLoop->client( + {port => $port} => sub { + my ($loop, $err, $stream) = @_; + $stream->on(read => sub { shift->close }); + $stream->write("GET /longpoll/chunked HTTP/1.1\x0d\x0a\x0d\x0a"); + } + ); + Mojo::IOLoop->one_tick until $stash->{finished}; + is $stash->{finished}, 1, 'finish event has been emitted once'; + ok $stash->{destroyed}, 'controller has been destroyed'; +}; + +subtest 'Interrupted by raising an error' => sub { + my $tx = $t->ua->build_tx(GET => '/longpoll/chunked'); + my $buffer = ''; + $tx->res->content->unsubscribe('read')->on( + read => sub { + my ($content, $chunk) = @_; + $buffer .= $chunk; + $tx->res->error({message => 'Interrupted'}) if length $buffer == 3; + } + ); + $t->ua->start($tx); + is $tx->res->code, 200, 'right status'; + is $tx->res->error->{message}, 'Interrupted', 'right error'; + is $buffer, 'hi ', 'right content'; +}; + +subtest 'Stream with delay and content length' => sub { + my $stash = undef; + $t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $t->get_ok('/longpoll/length')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') + ->content_type_is('text/plain')->content_is('hi there plain, whats up?'); + is $stash->{drain}, 1, 'drain event has been emitted once'; +}; + +subtest 'Stream with delay and finish' => sub { + $t->get_ok('/longpoll/nolength')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') + ->header_is('Content-Length' => undef)->content_type_is('text/plain')->content_is('hi there, what length?'); + ok !$t->tx->keep_alive, 'connection will not be kept alive'; +}; + +subtest 'The drain event should be emitted on the next reactor tick' => sub { + my $stash = undef; + $t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $t->get_ok('/longpoll/order')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') + ->content_is('First, second, third!'); + is $stash->{order}, 1, 'the drain event was emitted on the next reactor tick'; +}; + +subtest 'Static file with cookies and session' => sub { + my $log = ''; + my $cb = $t->app->log->on(message => sub { $log .= pop }); + $t->get_ok('/longpoll/static')->status_is(200)->header_is(Server => 'Mojolicious (Perl)') + ->header_like('Set-Cookie' => qr/bar=baz/)->header_like('Set-Cookie' => qr/mojolicious=/) + ->content_type_is('text/plain;charset=UTF-8')->content_is("Hello Mojo from a static file!\n"); + like $log, qr/Nothing has been rendered, expecting delayed response/, 'right message'; + $t->app->log->unsubscribe(message => $cb); +}; + +subtest 'Custom response' => sub { + $t->get_ok('/longpoll/dynamic')->status_is(201)->header_is(Server => 'Mojolicious (Perl)') + ->header_like('Set-Cookie' => qr/baz=yada/)->content_is('Dynamic!'); +}; + +subtest 'Chunked response streaming with drain event' => sub { + my $stash = undef; + $t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $t->get_ok('/stream')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_is('0123456789'); + is $stash->{subscribers}, 0, 'no leaking subscribers'; + ok $stash->{destroyed}, 'controller has been destroyed'; +}; + +subtest 'Rendering of template' => sub { + my $stash = undef; + $t->app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $t->get_ok('/render')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')->content_is('Finish!'); + Mojo::IOLoop->one_tick until $stash->{destroyed}; + ok !$stash->{writing}, 'finish event timing is right'; + ok $stash->{destroyed}, 'controller has been destroyed'; +}; + +subtest 'Request timeout' => sub { + my $tx = $t->ua->request_timeout(0.5)->get('/too_long'); + is $tx->error->{message}, 'Request timeout', 'right error'; + $t->ua->request_timeout(0); +}; + +subtest 'Inactivity timeout' => sub { + my $tx = $t->ua->inactivity_timeout(0.5)->get('/too_long'); + is $tx->error->{message}, 'Inactivity timeout', 'right error'; + $t->ua->inactivity_timeout(20); +}; done_testing();