From 80db8f4b5bdb30ce3683cb522eb30dcc81237a11 Mon Sep 17 00:00:00 2001 From: Andrew Grangaard Date: Sat, 31 Oct 2020 21:07:55 -0700 Subject: [PATCH] Convert websocket.t to use subtests --- t/mojo/websocket.t | 698 +++++++++++++++++++++++---------------------- 1 file changed, 359 insertions(+), 339 deletions(-) diff --git a/t/mojo/websocket.t b/t/mojo/websocket.t index 17fb483d1d..e29edf9b3b 100644 --- a/t/mojo/websocket.t +++ b/t/mojo/websocket.t @@ -10,11 +10,10 @@ use Mojo::Transaction::WebSocket; use Mojo::UserAgent; use Mojolicious::Lite; -# Max WebSocket size -{ +subtest "Max WebSocket size" => sub { local $ENV{MOJO_MAX_WEBSOCKET_SIZE} = 1024; is(Mojo::Transaction::WebSocket->new->max_websocket_size, 1024, 'right value'); -} +}; # Silence app->log->level('debug')->unsubscribe('message'); @@ -118,343 +117,364 @@ websocket '/timeout' => sub { shift->inactivity_timeout(0.25)->on(finish => sub { shift->stash->{finished}++ }); }; -# URL for WebSocket my $ua = app->ua; -my $res = $ua->get('/link')->result; -is $res->code, 200, 'right status'; -like $res->body, qr!ws://127\.0\.0\.1:\d+/!, 'right content'; - -# Plain HTTP request -$res = $ua->get('/early_start')->res; -is $res->code, 404, 'right status'; -like $res->body, qr/Page not found/, 'right content'; - -# Plain WebSocket -my ($stash, $result); -app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$ua->websocket( - '/' => sub { - my ($ua, $tx) = @_; - $tx->on(finish => sub { Mojo::IOLoop->stop }); - $tx->on(message => sub { shift->finish; $result = shift }); - $tx->send('test1'); - } -); -Mojo::IOLoop->start; -Mojo::IOLoop->one_tick until $stash->{finished}; -is $stash->{finished}, 1, 'finish event has been emitted once'; -like $result, qr!test1test2ws://127\.0\.0\.1:\d+/!, 'right result'; - -# Failed WebSocket connection -my ($code, $body, $ws); -$ua->websocket( - '/something/else' => sub { - my ($ua, $tx) = @_; - $ws = $tx->is_websocket; - $code = $tx->res->code; - $body = $tx->res->body; - Mojo::IOLoop->stop; - } -); -Mojo::IOLoop->start; -ok !$ws, 'not a WebSocket'; -is $code, 200, 'right status'; -ok $body =~ /^(\d+)failed!$/ && $1 == 30, 'right content'; - -# Server directly sends a message -$result = ''; -my ($established, $status, $msg); -$ua->websocket( - '/early_start' => sub { - my ($ua, $tx) = @_; - $established = $tx->established; - $tx->on( - finish => sub { - my ($tx, $code, $reason) = @_; - ($status, $msg) = ($code, $reason); - Mojo::IOLoop->stop; - } - ); - $tx->on( - message => sub { - my ($tx, $msg) = @_; - $result .= $msg; - $tx->send('test2'); - } - ); - } -); -Mojo::IOLoop->start; -ok $established, 'connection established'; -is $status, 1000, 'right status'; -is $msg, 'I ♥ Mojolicious!', 'right message'; -is $result, 'test0test2test1', 'right result'; - -# WebSocket connection gets closed very fast -$status = undef; -$ua->websocket( - '/early_finish' => sub { - my ($ua, $tx) = @_; - $tx->on(finish => sub { $status = [@_[1, 2]]; Mojo::IOLoop->stop }); - } -); -Mojo::IOLoop->start; -is $status->[0], 4000, 'right status'; -is $status->[1], 'kaboom', 'right message'; - -# Connection denied -($stash, $code, $ws) = (); -app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$ua->websocket( - '/denied' => sub { - my ($ua, $tx) = @_; - $ws = $tx->is_websocket; - $code = $tx->res->code; - Mojo::IOLoop->stop; - } -); -Mojo::IOLoop->start; -Mojo::IOLoop->one_tick until $stash->{finished}; -is $stash->{handshake}, 1, 'finish event has been emitted once for handshake'; -is $stash->{finished}, 1, 'finish event has been emitted once'; -ok !$ws, 'not a WebSocket'; -is $code, 403, 'right status'; - -# Subrequests -($stash, $code, $result) = (); -app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$ua->websocket( - '/subreq' => sub { - my ($ua, $tx) = @_; - $code = $tx->res->code; - $tx->on(message => sub { $result .= pop }); - $tx->on(finish => sub { Mojo::IOLoop->stop }); - } -); -Mojo::IOLoop->start; -Mojo::IOLoop->one_tick until $stash->{finished}; -is $stash->{finished}, 1, 'finish event has been emitted once'; -is $code, 101, 'right status'; -is $result, 'test0test1', 'right result'; - -# Concurrent subrequests -my $delay = Mojo::IOLoop->delay; -($code, $result) = (); -my ($code2, $result2); -my $end = $delay->begin; -$ua->websocket( - '/subreq' => sub { - my ($ua, $tx) = @_; - $code = $tx->res->code; - $tx->on( - message => sub { - my ($tx, $msg) = @_; - $result .= $msg; - $tx->finish if $msg eq 'test1'; - } - ); - $tx->on(finish => sub { $end->() }); - } -); -my $end2 = $delay->begin; -$ua->websocket( - '/subreq' => sub { - my ($ua, $tx) = @_; - $code2 = $tx->res->code; - $tx->on(message => sub { $result2 .= pop }); - $tx->on(finish => sub { $end2->() }); - } -); -$delay->wait; -is $code, 101, 'right status'; -is $result, 'test0test1', 'right result'; -is $code2, 101, 'right status'; -is $result2, 'test0test1', 'right result'; - -# Client-side drain callback -$result = ''; -my ($drain, $counter); -$ua->websocket( - '/echo' => sub { - my ($ua, $tx) = @_; - $tx->on(finish => sub { Mojo::IOLoop->stop }); - $tx->on( - message => sub { - my ($tx, $msg) = @_; - $result .= $msg; - $tx->finish if ++$counter == 2; - } - ); - $tx->send( - 'hi!' => sub { - shift->send('there!'); - $drain += @{Mojo::IOLoop->stream($tx->connection)->subscribers('drain')}; - } - ); - } -); -Mojo::IOLoop->start; -is $result, 'hi!there!', 'right result'; -is $drain, 1, 'no leaking subscribers'; - -# Server-side drain callback -$result = ''; -$counter = 0; -$ua->websocket( - '/double_echo' => sub { - my ($ua, $tx) = @_; - $tx->on(finish => sub { Mojo::IOLoop->stop }); - $tx->on( - message => sub { - my ($tx, $msg) = @_; - $result .= $msg; - $tx->finish if ++$counter == 2; - } - ); - $tx->send('hi!'); - } -); -Mojo::IOLoop->start; -is $result, 'hi!hi!', 'right result'; - -# Sending objects -$result = undef; -$ua->websocket( - '/trim' => sub { - my ($ua, $tx) = @_; - $tx->on(finish => sub { Mojo::IOLoop->stop }); - $tx->on(message => sub { shift->finish; $result = shift }); - $tx->send(b(' foo bar ')); - } -); -Mojo::IOLoop->start; -is $result, 'foo bar', 'right result'; - -# Promises -$result = undef; -$ua->websocket_p('/trim')->then(sub { - my $tx = shift; - my $promise = Mojo::Promise->new; - $tx->on(finish => sub { $promise->resolve }); - $tx->on(message => sub { shift->finish; $result = pop }); - $tx->send(' also works! '); - return $promise; -})->wait; -is $result, 'also works!', 'right result'; -$result = undef; -$ua->websocket_p('/foo')->then(sub { $result = 'test failed' })->catch(sub { $result = shift })->wait; -is $result, 'WebSocket handshake failed', 'right result'; -$result = undef; -$ua->websocket_p($ua->server->url->to_abs->scheme('wsss'))->then(sub { $result = 'test failed' }) +subtest "URL for WebSocket" => sub { + my $res = $ua->get('/link')->result; + is $res->code, 200, 'right status'; + like $res->body, qr!ws://127\.0\.0\.1:\d+/!, 'right content'; +}; + +subtest "Plain HTTP request" => sub { + my $res = $ua->get('/early_start')->res; + is $res->code, 404, 'right status'; + like $res->body, qr/Page not found/, 'right content'; +}; + +subtest "Plain WebSocket" => sub { + my ($stash, $result); + app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $ua->websocket( + '/' => sub { + my ($ua, $tx) = @_; + $tx->on(finish => sub { Mojo::IOLoop->stop }); + $tx->on(message => sub { shift->finish; $result = shift }); + $tx->send('test1'); + } + ); + Mojo::IOLoop->start; + Mojo::IOLoop->one_tick until $stash->{finished}; + is $stash->{finished}, 1, 'finish event has been emitted once'; + like $result, qr!test1test2ws://127\.0\.0\.1:\d+/!, 'right result'; +}; + +subtest "Failed WebSocket connection" => sub { + my ($code, $body, $ws); + $ua->websocket( + '/something/else' => sub { + my ($ua, $tx) = @_; + $ws = $tx->is_websocket; + $code = $tx->res->code; + $body = $tx->res->body; + Mojo::IOLoop->stop; + } + ); + Mojo::IOLoop->start; + ok !$ws, 'not a WebSocket'; + is $code, 200, 'right status'; + ok $body =~ /^(\d+)failed!$/ && $1 == 30, 'right content'; +}; + +subtest "Server directly sends a message" => sub { + my $result = ''; + my ($established, $status, $msg); + $ua->websocket( + '/early_start' => sub { + my ($ua, $tx) = @_; + $established = $tx->established; + $tx->on( + finish => sub { + my ($tx, $code, $reason) = @_; + ($status, $msg) = ($code, $reason); + Mojo::IOLoop->stop; + } + ); + $tx->on( + message => sub { + my ($tx, $msg) = @_; + $result .= $msg; + $tx->send('test2'); + } + ); + } + ); + Mojo::IOLoop->start; + ok $established, 'connection established'; + is $status, 1000, 'right status'; + is $msg, 'I ♥ Mojolicious!', 'right message'; + is $result, 'test0test2test1', 'right result'; +}; + +subtest "WebSocket connection gets closed very fast" => sub { + my $status = undef; + $ua->websocket( + '/early_finish' => sub { + my ($ua, $tx) = @_; + $tx->on(finish => sub { $status = [@_[1, 2]]; Mojo::IOLoop->stop }); + } + ); + Mojo::IOLoop->start; + is $status->[0], 4000, 'right status'; + is $status->[1], 'kaboom', 'right message'; +}; + +subtest "Connection denied" => sub { + my ($stash, $code, $ws) = (); + app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $ua->websocket( + '/denied' => sub { + my ($ua, $tx) = @_; + $ws = $tx->is_websocket; + $code = $tx->res->code; + Mojo::IOLoop->stop; + } + ); + Mojo::IOLoop->start; + Mojo::IOLoop->one_tick until $stash->{finished}; + is $stash->{handshake}, 1, 'finish event has been emitted once for handshake'; + is $stash->{finished}, 1, 'finish event has been emitted once'; + ok !$ws, 'not a WebSocket'; + is $code, 403, 'right status'; +}; + +subtest "Subrequests" => sub { + my ($stash, $code, $result) = (); + app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $ua->websocket( + '/subreq' => sub { + my ($ua, $tx) = @_; + $code = $tx->res->code; + $tx->on(message => sub { $result .= pop }); + $tx->on(finish => sub { Mojo::IOLoop->stop }); + } + ); + Mojo::IOLoop->start; + Mojo::IOLoop->one_tick until $stash->{finished}; + is $stash->{finished}, 1, 'finish event has been emitted once'; + is $code, 101, 'right status'; + is $result, 'test0test1', 'right result'; +}; + +subtest "Concurrent subrequests" => sub { + my $delay = Mojo::IOLoop->delay; + my ($code, $result) = (); + my ($code2, $result2); + my $end = $delay->begin; + $ua->websocket( + '/subreq' => sub { + my ($ua, $tx) = @_; + $code = $tx->res->code; + $tx->on( + message => sub { + my ($tx, $msg) = @_; + $result .= $msg; + $tx->finish if $msg eq 'test1'; + } + ); + $tx->on(finish => sub { $end->() }); + } + ); + my $end2 = $delay->begin; + $ua->websocket( + '/subreq' => sub { + my ($ua, $tx) = @_; + $code2 = $tx->res->code; + $tx->on(message => sub { $result2 .= pop }); + $tx->on(finish => sub { $end2->() }); + } + ); + $delay->wait; + is $code, 101, 'right status'; + is $result, 'test0test1', 'right result'; + is $code2, 101, 'right status'; + is $result2, 'test0test1', 'right result'; +}; + +subtest "Client-side drain callback" => sub { + my $result = ''; + my ($drain, $counter); + $ua->websocket( + '/echo' => sub { + my ($ua, $tx) = @_; + $tx->on(finish => sub { Mojo::IOLoop->stop }); + $tx->on( + message => sub { + my ($tx, $msg) = @_; + $result .= $msg; + $tx->finish if ++$counter == 2; + } + ); + $tx->send( + 'hi!' => sub { + shift->send('there!'); + $drain += @{Mojo::IOLoop->stream($tx->connection)->subscribers('drain')}; + } + ); + } + ); + Mojo::IOLoop->start; + is $result, 'hi!there!', 'right result'; + is $drain, 1, 'no leaking subscribers'; +}; + +subtest "Server-side drain callback" => sub { + my $result = ''; + my $counter = 0; + $ua->websocket( + '/double_echo' => sub { + my ($ua, $tx) = @_; + $tx->on(finish => sub { Mojo::IOLoop->stop }); + $tx->on( + message => sub { + my ($tx, $msg) = @_; + $result .= $msg; + $tx->finish if ++$counter == 2; + } + ); + $tx->send('hi!'); + } + ); + Mojo::IOLoop->start; + is $result, 'hi!hi!', 'right result'; +}; + +subtest "Sending objects" => sub { + my $result = undef; + $ua->websocket( + '/trim' => sub { + my ($ua, $tx) = @_; + $tx->on(finish => sub { Mojo::IOLoop->stop }); + $tx->on(message => sub { shift->finish; $result = shift }); + $tx->send(b(' foo bar ')); + } + ); + Mojo::IOLoop->start; + is $result, 'foo bar', 'right result'; +}; + +subtest "Promises" => sub { + my $result = undef; + $ua->websocket_p('/trim')->then(sub { + my $tx = shift; + my $promise = Mojo::Promise->new; + $tx->on(finish => sub { $promise->resolve }); + $tx->on(message => sub { shift->finish; $result = pop }); + $tx->send(' also works! '); + return $promise; + })->wait; + is $result, 'also works!', 'right result'; + $result = undef; + $ua->websocket_p('/foo')->then(sub { $result = 'test failed' })->catch(sub { $result = shift })->wait; + is $result, 'WebSocket handshake failed', 'right result'; + $result = undef; + $ua->websocket_p($ua->server->url->to_abs->scheme('wsss'))->then(sub { $result = 'test failed' }) ->catch(sub { $result = shift })->wait; -is $result, 'Unsupported protocol: wsss', 'right result'; - -# Dies -($ws, $code, $msg) = (); -my $finished; -$ua->websocket( - '/dead' => sub { - my ($ua, $tx) = @_; - $finished = $tx->is_finished; - $ws = $tx->is_websocket; - $code = $tx->res->code; - $msg = $tx->res->message; - Mojo::IOLoop->stop; - } -); -Mojo::IOLoop->start; -ok $finished, 'transaction is finished'; -ok !$ws, 'not a websocket'; -is $code, 500, 'right status'; -is $msg, 'Internal Server Error', 'right message'; - -# Forbidden -($ws, $code, $msg) = (); -$ua->websocket( - '/foo' => sub { - my ($ua, $tx) = @_; - $ws = $tx->is_websocket; - $code = $tx->res->code; - $msg = $tx->res->message; - Mojo::IOLoop->stop; - } -); -Mojo::IOLoop->start; -ok !$ws, 'not a websocket'; -is $code, 403, 'right status'; -is $msg, "i'm a teapot", 'right message'; - -# Connection close -$status = undef; -$ua->websocket( - '/close' => sub { - my ($ua, $tx) = @_; - $tx->on(finish => sub { $status = pop; Mojo::IOLoop->stop }); - $tx->send('test1'); - } -); -Mojo::IOLoop->start; -is $status, 1006, 'right status'; - -# Unsupported protocol -my $error; -$ua->websocket( - 'wsss://example.com' => sub { - my ($ua, $tx) = @_; - $error = $tx->error; - Mojo::IOLoop->stop; - } -); -Mojo::IOLoop->start; -is $error->{message}, 'Unsupported protocol: wsss', 'right error'; - -# 16-bit length -$result = undef; -$ua->websocket( - '/echo' => sub { - my ($ua, $tx) = @_; - $tx->on(finish => sub { Mojo::IOLoop->stop }); - $tx->on(message => sub { shift->finish; $result = shift }); - $tx->send('hi!' x 100); - } -); -Mojo::IOLoop->start; -is $result, 'hi!' x 100, 'right result'; - -# Timeout -my $log = ''; -$msg = app->log->on(message => sub { $log .= pop }); -$stash = undef; -app->plugins->once(before_dispatch => sub { $stash = shift->stash }); -$ua->websocket( - '/timeout' => sub { - pop->on(finish => sub { Mojo::IOLoop->stop }); - } -); -Mojo::IOLoop->start; -Mojo::IOLoop->one_tick until $stash->{finished}; -is $stash->{finished}, 1, 'finish event has been emitted once'; -like $log, qr/Inactivity timeout/, 'right log message'; -app->log->unsubscribe(message => $msg); - -# Ping/pong -my $pong; -$ua->websocket( - '/echo' => sub { - my ($ua, $tx) = @_; - $tx->on( - frame => sub { - my ($tx, $frame) = @_; - return unless $frame->[4] == 10; - $pong = $frame->[5]; - $tx->finish; - } - ); - $tx->on(finish => sub { Mojo::IOLoop->stop }); - $tx->send([1, 0, 0, 0, 9, 'test']); - } -); -Mojo::IOLoop->start; -is $pong, 'test', 'received pong with payload'; + is $result, 'Unsupported protocol: wsss', 'right result'; +}; + +subtest "Dies" => sub { + my ($ws, $code, $msg) = (); + my $finished; + $ua->websocket( + '/dead' => sub { + my ($ua, $tx) = @_; + $finished = $tx->is_finished; + $ws = $tx->is_websocket; + $code = $tx->res->code; + $msg = $tx->res->message; + Mojo::IOLoop->stop; + } + ); + Mojo::IOLoop->start; + ok $finished, 'transaction is finished'; + ok !$ws, 'not a websocket'; + is $code, 500, 'right status'; + is $msg, 'Internal Server Error', 'right message'; +}; + +subtest "Forbidden" => sub { + my ($ws, $code, $msg) = (); + $ua->websocket( + '/foo' => sub { + my ($ua, $tx) = @_; + $ws = $tx->is_websocket; + $code = $tx->res->code; + $msg = $tx->res->message; + Mojo::IOLoop->stop; + } + ); + Mojo::IOLoop->start; + ok !$ws, 'not a websocket'; + is $code, 403, 'right status'; + is $msg, "i'm a teapot", 'right message'; +}; + +subtest "Connection close" => sub { + my $status = undef; + $ua->websocket( + '/close' => sub { + my ($ua, $tx) = @_; + $tx->on(finish => sub { $status = pop; Mojo::IOLoop->stop }); + $tx->send('test1'); + } + ); + Mojo::IOLoop->start; + is $status, 1006, 'right status'; +}; + +subtest "Unsupported protocol" => sub { + my $error; + $ua->websocket( + 'wsss://example.com' => sub { + my ($ua, $tx) = @_; + $error = $tx->error; + Mojo::IOLoop->stop; + } + ); + Mojo::IOLoop->start; + is $error->{message}, 'Unsupported protocol: wsss', 'right error'; +}; + +subtest "16-bit length" => sub { + my $result = undef; + $ua->websocket( + '/echo' => sub { + my ($ua, $tx) = @_; + $tx->on(finish => sub { Mojo::IOLoop->stop }); + $tx->on(message => sub { shift->finish; $result = shift }); + $tx->send('hi!' x 100); + } + ); + Mojo::IOLoop->start; + is $result, 'hi!' x 100, 'right result'; +}; + +subtest "Timeout" => sub { + my $log = ''; + my $msg = app->log->on(message => sub { $log .= pop }); + my $stash = undef; + app->plugins->once(before_dispatch => sub { $stash = shift->stash }); + $ua->websocket( + '/timeout' => sub { + pop->on(finish => sub { Mojo::IOLoop->stop }); + } + ); + Mojo::IOLoop->start; + Mojo::IOLoop->one_tick until $stash->{finished}; + is $stash->{finished}, 1, 'finish event has been emitted once'; + like $log, qr/Inactivity timeout/, 'right log message'; + + app->log->unsubscribe(message => $msg); +}; + +subtest "Ping/pong" => sub { + my $pong; + $ua->websocket( + '/echo' => sub { + my ($ua, $tx) = @_; + $tx->on( + frame => sub { + my ($tx, $frame) = @_; + return unless $frame->[4] == 10; + $pong = $frame->[5]; + $tx->finish; + } + ); + $tx->on(finish => sub { Mojo::IOLoop->stop }); + $tx->send([1, 0, 0, 0, 9, 'test']); + } + ); + Mojo::IOLoop->start; + is $pong, 'test', 'received pong with payload'; +}; done_testing();