API-Docker
view release on metacpan or search on metacpan
t/truncated_response.t view on Meta::CPAN
is $@, '', 'nothing raised' or diag "raised: $@";
is_deeply $got, { Id => 'abc' },
'the size is read past the extension and the chunks decode';
is_deeply \@warnings, [], 'and no hexadecimal-digit warning is emitted';
close $c->peer;
};
subtest 'a 204 and a zero-length body are not truncation' => sub {
my $c = client_for("HTTP/1.1 204 No Content\r\n\r\n");
my $got = eval { $c->post('/containers/abc/start') };
is $@, '', '204 with no headers at all' or diag "raised: $@";
is $got, undef, 'and undef comes back';
close $c->peer;
my $z = client_for("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
my $zgot = eval { $z->get('/probe', raw => 1) };
is $@, '', 'Content-Length: 0 with nothing after it'
or diag "raised: $@";
is $zgot, '', 'and the empty body comes back as the empty string';
close $z->peer;
};
subtest 'HEAD is not truncation, whatever its Content-Length says' => sub {
# A HEAD response repeats the headers the equivalent GET would send and
# sends no body. Reading one is not attempted, so there is nothing to be
# short of -- and the completeness check must not reintroduce the wait.
my %res;
my $c = client_for("HTTP/1.1 200 OK\r\nContent-Length: 13\r\n"
. "X-Docker-Container-Path-Stat: e30=\r\n\r\n");
my $got = eval { $c->head('/containers/abc/archive', response => \%res) };
is $@, '', 'a 13-byte announcement with no body raises nothing'
or diag "raised: $@";
is $res{headers}{'x-docker-container-path-stat'}, 'e30=',
'and the header carrying the payload is there';
close $c->peer;
};
# ---------------------------------------------------------------------------
# The one shape where an EOF is the end
# ---------------------------------------------------------------------------
subtest 'a close-delimited body ends at the close, as it must' => sub {
# attach, logs(follow), exec/start: neither Content-Length nor chunked, so
# the response announces no end and the close is the only one there is.
# Making this fatal would break every raw-stream endpoint in the
# distribution.
my $c = client_for("HTTP/1.1 200 OK\r\n"
. "Content-Type: application/vnd.docker.raw-stream\r\n\r\n"
. "\x01\x00\x00\x00\x00\x00\x00\x05hello");
my $got = eval { $c->get('/containers/abc/attach', raw => 1) };
is $@, '', 'nothing raised' or diag "raised: $@";
is $got, "\x01\x00\x00\x00\x00\x00\x00\x05hello",
'the bytes up to the close are the body';
close $c->peer;
};
subtest 'a close-delimited body cut mid-frame is still not truncation' => sub {
# Half a frame is a real problem, and it is not this one: nothing announced
# eight bytes of header plus five of payload, so the transport has no
# statement to compare it against. stream_frames says so at the framing
# level; the transport must not guess at it.
my $c = client_for("HTTP/1.1 200 OK\r\n\r\n\x01\x00\x00\x00\x00\x00\x00\x05he");
my $got = eval { $c->get('/containers/abc/attach', raw => 1) };
is $@, '', 'the transport raises nothing' or diag "raised: $@";
is length($got), 10, 'and hands over the bytes it got';
close $c->peer;
};
# ---------------------------------------------------------------------------
# Streaming: the caller already has the units, and still has to be told
# ---------------------------------------------------------------------------
subtest 'a chunked stream cut short croaks after delivering what arrived'
=> sub {
my @got;
my $c = client_for("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
. qq(11\r\n{"status":"one"}\n\r\n)
. qq(11\r\n{"status":"two"}\n\r\n)
. "20\r\n{\"status\":\"thr");
my $summary = eval {
$c->get('/events', croak_on_error => 0,
on_event => sub { push @got, $_[0] });
};
my $err = $@;
close $c->peer;
is $summary, undef, 'no summary came back';
isa_ok $err, 'API::Docker::Error::Truncated';
is $err && $err->phase, 'chunk-data', 'the chunk fell short of its header';
is_deeply [ map { $_->{status} } @got ], [qw( one two )],
'both complete events reached the callback first';
is_deeply $err && $err->summary, { delivered => 2, stopped => 0 },
'and the summary says so, exactly as the return value would have';
is $err && $err->partial, '',
'no body: a streamed request keeps none by design';
like $err && "$err", qr/2 units delivered/, 'the message says so too';
};
subtest 'a stream the callback stopped is never truncation' => sub {
# The rest of the response is unread because the caller said so. Every
# completeness check on the streaming path is guarded by that, and this is
# the assertion that keeps it guarded: without it every early $stop->()
# would come back as an exception.
my @got;
my $c = client_for("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
. qq(11\r\n{"status":"one"}\n\r\n)
. qq(11\r\n{"status":"two"}\n\r\n)
. "20\r\n{\"status\":\"thr");
my $summary = eval {
$c->get('/events', croak_on_error => 0, on_event => sub {
my ($event, $stop) = @_;
push @got, $event;
$stop->() if $event->{status} eq 'two';
});
};
my $err = $@;
close $c->peer;
is $err, '', 'the truncated tail of the response raises nothing'
or diag "raised: $err";
( run in 1.657 second using v1.01-cache-2.11-cpan-54e63673c56 )