API-Docker
view release on metacpan or search on metacpan
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 )