API-Docker

 view release on metacpan or  search on metacpan

t/images_commit.t  view on Meta::CPAN

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use FindBin;
use lib "$FindBin::Bin/lib";
use Test::API::Docker::Mock;
use JSON::MaybeXS qw( decode_json );
use API::Docker;

# karr k22 -- images->commit, POST /commit. Turns a container into an image:
# the one image-producing path that does not go through a build context.
#
# Everything asserted about the engine here was measured against the rootless
# Podman socket (5.4.2, API 1.41): 201 Created with {"Id":"<64 hex>"} and no
# sha256: prefix, `changes` accepted both newline-joined and as repeated
# query pairs with the same result, and a ContainerConfig body whose Labels
# and Cmd replace the container's while Env is merged onto the inherited one.

check_live_access();

# ---------------------------------------------------------------------------
# See t/images_tar.t for why this exists: the mock harness replaces _request
# wholesale, so the request line and body it assembles are only reachable
# through a client whose socket is an in-memory sink.
package Test::ImagesCommit::FakeTransport;
use Moo;
extends 'API::Docker';

has canned => (is => 'rw', default => sub { [201, 'Created', {}, '{"Id":"deadbeef"}'] });
has _sink  => (is => 'rw');

sub _build__socket {
  my ($self) = @_;
  my $sink = '';
  $self->_sink(\$sink);
  open my $fh, '>', \$sink or die "open: $!";
  return $fh;
}

sub _read_response { return $_[0]->canned }

sub written { return ${ $_[0]->_sink } }

package main;

sub fake_client {
  return Test::ImagesCommit::FakeTransport->new(
    host        => 'unix:///nonexistent.sock',
    api_version => '1.41',
  );
}

sub request_line {
  my ($t) = @_;
  my ($line) = $t->written =~ /\A(\S+ [^\r\n]+) HTTP\/1\.1\r\n/;
  return $line;
}

# ---------------------------------------------------------------------------
subtest 'commit: returns the daemon response and requires a container' => sub {
  plan skip_all => 'route assertions are fixture-only' if is_live();

  my %seen;
  my $docker = test_docker(
    'POST /commit' => sub {
      my ($method, $path, %opts) = @_;
      %seen = %opts;
      return { Id => 'ae3996fdfd897ec18aa3eaa07e095418dda8f19902e43a2761373399f13132b3' };
    },
  );

  my $result = $docker->images->commit(
    container => 'c0ffee',
    repo      => 'myapp',
    tag       => 'snapshot',
    comment   => 'after the migration ran',
    author    => 'Jane <jane@example.com>',
  );

  is ref $result, 'HASH', 'the raw daemon response, not an entity object';
  like $result->{Id}, qr/^[0-9a-f]{64}$/,
    'Podman answers a bare hex digest with no sha256: prefix';

  is_deeply $seen{params}, {
    container => 'c0ffee',
    repo      => 'myapp',
    tag       => 'snapshot',
    comment   => 'after the migration ran',
    author    => 'Jane <jane@example.com>',
  }, 'every option went to the query string';
  ok !exists $seen{body}, 'and no body was sent without a config';

  my $err = do { local $@; eval { $docker->images->commit(repo => 'myapp') }; $@ };
  like $err, qr/container required/, 'a missing container croaks';
};

# ---------------------------------------------------------------------------
subtest 'commit: the options ride in the query string' => sub {
  my $t = fake_client();
  $t->images->commit(container => 'c0ffee', repo => 'myapp', tag => 'v2');

  is request_line($t),
    'POST /v1.41/commit?container=c0ffee&repo=myapp&tag=v2',
    'container, repo and tag on the wire, sorted by the transport';
};

subtest 'commit: pause is normalised to 1/0, not passed through' => sub {



( run in 0.889 second using v1.01-cache-2.11-cpan-364913b4093 )