API-Docker
view release on metacpan or search on metacpan
t/system_auth.t view on Meta::CPAN
my ($line) = $raw =~ /\A([^\r\n]*)/;
return $line;
}
sub request_body {
my ($raw) = @_;
my ($body) = $raw =~ /\r\n\r\n(.*)\z/s;
return $body;
}
my $CREDS = {
username => 'me',
password => 'secret',
serveraddress => 'ghcr.io',
};
# ---------------------------------------------------------------------------
subtest 'the credentials go in the body, not in a header' => sub {
my $c = fake_client();
my $out = $c->system->auth(%$CREDS);
is request_line($c->written), 'POST /v1.41/auth HTTP/1.1',
'POST /auth, no query string';
is_deeply decode_json(request_body($c->written)), $CREDS,
'the AuthConfig is the JSON request body';
# The same object push carries in X-Registry-Auth, but /auth is the one
# endpoint that takes it unencoded -- a header here would be ignored.
unlike $c->written, qr/X-Registry-Auth/i,
'no X-Registry-Auth header on this endpoint';
like $c->written, qr{Content-Type: application/json\r\n},
'sent as JSON';
is_deeply $out, { Status => 'Login Succeeded', IdentityToken => '' },
'the decoded engine answer is returned unwrapped';
};
# ---------------------------------------------------------------------------
subtest 'the auth argument push takes is accepted here too' => sub {
# The point of the second spelling: a caller can hand over exactly what it
# was about to push with, without taking it apart first.
my $c = fake_client();
$c->system->auth(auth => $CREDS);
is_deeply decode_json(request_body($c->written)), $CREDS,
'a HashRef reaches the body as itself';
my $encoded = $c->system->_registry_auth_header($CREDS);
my $b = fake_client();
$b->system->auth(auth => $encoded);
is_deeply decode_json(request_body($b->written)), $CREDS,
'and an already-encoded X-Registry-Auth value is decoded back into it';
my $j = fake_client();
$j->system->auth(auth => '{"identitytoken":"tok-123"}');
is_deeply decode_json(request_body($j->written)), { identitytoken => 'tok-123' },
'as is a raw JSON AuthConfig';
};
# ---------------------------------------------------------------------------
subtest 'the two spellings may not be mixed' => sub {
# Not a style rule: picking one silently would be picking which of two sets
# of credentials gets sent.
my $c = fake_client();
ok !eval { $c->system->auth(auth => $CREDS, username => 'other'); 1 },
'auth => $config beside a credential key croaks';
like $@, qr/not both/, 'and says which keys were seen';
is $c->written, '', 'nothing was sent';
};
# ---------------------------------------------------------------------------
subtest 'a check with nothing to check croaks before the request' => sub {
my $c = fake_client();
ok !eval { $c->system->auth; 1 }, 'no arguments croaks';
like $@, qr/requires credentials/, 'and says what is missing';
is $c->written, '', 'nothing was sent';
my $e = fake_client();
ok !eval { $e->system->auth(auth => {}); 1 },
'an empty AuthConfig croaks too';
is $e->written, '', 'nothing was sent';
# Stricter than the engine on purpose: Podman answers the empty body with
# 500 'getting username and password: cannot prompt for username without
# stdin', which describes the daemon's stdin rather than the caller's bug.
};
# ---------------------------------------------------------------------------
subtest 'only identitytoken is enough' => sub {
my $c = fake_client();
$c->system->auth(identitytoken => 'tok-123', serveraddress => 'ghcr.io');
is_deeply decode_json(request_body($c->written)),
{ identitytoken => 'tok-123', serveraddress => 'ghcr.io' },
'a token-only AuthConfig is a complete one';
};
# ---------------------------------------------------------------------------
subtest 'a rejected credential croaks, and the status is still readable' => sub {
# Docker's answer: 401 with {"message":...}.
my $c = fake_client('{"message":"Get \"https://ghcr.io/v2/\": unauthorized"}', 401);
my %res;
ok !eval { $c->system->auth(%$CREDS, response => \%res); 1 },
'a 401 croaks rather than returning a false value';
like $@, qr/unauthorized/, 'the registry message survives into the croak';
is $res{status}, 401,
'response is filled before the croak, so an eval-ing caller can tell '
. 'a rejected credential from an unreachable registry';
# Podman's answer to the same failure: 500, measured -- see the header
# comment. A caller testing for 401 would call this a success.
my $p = fake_client(
'{"message":"login attempt to ghcr.io failed with status: '
. 'authenticating creds for \"ghcr.io\": pinging container registry"}', 500);
my %pres;
ok !eval { $p->system->auth(%$CREDS, response => \%pres); 1 },
'Podman 500 croaks the same way';
is $pres{status}, 500, 'and reports its own status';
};
done_testing;
( run in 2.594 seconds using v1.01-cache-2.11-cpan-7e1922ba9f2 )