API-Docker

 view release on metacpan or  search on metacpan

t/entity_roles.t  view on Meta::CPAN

use Test::More;
use lib 't/lib';
use Test::API::Docker::Mock;
use API::Docker::Role::Entity::Config;
use API::Docker::Role::Entity::Image;
use API::Docker::Role::Entity::Network;
use API::Docker::Role::Entity::Plugin;
use API::Docker::Role::Entity::Secret;
use API::Docker::Role::Entity::Volume;

# karr k84, the six resources k79 step 6/7 still owed after containers:
# Image, Network, Volume, Plugin, Secret, Config. The convenience methods live
# in API::Docker::Role::Entity::<Resource> and are composed into the generated
# classes the daemon answers with. Nothing may be written into those classes
# by hand -- maint/spec-to-type.pl --verify compares them against the swagger
# byte for byte (t/spec_to_type.t) -- so this file is what proves the methods
# arrive anyway.
#
# t/entity_container.t is the same file for containers, and stays separate:
# containers have nineteen methods and two shapes that disagree in ways worth
# asserting one by one. This file carries the invariants all six share, and
# each resource's own forwarding assertions stay in its own test file
# (t/images.t, t/networks.t, t/volumes.t, t/plugins.t, t/secrets_configs.t).
#
# Fixture-only throughout: none of it needs a daemon, and the assertions are
# about the model rather than about anything an engine answers.

check_live_access();

# Which generated classes each role belongs on, and the field the role
# addresses the resource by. Written out rather than derived from the roles,
# so a class quietly dropping out of a composition loop is a failure here and
# not a shorter list compared against itself.
#
# Two of the six answer list and inspect with two different definitions;
# four answer both with one. Read off spec/v1.51.yaml, not assumed:
#   /images/json      -> [ImageSummary]   /images/{name}/json -> ImageInspect
#   /networks         -> [Network]        /networks/{id}      -> Network
#   /volumes          -> VolumeListResponse{Volumes: [Volume]}
#                                         /volumes/{name}     -> Volume
#   /plugins          -> [Plugin]         /plugins/{name}/json-> Plugin
#   /secrets          -> [Secret]         /secrets/{id}       -> Secret
#   /configs          -> [Config]         /configs/{id}       -> Config
my @RESOURCES = (
  { role    => 'API::Docker::Role::Entity::Image',
    classes => [qw( API::Docker::Type::ImageSummary
                    API::Docker::Type::ImageInspect )],
    key     => 'id',
    methods => [qw( history inspect remove tag )] },
  { role    => 'API::Docker::Role::Entity::Network',
    classes => ['API::Docker::Type::Network'],
    key     => 'id',
    methods => [qw( connect disconnect inspect remove )] },
  { role    => 'API::Docker::Role::Entity::Volume',
    classes => ['API::Docker::Type::Volume'],
    key     => 'name',
    methods => [qw( inspect remove )] },
  { role    => 'API::Docker::Role::Entity::Plugin',
    classes => ['API::Docker::Type::Plugin'],
    key     => 'name',
    methods => [qw( configure disable enable inspect push remove upgrade )] },
  { role    => 'API::Docker::Role::Entity::Secret',
    classes => ['API::Docker::Type::Secret'],
    key     => 'id',
    methods => [qw( inspect remove update version_index )] },
  { role    => 'API::Docker::Role::Entity::Config',
    classes => ['API::Docker::Type::Config'],
    key     => 'id',
    methods => [qw( decoded_data inspect remove update version_index )] },
);

subtest 'every entity role reaches the generated classes of its resource' => sub {
  for my $r (@RESOURCES) {
    for my $class (@{ $r->{classes} }) {
      ok $class->does($r->{role}), "$class does $r->{role}";
      ok $class->does('API::Docker::Role::Entity'),
        "$class does the shared entity role with it";
      my @missing = grep { !$class->can($_) } @{ $r->{methods} };
      is_deeply \@missing, [], "$class can every convenience method";
      ok $class->can($r->{key}),
        "$class has the $r->{key} the role addresses it by";
    }
  }
};

# Moo composes a role into a class the class-wins way, so a generated accessor
# of the same name as a role method keeps its place silently and the method is
# simply missing. The composition block in each role croaks on that at load
# time; this asserts the condition it checks actually holds, which is the part
# a future swagger can break without anyone touching this distribution.
subtest 'no role method is also a daemon field of the class it lands on' => sub {
  for my $r (@RESOURCES) {
    for my $class (@{ $r->{classes} }) {
      my $fields = $class->docker_attributes;
      my @clash = sort grep { $fields->{$_} } @{ $r->{methods} };
      is_deeply \@clash, [],
        "$class declares none of $r->{role}'s method names as a field";
    }
  }
};

# The defect the whole mechanism had to be designed around, and the reason
# API::Docker::Role::Type grew _entity_attribute_index: without the hook the
# client landed in unknown_fields and to_json died trying to encode the client
# object into a request body. Asserted for containers in t/entity_container.t;
# this is the same claim for the other six, because the hook is per-role.
subtest 'the client is an attribute, not a field the daemon sent' => sub {
  plan skip_all => 'fixture-only' if is_live();

  my $docker = test_docker();

  for my $r (@RESOURCES) {
    for my $class (@{ $r->{classes} }) {
      my $obj = $class->new(
        client      => $docker,
        NoSuchField => 'kept',
      );
      is $obj->client, $docker, "$class: client reached the attribute";
      is_deeply [ sort keys %{ $obj->unknown_fields } ], ['NoSuchField'],
        "$class: it is not filed as an unknown daemon field -- and one that "
        . 'really is unknown still survives beside it';



( run in 0.496 second using v1.01-cache-2.11-cpan-aadc1410aed )