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 )