#! /usr/bin/perl
{
throws_ok {
OpenStack::Client->new
}
qr/No API endpoint provided/
,
"OpenStack::Client->new() dies when no API endpoint URI is provided"
;
lives_ok {
}
"OpenStack::Client->new() doesn't die when API endpoint is provided"
;
}
{
my
$token
= {
'foo'
=>
'bar'
};
my
%tests
= (
'foobar'
=>
"${endpoint}foobar"
,
'/foobar'
=>
"${endpoint}foobar"
,
'foobar/baz'
=>
"${endpoint}foobar/baz"
);
my
$client
= OpenStack::Client->new(
$endpoint
,
'token'
=>
$token
);
foreach
my
$input
(
sort
keys
%tests
) {
my
$expected
=
$tests
{
$input
};
is(
$client
->uri(
$input
) =>
$expected
,
"\$client->uri() yields '$expected' for '$input'"
);
}
is(
$client
->
endpoint
=>
$endpoint
,
"\$client->endpoint() returns original HTTP endpoint URI"
);
is(
$client
->
token
=>
$token
,
"\$client->token() returns original token object"
);
}
Test::OpenStack::Client->run_client_tests({
'responses'
=> [{
'content'
=> JSON::encode_json({
'foo'
=>
'bar'
})
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
my
$content
;
lives_ok {
$content
=
$client
->call(
'GET'
=>
'/foo'
);
}
"\$client->call() doesn't die on a well-instantiated client"
;
is(
$content
->{
'foo'
} =>
'bar'
,
"Response contains expected result"
);
}
}, {
'test'
=>
sub
{
is(
shift
->call(
'GET'
=>
'/foo'
) =>
''
,
"Empty response contains expected empty result"
);
}
}, {
'responses'
=> [{
'code'
=> 420,
'content'
=>
'420 Baked'
,
'headers'
=> {
'content-type'
=>
'text/plain'
}
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
throws_ok {
$client
->call(
'GET'
=>
'/foo'
)
}
qr/420 Baked/
,
"\$client->call() dies when 400 error is returned by service"
;
}
}, {
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
my
$content
= {
'bar'
=>
'baz'
};
$client
->call(
'GET'
=>
'/foo'
,
$content
);
my
$got
=
$ua
->{
'requests'
}->[0]->content;
my
$expected
= JSON::encode_json(
$content
);
is_deeply(
$got
=>
$expected
,
"\$client decodes JSON response bodies"
);
}
}, {
'responses'
=> [{
'code'
=> 520,
'content'
=>
'Temporarily out of fucks to give'
,
'headers'
=> {
'Content-Type'
=>
'text/plain'
}
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
throws_ok {
$client
->call(
'GET'
=>
'/foo'
);
}
qr/Temporarily out of fucks to give/
,
"\$client->call() dies with response body when 400 or 500 status is raised"
;
}
}, {
'responses'
=> [{
'code'
=> 320,
'content'
=>
'Do whatever you want'
,
'headers'
=> {
'Content-Type'
=>
'text/plain'
}
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
my
$got
=
$client
->call(
'GET'
=>
'/foo'
);
my
$expected
=
'Do whatever you want'
;
is(
$got
=>
$expected
,
"\$client->call() returns message body for plain text, non-error responses"
);
}
}, {
'responses'
=> [{
'code'
=> 504,
'content'
=>
undef
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
throws_ok {
$client
->call(
'GET'
=>
'/foo'
);
}
qr/504 Unknown error/
,
"\$client->call() dies with an unknown error when no response body is provided"
;
}
}, {
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
my
@tests
= (
[
'/foo'
=> { },
"no parameters"
],
[
'/foo?bar=baz'
=> {
'bar'
=>
'baz'
},
"a single parameter"
],
[
'/foo?bar=baz&meow=cats'
=> {
'bar'
=>
'baz'
,
'meow'
=>
'cats'
},
"multiple parameters"
],
[
'/foo?bar=baz&tiny%20kittens=are%20awesome'
=> {
'bar'
=>
'baz'
,
'tiny kittens'
=>
'are awesome'
},
"parameters needing URI encoding"
]
);
my
$i
= 0;
foreach
my
$test
(
@tests
) {
my
(
$expected_suffix
,
$input
,
$message
) = @{
$test
};
my
$expected
=
"${prefix}${expected_suffix}"
;
$client
->get(
'/foo'
, %{
$input
});
my
$got
=
$ua
->{
'requests'
}->[
$i
++]->{
'path'
};
is(
$got
=>
$expected
,
"\$client->get() encodes with $message"
);
}
}
}, {
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
$client
->get(
'/foo?bar=baz'
,
'meow'
=>
'cats'
);
my
$got
=
$ua
->{
'requests'
}->[0]->{
'path'
};
my
$expected
=
"${prefix}/foo?bar=baz&meow=cats"
;
is(
$got
=>
$expected
,
"\$client->get() handles request paths with arguments already present"
);
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({
'payload'
=> [
'foo'
,
'bar'
,
'baz'
]
})
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
my
$got
= [];
my
$expected
= [
'foo'
,
'bar'
,
'baz'
];
$client
->
each
(
'/foo'
,
sub
{
my
(
$result
) =
@_
;
push
@{
$got
}, @{
$result
->{
'payload'
}};
});
is_deeply(
$got
=>
$expected
,
"\$client->each() calls a callback for each paginated result"
);
throws_ok {
$client
->
each
();
}
qr/Invalid number of arguments/
,
"\$client->each() dies when too few arguments are passed"
;
throws_ok {
$client
->
each
(
'/foo'
, {},
sub
{},
'foo'
);
}
qr/Invalid number of arguments/
,
"\$client->each() dies when too many arguments are passed"
;
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({})
}],
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
lives_ok {
$client
->
each
(
'/foo'
, {
'bar'
=>
'baz'
},
sub
{});
}
"\$client->each() succeeds when passed GET parameters"
;
my
$got
=
$ua
->{
'requests'
}->[0]->{
'path'
};
is(
$got
=>
$expected
,
"\$client->each() handles GET parameters properly"
);
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({
'items'
=> [{
'foo'
=>
'bar'
}],
'next'
=>
'/foo/1'
})
}, {
'content'
=> JSON::encode_json({
'items'
=> [{
'baz'
=>
'boo'
}],
'next'
=>
'/foo/2'
})
}, {
'content'
=> JSON::encode_json({
'items'
=> [{
'meow'
=>
'cats'
}]
})
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
my
@responses
;
$client
->every(
'/foo'
,
'items'
,
sub
{
my
(
$item
) =
@_
;
push
@responses
,
$item
;
});
is(
scalar
@responses
=> 3,
"\$client->every() callback is called once per item across paginations"
);
my
@expected
= (
{
'foo'
=>
'bar'
},
{
'baz'
=>
'boo'
},
{
'meow'
=>
'cats'
}
);
is_deeply(\
@responses
=> \
@expected
,
"\$client->every() decodes response bodies as expected"
);
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({
'items'
=> [
'foo'
,
'bar'
,
'baz'
]
})
}],
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
lives_ok {
$client
->every(
'/foo'
,
'items'
, {
'meow'
=>
'cats'
},
sub
{});
}
"\$client->every() won't die if passed GET parameters"
;
my
$got
=
$ua
->{
'requests'
}->[0]->{
'path'
};
is(
$got
=>
$expected
,
"\$client->every() passes along GET parameters fine"
);
}
}, {
'responses'
=> [{
'content'
=>
'{}'
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
throws_ok {
$client
->every(
'/foo'
,
'items'
,
sub
{})
}
qr/Response from \/
foo does not contain attribute/,
"\$client->every() dies when responses lack attribute corresponding to result set"
;
throws_ok {
$client
->every(
'/foo'
,
'items'
,
sub
{}, {},
'bleh'
);
}
qr/Invalid number of arguments/
,
"\$client->every() will complain if argument count is too much"
;
throws_ok {
$client
->every(
'/foo'
,
'items'
);
}
qr/Invalid number of arguments/
,
"\$client->every() will complain if argument count is too little"
;
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({
'items'
=> [
'foo'
,
'bar'
,
'baz'
],
'next'
=>
'/foo/2'
})
}, {
'content'
=> JSON::encode_json({
'items'
=> [
'eins'
,
'zwei'
,
'drei'
]
})
}],
'test'
=>
sub
{
my
(
$client
) =
@_
;
my
@got
;
my
@expected
=
qw(foo bar baz eins zwei drei)
;
lives_ok {
@got
=
$client
->all(
'/foo'
,
'items'
);
}
"\$client->all() doesn't die in ordinary circumstances"
;
is_deeply(\
@got
=> \
@expected
,
"\$client->all() handles result sets in the appropriate manner"
);
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({})
}],
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
my
$sub
=
sub
{};
lives_ok {
$client
->call(
'PUT'
, {},
'/foo'
,
$sub
);
}
"\$client->call() doesn't die when passed a CODE ref request body"
;
is
$ua
->{
'requests'
}->[0]->{
'content'
} =>
$sub
,
"\$client->call() passes CODE ref request body appropriately"
;
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({})
}],
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
my
$called
= 0;
lives_ok {
$client
->call({
'method'
=>
'PUT'
,
'path'
=>
'/foo'
,
'handler'
=>
sub
{
$called
= 1;
}
});
}
"\$client->call() doesn't die when passed a CODE ref response handler"
;
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({
'items'
=> []
})
}],
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
lives_ok {
$client
->all(
'/foo'
,
'items'
, {
'meow'
=>
'cats'
});
}
"\$client->all() doesn't die when provided GET parameters"
;
my
$got
=
$ua
->{
'requests'
}->[0]->{
'path'
};
is(
$got
=>
$expected
,
"\$client->all() passes GET parameters on expectedly"
);
}
}, {
'responses'
=> [{
'content'
=> JSON::encode_json({
'bar'
=>
'baz'
})
}, {
'content'
=> JSON::encode_json({
'meow'
=>
'cats'
})
}],
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
my
@tests
= (
[
'put'
,
'PUT'
=> {
'bar'
=>
'baz'
}],
[
'post'
,
'POST'
=> {
'meow'
=>
'cats'
}]
);
my
@got_responses
;
lives_ok {
push
@got_responses
,
$client
->put(
'/foo'
, {
'bar'
=>
'baz'
});
}
"\$client->put() doesn't die when called normally"
;
lives_ok {
push
@got_responses
,
$client
->post(
'/foo'
, {
'meow'
=>
'cats'
});
}
"\$client->post() doesn't die when called normally"
;
lives_ok {
$client
->
delete
(
'/foo/1'
);
}
"\$client->delete() doesn't die when called normally"
;
my
$i
= 0;
foreach
my
$test
(
@tests
) {
my
(
$name
,
$expected_method
,
$expected_response
) = @{
$test
};
my
$got_method
=
$ua
->{
'requests'
}->[
$i
]->method;
my
$got_response
=
$got_responses
[
$i
];
is(
$got_method
=>
$expected_method
,
"\$client->$name() issues call with appropriate HTTP method"
);
is_deeply(
$got_response
=>
$expected_response
,
"\$client->$name() decodes response body appropriately"
);
$i
++;
}
}
}, {
'test'
=>
sub
{
my
(
$client
,
$ua
) =
@_
;
no
warnings
qw/redefine/
;
my
$orig_request
= \
&Test::OpenStack::Client::UserAgent::request
;
my
$req_headers
;
local
*Test::OpenStack::Client::UserAgent::request
=
sub
{
my
(
$self
,
$request
) =
@_
;
$req_headers
=
$request
->{
'headers'
};
return
$orig_request
->(
$self
,
$request
);
};
my
@got_responses
= ();
my
$headers
= {
'content-type'
=>
'foobar'
,
'x-grumpy-cat'
=>
'0 ftg'
,
};
my
$response
;
lives_ok {
$response
=
$client
->call(
'PATCH'
,
$headers
,
'/foo'
, {
'bar'
=>
'baz'
});
}
"\$client->call() doesn't die when called normally with 4 arguments (including \$headers)"
;
$headers
->{
'accept-encoding'
} =
'identity, gzip, deflate, compress'
;
$headers
->{
'content-length'
} = 0;
$headers
->{
'accept'
} =
'application/json, text/plain'
;
is_deeply
$req_headers
=>
$headers
,
"4 argument form of \$client->call() sets headers as expected."
;
delete
$headers
->{
'content-length'
};
delete
$headers
->{
'x-grumpy-cat'
};
$headers
->{
'accept-encoding'
} =
'I have no idea what I am doing.'
;
$headers
->{
'accept'
} =
'The honeybadger accepts it all!'
;
$headers
->{
'content-type'
} =
'application/openstack-images-v2.1-json-patch'
;
$headers
->{
'x-ftg'
} = 0;
$req_headers
= {};
lives_ok {
$response
=
$client
->call(
'PATCH'
,
$headers
,
'/foo'
, {
'bar'
=>
'baz'
});
}
"\$client->call() doesn't die when called normally with 4 arguments (including \$headers)"
;
$headers
->{
'content-length'
} = 0;
is_deeply
$req_headers
=>
$headers
,
"4 argument form of \$client->call() sets headers as expected."
;
}
});