API-Docker

 view release on metacpan or  search on metacpan

t/truncated_response.t  view on Meta::CPAN

  cmp_ok length($got[0]), '<', 20,
    'and it is less than the whole chunk, so the reader really did stop '
    . 'short of what the chunk header announced';
  is_deeply $res && $res->[4], { delivered => 1, stopped => 1 },
    'the summary says the callback ended it, not the daemon';
};

subtest 'a content-length body the callback stopped inside is not truncation'
  => sub {
  my @got;
  my $fh = stepped("HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n"
    . ('x' x 20), 5);
  my $handler = $client->_stream_handler('GET /v1.41/probe', 'on_chunk',
    sub { push @got, $_[0]; $_[1]->() }, 0);

  my $res = eval {
    $client->_read_streaming_response($fh, 'GET', $handler, {}) };
  is $@, '', 'stopping inside the body raises nothing' or diag "raised: $@";
  is scalar @got, 1, 'one unit reached the callback';
  cmp_ok length($got[0]), '<', 20, 'short of the announced length';
  is_deeply $res && $res->[4], { delivered => 1, stopped => 1 },
    'and the summary says who ended it';
};

subtest 'a close-delimited stream is not truncated by its close' => sub {
  my @got;
  my $c = client_for("HTTP/1.1 200 OK\r\n\r\n"
    . "\x01\x00\x00\x00\x00\x00\x00\x05hello");

  my $summary = eval {
    $c->get('/containers/abc/attach', on_frame => sub { push @got, $_[0] });
  };
  is $@, '', 'nothing raised' or diag "raised: $@";
  is_deeply $summary, { delivered => 1, stopped => 0 }, 'one frame delivered';
  is_deeply \@got, [{ stream => 'stdout', data => 'hello' }], 'and decoded';
  close $c->peer;
};

# ---------------------------------------------------------------------------
# What the exception is
# ---------------------------------------------------------------------------
subtest 'the exception behaves like the others' => sub {
  my ($err) = over_pair(
    "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nhello wor",
    sub { $client->_read_response($_[0], 'GET', { endpoint => 'GET /v1.41/x' }) });

  isa_ok $err, 'API::Docker::Error::Truncated';
  ok overload::Overloaded($err),
    'stringification is in place -- namespace::clean before use overload';
  unlike "$err", qr/=HASH\(0x/,
    'stringifies to the reason, not to a reference address';
  like "$err", qr{\QGET /v1.41/x\E}, 'the request is named in the string';
  like "$err", qr/ at \S+ line \d+/, "with Carp's own location suffix";
  ok !!$err, 'and it is true as an exception';
  is $err->as_string, "$err", 'as_string is what the overload returns';
};

subtest 'a reader driven with no context still names the phase' => sub {
  # t/role_http.t drives the readers directly with no request behind them, so
  # there is no endpoint to name. The message leaves it out rather than
  # interpolating an empty pair of parentheses.
  my ($err) = over_pair(
    "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nhello wor",
    sub { $client->_read_response($_[0], 'GET', {}) });

  isa_ok $err, 'API::Docker::Error::Truncated';
  is $err && $err->endpoint, '', 'no endpoint';
  like $err && "$err", qr/\ADocker API response truncated: /,
    'and the message goes straight to the reason';
};

subtest 'nothing at all is said so rather than reported as zero bytes' => sub {
  my ($err) = over_pair("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\n",
    sub { $client->_read_response($_[0], 'GET', {}) });

  isa_ok $err, 'API::Docker::Error::Truncated';
  is $err && $err->partial, '', 'partial is empty';
  is $err && $err->received, 0, 'and nothing was received';
  like $err && "$err", qr/nothing arrived at all/, 'the message says so';
};

subtest 'a >= 400 body that is itself cut short is reported as truncation'
  => sub {
  # The rule the read timeout already follows, applied to the same place: the
  # transport cannot tell a caller what the engine said when it did not finish
  # saying it. The price is that a 404 whose body the daemon cut off arrives
  # as this class rather than as API::Docker::Error::HTTP, so ->status is not
  # there -- which is the honest answer, the status line having been the only
  # complete part of a response that was not.
  my $c = client_for(sized('{"message":"no such contain',
    announce => 40, status => '404 Not Found'));

  my %res;
  my $got = eval { $c->get('/containers/abc/json', response => \%res) };
  my $err = $@;
  close $c->peer;

  is $got, undef, 'nothing came back';
  isa_ok $err, 'API::Docker::Error::Truncated';
  ok !$err->isa('API::Docker::Error::HTTP'),
    'and it is not the status error, which would have claimed a whole body';
  is $err && $err->partial, '{"message":"no such contain',
    'the part of the error body that did arrive is carried';
  is_deeply \%res, {},
    'the response out-parameter is untouched: _request never got that far';
};

done_testing;



( run in 0.945 second using v1.01-cache-2.11-cpan-80ec619307d )