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 )