API-Docker

 view release on metacpan or  search on metacpan

t/lib/Test/API/Docker/Mock.pm  view on Meta::CPAN

package Test::API::Docker::Mock;
use strict;
use warnings;
use JSON::MaybeXS qw( decode_json encode_json );
use Path::Tiny;
use Carp qw( croak shortmess );
use API::Docker::Error::HTTP;
use Test::More;

use Exporter 'import';
our @EXPORT = qw(
  test_docker
  load_fixture
  load_fixture_raw
  mock_response
  is_live
  can_write
  skip_unless_write
  check_live_access
  register_cleanup
  live_engine
);

my $FIXTURES_DIR = path(__FILE__)->parent->parent->parent->parent->parent->child('fixtures');

my @_cleanups;

sub load_fixture {
  my ($name) = @_;
  my $file = $FIXTURES_DIR->child("$name.json");
  croak "Fixture not found: $file" unless $file->exists;
  return decode_json($file->slurp_utf8);
}

# Some fixtures are not JSON: the framed log/exec streams are captured
# engine bytes, and the build/pull event streams are newline-delimited JSON
# whose line framing is the thing under test. Both must come back byte-exact.
sub load_fixture_raw {
  my ($name) = @_;
  my $file = $FIXTURES_DIR->child($name);
  croak "Fixture not found: $file" unless $file->exists;
  return $file->slurp_raw;
}

# The status line and the response headers reach a caller through the
# `response` out-parameter of _request, which the mock replaces wholesale --
# so without this a mocked route cannot say 304, and the very distinction
# API::Docker::API::Containers/start now makes would be untestable offline.
# A plain route keeps working and gets a status inferred from its value.
#
# The error phrases are here because API::Docker::Error::HTTP carries
# `reason` as well: a mocked 404 falling through to 'Unknown' would put a
# value on the exception that no engine ever sends.
my %REASON = (
  200 => 'OK',
  204 => 'No Content',
  304 => 'Not Modified',
  400 => 'Bad Request',
  401 => 'Unauthorized',
  403 => 'Forbidden',
  404 => 'Not Found',
  409 => 'Conflict',
  500 => 'Internal Server Error',
);

sub mock_response {
  my (%args) = @_;
  my $status = $args{status} // 200;
  # _read_response lowercases every header name it collects; a mock that kept
  # the wire capitalisation would let a test pass against a key the real
  # transport never produces.
  my %headers = map { lc($_) => $args{headers}{$_} } keys %{ $args{headers} || {} };
  return bless {
    status  => $status,
    reason  => $args{reason} // $REASON{$status} // 'Unknown',
    headers => \%headers,
    data    => $args{data},
    stream  => $args{stream},
  }, 'Test::API::Docker::Mock::Response';
}

# The units a route hands to an on_event/on_frame/on_chunk callback. A route
# that says nothing gets them inferred from its data, so an existing ndjson
# route -- whose value is already the ArrayRef of events -- streams without
# being rewritten; `stream => [...]` is for a route whose buffered value and
# whose stream units differ, and for one that has to deliver more units than
# its return value has elements.
sub _mock_stream_units {
  my ($response) = @_;

  return $response->{stream} if defined $response->{stream};
  return [] unless defined $response->{data};
  return $response->{data} if ref $response->{data} eq 'ARRAY';
  return [ $response->{data} ];
}

# The mock replaces _request wholesale, so the callback path exists here only
# because it is written here too. It is the transport's contract and not a
# second one: one unit per call, a $stop closure as the second argument, the
# return value ignored, and the summary HashRef back.
sub _mock_stream {
  my ($cb, $response) = @_;

  my $units     = _mock_stream_units($response);
  my $delivered = 0;
  my $stopped   = 0;
  my $stop      = sub { $stopped = 1; return };



( run in 0.671 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )