PAGI-Server
view release on metacpan or search on metacpan
t/sse-decline.t view on Meta::CPAN
like($wire, qr/No such stream/, 'decline body delivered');
unlike($wire, qr{text/event-stream}, 'NOT an event stream');
ok($eof, 'connection closed');
$server->shutdown->get;
};
subtest '204 decline (stop-reconnect)' => sub {
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $send->({ type => 'sse.http.response.start', status => 204, headers => [] });
await $send->({ type => 'sse.http.response.body', body => '', more => 0 });
};
my $server = create_server($app);
my ($wire, $eof) = sse_get($server->port);
like($wire, qr{HTTP/1\.1 204}, '204 No Content');
ok($eof, 'connection closed');
$server->shutdown->get;
};
subtest 'first-send-wins: stream after decline, and decline after stream, raise' => sub {
my ($after_decline_raised, $after_start_raised) = (0, 0);
my $app1 = async sub {
my ($scope, $receive, $send) = @_;
await $send->({ type => 'sse.http.response.start', status => 404, headers => [] });
eval { await $send->({ type => 'sse.send', data => 'x' }); 1 } or $after_decline_raised = 1;
await $send->({ type => 'sse.http.response.body', body => '', more => 0 });
};
my $s1 = create_server($app1);
sse_get($s1->port);
ok($after_decline_raised, 'sse.send after sse.http.response.start raised');
$s1->shutdown->get;
my $app2 = async sub {
my ($scope, $receive, $send) = @_;
await $send->({ type => 'sse.start', status => 200 });
eval { await $send->({ type => 'sse.http.response.start', status => 404, headers => [] }); 1 }
or $after_start_raised = 1;
};
my $s2 = create_server($app2);
sse_get($s2->port);
ok($after_start_raised, 'sse.http.response.start after sse.start raised');
$s2->shutdown->get;
};
subtest 'a completed decline delivers no sse.disconnect to a later receive() call' => sub {
# Per the PAGI spec, a decline delivers no events at all. An app that
# calls receive() again after its decline response has fully completed
# (before it returns) must not observe a synthesized sse.disconnect --
# that would look exactly like an abnormal end that never happened.
my ($settled, $event);
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $send->({ type => 'sse.http.response.start', status => 404, headers => [] });
await $send->({ type => 'sse.http.response.body', body => 'nope', more => 0 });
# Decline is now complete. Call receive() once more, WITHOUT
# awaiting it directly (an unresolved Future would hang this
# coroutine forever under the fix) -- just observe, after a bounded
# wait on the same loop, whether it ever resolved and with what.
my $future = $receive->();
await $loop->delay_future(after => 0.3);
$settled = $future->is_ready;
$event = $future->is_ready ? $future->get : undef;
};
my $server = create_server($app);
my ($wire, $eof) = sse_get($server->port);
like($wire, qr{HTTP/1\.1 404}, 'decline delivered as usual');
# Give the app's post-decline receive() its full bounded window to settle.
$loop->loop_once(0.05) for 1 .. 10;
if ($settled) {
isnt($event->{type}, 'sse.disconnect',
'a resolved receive() did not synthesize sse.disconnect');
}
else {
ok(!$settled, 'receive() after a completed decline stayed pending (no event synthesized)');
}
$server->shutdown->get;
};
done_testing;
( run in 2.395 seconds using v1.01-cache-2.11-cpan-364913b4093 )