API-Docker
view release on metacpan or search on metacpan
t/lib/Test/API/Docker/Mock.pm view on Meta::CPAN
eval { $cleanup->() };
warn "Cleanup failed: $@" if $@;
}
@_cleanups = ();
}
sub test_docker {
my (%routes) = @_;
if (is_live()) {
require API::Docker;
return API::Docker->new(host => $ENV{API_DOCKER_TEST_HOST});
}
return _mock_docker(%routes);
}
sub _mock_docker {
my (%routes) = @_;
unless (grep { /version/ } keys %routes) {
$routes{'GET /version'} = load_fixture('system_version');
}
require API::Docker;
# Pinned rather than left to negotiate: setting api_version here means
# negotiate_version's own guard ("return if defined $self->api_version",
# API::Docker.pm) short-circuits for every mocked client, so this number
# is never actually read off the injected 'GET /version' route -- it is
# read back as-is by anything that asserts $docker->api_version (only
# t/version.t does). Kept equal to t/fixtures/system_version.json's own
# ApiVersion (karr k101: Docker Engine Community 29.7.2, API 1.55) so the
# two do not silently drift apart the way they did when the fixture was
# hand-rolled at '1.47' to match this literal instead of the other way
# round.
my $docker = API::Docker->new(
host => 'unix:///var/run/docker.sock',
api_version => '1.55',
);
my $mock_request = sub {
my ($self, $method, $path, %opts) = @_;
my $clean_path = $path;
$clean_path =~ s{^/v[\d.]+}{};
my $key = "$method $clean_path";
my $handler;
my $matched = 0;
if (exists $routes{$key}) {
$handler = $routes{$key};
$matched = 1;
}
else {
for my $pattern (keys %routes) {
my ($route_method, $route_path) = split /\s+/, $pattern, 2;
next unless $method eq $route_method;
# \Q...\E: $route_path is a literal path, not a regex a test author
# wrote on purpose -- interpolated unescaped, a route key containing
# a regex metacharacter (`.`, `?`, `+`, `(` ...) was read as a
# pattern rather than as the literal string it looks like, and could
# match a different path than the one it was registered for (or
# croak on an unbalanced `(`). This tier still tolerates only
# whitespace between method and path -- the exact-match branch above
# is the fast path for everything else. See t/mock_harness.t.
next unless $clean_path =~ m{^\Q$route_path\E$};
$handler = $routes{$pattern};
$matched = 1;
last;
}
}
croak "No mock route for: $key (available: " . join(', ', sort keys %routes) . ")"
unless $matched;
# A mocked request can never time out -- nothing here reads a socket, and
# nothing here opens one -- so read_timeout and connect_timeout are both
# accepted and ignored, exactly as a route ignores every other transport
# option. What is not ignored is their shape: that is part of the contract
# this stands in for, and a value the real transport would refuse has to
# fail here rather than only once a test is run live. The role's own
# checks are called rather than copied, so the two cannot drift.
$self->_read_timeout_value($opts{read_timeout})
if exists $opts{read_timeout};
$self->_connect_timeout_value($opts{connect_timeout})
if exists $opts{connect_timeout};
my $result = ref $handler eq 'CODE'
? $handler->($method, $clean_path, %opts)
: $handler;
# A route that says nothing about the status gets the one the daemon
# would have sent for that body: 200 with one, 204 without. A route built
# with mock_response() carries its own status and headers.
my $response = ref $result eq 'Test::API::Docker::Mock::Response'
? $result
: mock_response(status => (defined $result ? 200 : 204), data => $result);
if (my $out = $opts{response}) {
%$out = (
status => $response->{status},
reason => $response->{reason},
headers => $response->{headers},
);
}
# Filled above, croaked below -- the same order _request keeps, so an
# eval-ing caller can still read the status of a mocked failure. A >= 400
# route never reaches a streaming callback either: the real transport
# reads such a body whole before the croak (_read_streaming_response
# returns it with no summary), so the callback is not invoked here.
_mock_croak($response->{status}, $response->{reason}, $response->{data})
if $response->{status} >= 400;
my @streaming = grep { exists $opts{$_} } qw( on_event on_frame on_chunk );
croak "Mock route $key got more than one of on_event, on_frame, on_chunk: "
. join(' and ', @streaming) if @streaming > 1;
return _mock_stream($opts{$streaming[0]}, $response) if @streaming;
( run in 1.161 second using v1.01-cache-2.11-cpan-54e63673c56 )