API-Docker

 view release on metacpan or  search on metacpan

t/type.t  view on Meta::CPAN


subtest 'an unknown field goes in and comes out unchanged' => sub {
  my %newer = (
    FieldFromANewerEngine => 'scalar',
    NestedNewThing        => { a => [ 1, 2, { b => 'c' } ] },
    lowercase_new_thing   => 42,
  );
  my $hc = API::Docker::Type::HostConfig->from_data({ Privileged => JSON->true, %newer });
  is_deeply($hc->unknown_fields, { %newer },
    'everything unrecognised is kept verbatim, under its own name');
  my $out = $hc->TO_JSON;
  is_deeply({ map { ($_ => $out->{$_}) } keys %newer }, { %newer },
    'and is written back out byte for byte');
  is($out->{Privileged}, JSON->true, 'the fields we do know still translate');

  my $round = API::Docker::Type::HostConfig->from_data($out);
  is($json->encode($round->TO_JSON), $json->encode($out),
    'a second pass through the model changes nothing');

  my $unknown_only = API::Docker::Type::Mount->from_data({ Whatever => 1 });
  is_deeply($unknown_only->TO_JSON, { Whatever => 1 },
    'a class that recognises nothing still forwards everything');
};

# ---------------------------------------------------------------------------
# INVARIANT 3: `since` is documentation. Nothing checks it, warns about it
# or drops a field because of it -- we are not the authority on what an
# engine can do (karr k79, decision 2).
# ---------------------------------------------------------------------------

subtest 'since is inert at runtime' => sub {
  my $reg = API::Docker::Type::Mount->docker_attributes;
  is($reg->{image_options}{since}, '1.51',
    'the registry records which spec first carried the field');
  is($reg->{target}{since}, undef,
    'a field already in the oldest spec we hold carries no since');

  my $warned = '';
  local $SIG{__WARN__} = sub { $warned .= $_[0] };
  my $mount = API::Docker::Type::Mount->from_data({
    Target       => '/data',
    ImageOptions => { Subpath => 'dir/sub' },
  });
  is($warned, '', 'setting a field newer than any engine we know warns about nothing');
  is($mount->image_options->subpath, 'dir/sub', 'and the value is kept');
  is_deeply($mount->TO_JSON->{ImageOptions}, { Subpath => 'dir/sub' },
    'and it goes to the daemon; the engine decides, not us');

  my $bind = API::Docker::Type::Mount::BindOptions->new(create_mountpoint => 1);
  is_deeply($bind->TO_JSON, { CreateMountpoint => JSON->true },
    'a 1.44 field on a 1.41 engine is still sent -- Podman serves fields its '
      . 'announced version does not promise, and refuses ones it does');
};

# ---------------------------------------------------------------------------
# The wire name is derived from the Perl name, and the spec's spelling wins
# ---------------------------------------------------------------------------

subtest 'wire names' => sub {
  my $reg = API::Docker::Type::Resources->docker_attributes;
  is($reg->{cpu_shares}{wire},  'CpuShares',  'port_bindings-style derivation');
  is($reg->{nano_cpus}{wire},   'NanoCpus',   'derivation covers most fields');
  is($reg->{oom_kill_disable}{wire}, 'OomKillDisable', 'and multi-word ones');
  is($reg->{kernel_memory_tcp}{wire}, 'KernelMemoryTCP',
    'a spelling the derivation cannot reach is declared explicitly');
  is($reg->{io_maximum_iops}{wire}, 'IOMaximumIOps', 'likewise IOMaximumIOps');
  is($reg->{blkio_device_read_iops}{wire}, 'BlkioDeviceReadIOps',
    'likewise BlkioDeviceReadIOps');
  is(API::Docker::Type::Port->docker_attributes->{ip}{wire}, 'IP',
    'and a two-letter all-caps name');
  is(API::Docker::Type::HostConfig->docker_attributes->{uts_mode}{wire}, 'UTSMode',
    'and UTSMode');

  my $r = API::Docker::Type::Resources->new(
    kernel_memory_tcp => 1024, io_maximum_iops => 7);
  is_deeply($r->TO_JSON, { KernelMemoryTCP => 1024, IOMaximumIOps => 7 },
    'the explicit spelling is what goes on the wire');
  my $back = API::Docker::Type::Resources->from_data({ KernelMemoryTCP => 1024 });
  is($back->kernel_memory_tcp, 1024, 'and what is read back off it');
  is_deeply($back->unknown_fields, {},
    'a wire name the registry knows is never mistaken for an unknown field');
};

# ---------------------------------------------------------------------------
# Booleans: Docker tells an absent flag apart from a false one
# ---------------------------------------------------------------------------

subtest 'booleans' => sub {
  for my $false (JSON->false, \0, 'false', 'FALSE', 0, '') {
    my $m = API::Docker::Type::Mount->new(read_only => $false);
    is($m->TO_JSON->{ReadOnly}, JSON->false,
      'false-ish value ' . (ref $false ? ref $false : "'$false'") . ' serialises to JSON false');
  }
  for my $true (JSON->true, \1, 'true', 1, 'yes') {
    my $m = API::Docker::Type::Mount->new(read_only => $true);
    is($m->TO_JSON->{ReadOnly}, JSON->true,
      'true-ish value ' . (ref $true ? ref $true : "'$true'") . ' serialises to JSON true');
  }
  my $unset = API::Docker::Type::Mount->new(target => '/x');
  ok(!exists $unset->TO_JSON->{ReadOnly},
    'a Bool that was never set is absent, not false');
  my $explicit = API::Docker::Type::Mount->new(target => '/x', read_only => 0);
  ok(exists $explicit->TO_JSON->{ReadOnly},
    'a Bool explicitly set to false is present and false');
  is($json->encode($explicit->TO_JSON), '{"ReadOnly":false,"Target":"/x"}',
    'and encodes as JSON false, never as 1 or the empty string');
  like(
    do { local $@; eval { API::Docker::Type::Mount->new(read_only => [1]) }; $@ },
    qr/Bool wants a scalar/,
    'something that cannot mean true or false croaks instead of being guessed at');
};

# ---------------------------------------------------------------------------
# Nesting, arrays of objects, and the allOf inheritance
# ---------------------------------------------------------------------------

subtest 'nested objects and arrays' => sub {
  my $hc = API::Docker::Type::HostConfig->from_data({
    RestartPolicy       => { Name => 'on-failure', MaximumRetryCount => 3 },
    LogConfig           => { Type => 'json-file', Config => { 'max-size' => '10m' } },
    Mounts              => [ { Target => '/data', Type => 'volume' } ],



( run in 2.814 seconds using v1.01-cache-2.11-cpan-7e1922ba9f2 )