You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
922 lines
30 KiB
922 lines
30 KiB
package NGCP::Test::Resource;
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Moose;
|
|
use Test::More;
|
|
use Test::Deep qw/cmp_bag/;
|
|
use Clone qw/clone/;
|
|
use Data::Dumper;
|
|
use JSON qw/from_json to_json/;
|
|
use NGCP::Test::Patch;
|
|
|
|
has '_test' => (
|
|
isa => 'Object',
|
|
is => 'ro',
|
|
);
|
|
|
|
has 'client' => (
|
|
isa => 'Object',
|
|
is => 'rw',
|
|
);
|
|
|
|
has 'resource' => (
|
|
isa => 'Str',
|
|
is => 'ro',
|
|
);
|
|
|
|
has 'data' => (
|
|
isa => 'Maybe[HashRef]',
|
|
is => 'ro',
|
|
);
|
|
|
|
has 'allowed' => (
|
|
isa => 'HashRef',
|
|
is => 'ro',
|
|
default => sub { { collection => [], item => [] } },
|
|
);
|
|
|
|
has 'autodelete_created_items' => (
|
|
isa => 'Bool',
|
|
is => 'rw',
|
|
default => 1,
|
|
);
|
|
|
|
has 'print_summary_on_finish' => (
|
|
isa => 'Bool',
|
|
is => 'rw',
|
|
default => 0,
|
|
);
|
|
|
|
has 'test_count' => (
|
|
isa => 'Int',
|
|
is => 'rw',
|
|
default => 0,
|
|
);
|
|
|
|
has 'created_items' => (
|
|
isa => 'ArrayRef',
|
|
is => 'rw',
|
|
default => sub {[]}
|
|
);
|
|
|
|
has 'requests' => (
|
|
is => 'rw',
|
|
isa => 'ArrayRef',
|
|
default => sub {[]},
|
|
);
|
|
|
|
sub BUILD {
|
|
my $self = shift;
|
|
unless($self->client) {
|
|
die "Missing parameter 'client' when creating " . __PACKAGE__ . "\n";
|
|
}
|
|
unless($self->resource) {
|
|
die "Missing parameter 'resource' when creating " . __PACKAGE__ . "\n";
|
|
}
|
|
}
|
|
|
|
sub _process_input {
|
|
my ($self, $args) = @_;
|
|
|
|
my @data = ();
|
|
my @qparams= ();
|
|
my @expected = ();
|
|
|
|
if(exists $args->{data_replace}) {
|
|
my $ref = ref $args->{data_replace};
|
|
if($ref eq 'HASH') {
|
|
push @data, ($args->{data_replace});
|
|
} elsif($ref eq 'ARRAY') {
|
|
@data = @{ $args->{data_replace} };
|
|
} else {
|
|
die "Invalid 'data_replace' type, must be hashref or arrayref\n";
|
|
}
|
|
} else {
|
|
@data = ($self->data);
|
|
}
|
|
if(exists $args->{expected_result}) {
|
|
my $ref = ref $args->{expected_result};
|
|
if($ref eq 'HASH') {
|
|
@expected = map { $args->{expected_result} } (1 .. @data);
|
|
} elsif($ref eq 'ARRAY') {
|
|
@expected = @{ $args->{expected_result} };
|
|
unless($#expected == $#data) {
|
|
die "Number of elements in 'data_replace' doesn't match number of elements in 'expected_result'\n";
|
|
}
|
|
} else {
|
|
die "Invalid 'data_replace' type, must be hashref or arrayref\n";
|
|
}
|
|
} else {
|
|
die "Missing argument 'expected_result'\n";
|
|
}
|
|
if(exists $args->{query_params}) {
|
|
my $ref = ref $args->{query_params};
|
|
if($ref eq 'HASH') {
|
|
push @qparams, ($args->{query_params});
|
|
} elsif($ref eq 'ARRAY') {
|
|
@qparams = @{ $args->{query_params} };
|
|
} else {
|
|
die "Invalid 'query_params' type, must be hashref or arrayref\n";
|
|
}
|
|
}
|
|
|
|
return { data => \@data, expected => \@expected, qparams => \@qparams };
|
|
}
|
|
|
|
sub _get_replaced_data {
|
|
my ($self, $data, $repl) = @_;
|
|
|
|
my $allrepl = [];
|
|
|
|
if(ref $repl eq "HASH") {
|
|
push @{ $allrepl }, $repl;
|
|
} elsif(ref $repl eq "ARRAY") {
|
|
$allrepl = $repl;
|
|
} else {
|
|
die "All data_replace elements must be hashref or arrayref\n";
|
|
}
|
|
my $d = clone($data);
|
|
foreach my $repl(@{ $allrepl }) {
|
|
my $key = $repl->{field};
|
|
my $val = $repl->{value};
|
|
if(defined $key) {
|
|
my @parts = split(/\./, $key);
|
|
my $tmp = \$d;
|
|
for(my $i = 0; $i < @parts; ++$i) {
|
|
if($repl->{delete} && $i == @parts - 1) {
|
|
last;
|
|
}
|
|
$tmp = \$$$tmp{$parts[$i]};
|
|
}
|
|
if($repl->{delete}) {
|
|
delete $$tmp->{$parts[$#parts]};
|
|
} else {
|
|
$$tmp = $val;
|
|
}
|
|
}
|
|
}
|
|
return $d;
|
|
}
|
|
|
|
sub _apply_patch {
|
|
my ($self, $data, $json_patch) = @_;
|
|
|
|
my $p = NGCP::Test::Patch->new;
|
|
my $res = $p->apply_patch($data, $json_patch);
|
|
return $res;
|
|
|
|
}
|
|
|
|
sub _get_patch_data {
|
|
my ($self, $data, $repl) = @_;
|
|
|
|
# TODO: support dot-style notation for nested fields
|
|
|
|
my $key = $repl->{field};
|
|
my $path = "/$key";
|
|
$path =~ s/\./\//g;
|
|
my $val = $repl->{value};
|
|
my $op = $repl->{op};
|
|
my $d = {
|
|
op => $op,
|
|
path => $path,
|
|
};
|
|
if($op eq "remove") {
|
|
# no value to be set
|
|
} elsif($op eq "copy" || $op eq "move") {
|
|
# value is the "from" path
|
|
my $from = "/$val";
|
|
$from =~ s/\./\//g;
|
|
$d->{from} = $from;
|
|
} else {
|
|
$d->{value} = $val;
|
|
}
|
|
$d = [$d];
|
|
return $d;
|
|
}
|
|
|
|
sub _test_fields {
|
|
my ($self, $skip, $expected, $ref, $item, $name) = @_;
|
|
#print "++++ testing fields for $name\n";
|
|
#print Dumper $ref;
|
|
#print Dumper $item;
|
|
|
|
$ref //= {};
|
|
|
|
$skip //= [];
|
|
my %skip = ();
|
|
foreach my $s(@{ $skip }) {
|
|
my @f = split /\./, $s;
|
|
my $elem = \%skip;
|
|
foreach my $p(@f) {
|
|
$elem->{$p} = {};
|
|
$elem = $elem->{$p};
|
|
}
|
|
}
|
|
|
|
my %expect = map { $_ => 0 } @{ $expected // [] };
|
|
foreach my $field(keys %{ $ref }) {
|
|
next if($field eq "_links");
|
|
|
|
if(defined $expected && exists $expect{$field}) {
|
|
ok(exists $item->{$field}, "$name - expected field $field seen");
|
|
$self->_inc_test_count;
|
|
$expect{$field} = 1;
|
|
}
|
|
|
|
next if(exists $skip{$field} && !keys %{ $skip{$field} });
|
|
|
|
if(defined $expected && !exists($expect{$field})) {
|
|
next;
|
|
}
|
|
if(ref $ref->{$field} eq "ARRAY") {
|
|
if(exists $skip{$field} && keys %{ $skip{$field} } && @{ $ref->{$field} } && ref $ref->{$field}->[0] eq "HASH") {
|
|
for(my $i = 0; $i < @{ $ref->{$field} }; ++$i) {
|
|
my $refelem = $ref->{$field}->[$i];
|
|
my $elem = $item->{$field}->[$i];
|
|
my $k = (keys %{ $skip{$field} })[0];
|
|
my $refold = delete $refelem->{$k};
|
|
my $old = delete $elem->{$k};
|
|
$self->_test->debug("compare content of $field\n");
|
|
$self->_test->debug("element: " . Dumper $elem);
|
|
$self->_test->debug("reference element: " . Dumper $refelem);
|
|
is_deeply($elem, $refelem, $name . " - content of $field");
|
|
$self->_inc_test_count;
|
|
$refelem->{$k} = $refold;
|
|
$elem->{$k} = $old;
|
|
}
|
|
is(@{ $ref->{$field} }, @{ $item->{$field} }, "$name - element count of $field");
|
|
$self->_inc_test_count;
|
|
} else {
|
|
$self->_test->debug("compare content of $field via bag comparison\n");
|
|
$self->_test->debug("element: " . Dumper $item->{$field});
|
|
$self->_test->debug("reference element: " . Dumper $ref->{$field});
|
|
cmp_bag($item->{$field}, $ref->{$field}, $name . " - content of $field");
|
|
$self->_inc_test_count;
|
|
}
|
|
} elsif(ref $ref->{$field} eq "HASH") {
|
|
if(exists $skip{$field} && keys %{ $skip{$field} }) {
|
|
my $k = (keys %{ $skip{$field} })[0];
|
|
my $refold = delete $ref->{$field}->{$k};
|
|
my $old = delete $item->{$field}->{$k};
|
|
is_deeply($item->{$field}, $ref->{$field}, $name . " - content of $field");
|
|
$self->_inc_test_count;
|
|
$ref->{$field}->{$k} = $refold;
|
|
$item->{$field}->{$k} = $old;
|
|
} else {
|
|
is_deeply($item->{$field}, $ref->{$field}, $name . " - content of $field");
|
|
$self->_inc_test_count;
|
|
}
|
|
} elsif(ref $ref->{$field} ne "") {
|
|
is_deeply($item->{$field}, $ref->{$field}, $name . " - content of $field");
|
|
$self->_inc_test_count;
|
|
} else {
|
|
is($item->{$field}, $ref->{$field}, $name . " - content of $field");
|
|
$self->_inc_test_count;
|
|
}
|
|
}
|
|
if(defined $expected) {
|
|
foreach my $field(keys %{ $item }) {
|
|
next if($field eq "_links");
|
|
ok(exists $expect{$field}, "$name - field $field expected");
|
|
$self->_inc_test_count;
|
|
delete $expect{$field};
|
|
}
|
|
foreach my $field(keys %expect) {
|
|
delete $expect{$field} if($expect{$field} == 1);
|
|
}
|
|
}
|
|
|
|
if(!ok(keys %expect == 0, "$name - all expected fields seen")) {
|
|
diag("the following expected fields were not seen: " . join(', ', keys %expect));
|
|
diag("existing fields were: " . join(', ', keys %{ $item }));
|
|
}
|
|
$self->_inc_test_count;
|
|
}
|
|
|
|
sub test_allowed_methods {
|
|
my ($self, $item) = @_;
|
|
|
|
# while at it, test without item as well
|
|
if(defined $item) {
|
|
$self->test_allowed_methods();
|
|
}
|
|
|
|
my $res;
|
|
my $expected;
|
|
my $name = $self->resource . ' - allowed methods';
|
|
if(defined $item) {
|
|
$res = $self->client->_options('api/'.$self->resource.'/'.$item->{id});
|
|
$name .= " - item";
|
|
$expected = $self->allowed->{item};
|
|
} else {
|
|
$res = $self->client->_options('api/'.$self->resource);
|
|
$name .= " - collection";
|
|
$expected = $self->allowed->{collection};
|
|
}
|
|
|
|
is($res->code, 200, "$name - options request");
|
|
$self->_inc_test_count;
|
|
|
|
return unless($res->is_success);
|
|
my $data = from_json($res->decoded_content);
|
|
my $methods = $data->{methods};
|
|
|
|
my @hopts = split /\s*,\s*/, $res->header('Allow');
|
|
|
|
is(@hopts, @{ $expected }, "$name - correct amount of allowed methods in header");
|
|
$self->_inc_test_count;
|
|
is(@{ $methods }, @{ $expected }, "$name - correct amount of allowed methods in body");
|
|
$self->_inc_test_count;
|
|
|
|
while((my $opt = shift @{ $expected })) {
|
|
ok(grep { /^$opt$/ } @hopts, "$name - '$opt' in Allow header");
|
|
$self->_inc_test_count;
|
|
ok(grep { /^$opt$/ } @{ $methods }, "$name - '$opt' in body");
|
|
$self->_inc_test_count;
|
|
}
|
|
is(@{ $expected }, 0, "$name - no leftover in allowed methods");
|
|
$self->_inc_test_count;
|
|
}
|
|
|
|
sub test_empty_bodies {
|
|
my ($self, $item) = @_;
|
|
|
|
# while at it, test without item as well
|
|
if(defined $item) {
|
|
$self->test_empty_bodies();
|
|
}
|
|
|
|
my $res;
|
|
my $name = $self->resource . ' - missing body';
|
|
if(defined $item) {
|
|
$name .= " - put";
|
|
$res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, undef);
|
|
is($res->code, 400, $name);
|
|
$self->_inc_test_count;
|
|
|
|
$name .= " - patch";
|
|
$res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, undef);
|
|
is($res->code, 400, $name);
|
|
$self->_inc_test_count;
|
|
} else {
|
|
$name .= " - post";
|
|
$res = $self->client->_post('api/'.$self->resource, undef);
|
|
is($res->code, 400, $name);
|
|
$self->_inc_test_count;
|
|
}
|
|
}
|
|
|
|
sub test_invalid_headers {
|
|
my ($self, $item) = @_;
|
|
|
|
# while at it, test without item as well
|
|
if(defined $item) {
|
|
$self->test_invalid_headers();
|
|
}
|
|
|
|
my $res;
|
|
my $data;
|
|
my $testname = $self->resource . ' - invalid headers';
|
|
my $name;
|
|
if(defined $item) {
|
|
$name = "$testname - put without content-type";
|
|
$res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, '', undef);
|
|
is($res->code, 415, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Unsupported Media Type/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/'undefined', accepting application\/json only/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - put with invalid content-type";
|
|
$res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, 'something/invalid', undef);
|
|
is($res->code, 415, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Unsupported Media Type/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/'something\/invalid', accepting application\/json only/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - patch without content-type";
|
|
$res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $item, '', undef);
|
|
is($res->code, 415, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Unsupported Media Type/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/'undefined', accepting application\/json-patch\+json only/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - patch with invalid content-type";
|
|
$res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $item, 'application/json', undef);
|
|
is($res->code, 415, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Unsupported Media Type/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/'application\/json', accepting application\/json-patch\+json only/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - put without prefer";
|
|
$res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, undef, '');
|
|
is($res->code, 204, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/No Content/, "$name - status");
|
|
$self->_inc_test_count;
|
|
is($res->decoded_content, '', "$name - no content");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - put with invalid prefer";
|
|
$res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, undef, 'something=invalid');
|
|
is($res->code, 400, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Bad Request/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/Header 'Prefer' must be either 'return=minimal' or 'return=representation'/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - patch without prefer";
|
|
$res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, [], undef, '');
|
|
is($res->code, 204, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/No Content/, "$name - status");
|
|
$self->_inc_test_count;
|
|
is($res->decoded_content, '', "$name - no content");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - patch with invalid prefer";
|
|
$res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $item, undef, 'something=invalid');
|
|
is($res->code, 400, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Bad Request/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/Header 'Prefer' must be either 'return=minimal' or 'return=representation'/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
} else {
|
|
$item = { test => 'test' };
|
|
$name = "$testname - post without content-type";
|
|
$res = $self->client->_post('api/'.$self->resource, $item, '', undef);
|
|
is($res->code, 415, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Unsupported Media Type/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/Unsupported media type 'undefined', accepting application\/json only/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - post with invalid content-type";
|
|
$res = $self->client->_post('api/'.$self->resource, $item, 'something/invalid', undef);
|
|
is($res->code, 415, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Unsupported Media Type/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/'something\/invalid', accepting application\/json only/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - post without prefer";
|
|
$res = $self->client->_post('api/'.$self->resource, {}, undef, '');
|
|
is($res->code, 422, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Unprocessable Entity/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
# we assume here that at least ANY field must be existent in a post
|
|
like($data->{message}, qr/Validation failed/, "$name - error");
|
|
$self->_inc_test_count;
|
|
|
|
$name = "$testname - post with invalid prefer";
|
|
$res = $self->client->_post('api/'.$self->resource, $item, undef, 'something=invalid');
|
|
is($res->code, 400, "$name - code");
|
|
$self->_inc_test_count;
|
|
like($res->message, qr/Bad Request/, "$name - status");
|
|
$self->_inc_test_count;
|
|
$data = from_json($res->decoded_content);
|
|
like($data->{message}, qr/Header 'Prefer' must be either 'return=minimal' or 'return=representation'/, "$name - error");
|
|
$self->_inc_test_count;
|
|
}
|
|
}
|
|
|
|
sub _test_link {
|
|
my ($self, $href, $name) = @_;
|
|
my $res = $self->client->_get($href);
|
|
$self->_push_request($name, $res);
|
|
return $res->is_success;
|
|
}
|
|
|
|
sub _test_item {
|
|
my ($self, $item, $args, $name, $ref) = @_;
|
|
|
|
ok(!exists $item->{_embedded}, "$name - absence of _embedded in item");
|
|
$self->_inc_test_count;
|
|
ok(!exists $item->{total_count}, "$name - absence of total_count in item");
|
|
$self->_inc_test_count;
|
|
$self->_test_fields($args->{skip_test_fields}, $args->{expected_fields}, $ref, $item, $name);
|
|
my %links = map { $_ => 0 } @{ $args->{expected_links} };
|
|
foreach my $link(keys %{ $item->{_links} }) {
|
|
next if(grep { /^$link$/ } qw/self curies profile collection/);
|
|
ok(exists $links{$link}, "$name - existence of link $link in reference");
|
|
$self->_inc_test_count;
|
|
delete $links{$link};
|
|
ok($self->_test_link($item->{_links}->{$link}->{href}, $name), "$name - follow link to $link");
|
|
$self->_inc_test_count;
|
|
}
|
|
if(!ok(keys %links == 0, "$name - missing links")) {
|
|
diag("missing links: " . join(", ", keys %links));
|
|
}
|
|
$self->_inc_test_count;
|
|
}
|
|
|
|
sub test_get {
|
|
my ($self, %args) = @_;
|
|
my $testname = $self->resource . ' - ' . ($args{name} // 'test_get');
|
|
my $ref = $args{item};
|
|
my $input = $self->_process_input(\%args);
|
|
unless(@{ $input->{qparams} }) {
|
|
$input->{qparams} = [{}];
|
|
}
|
|
|
|
my @qparams = @{ $input->{qparams} };
|
|
my @expected = @{ $input->{expected} };
|
|
my @items = ();
|
|
|
|
if($ref && @qparams != 1) {
|
|
die "When providing a reference 'item', query_params must contain one row only\n";
|
|
}
|
|
|
|
for(my $i = 0; $i < @qparams; ++$i) {
|
|
my $e = $expected[$i];
|
|
my $q = $qparams[$i];
|
|
my $name = "$testname $i";
|
|
|
|
my $uri = 'api/'.$self->resource . ($ref ? "/$$ref{id}" : "");
|
|
my @tmpq = ();
|
|
foreach my $k(keys %{ $q }) {
|
|
push @tmpq, "$k=$$q{$k}";
|
|
}
|
|
if(@tmpq) {
|
|
$uri .= '?' . join('&', @tmpq);
|
|
}
|
|
|
|
my $res = $self->client->_get($uri);
|
|
is($res->code, $e->{code}, $name . " - code");
|
|
$self->_inc_test_count;
|
|
if(exists $e->{reason_re}) {
|
|
like($res->message, qr/$e->{reason_re}/, $name . " - message");
|
|
$self->_inc_test_count;
|
|
} elsif(exists $e->{error_re}) {
|
|
ok(!$res->is_success, $name . " - is an error");
|
|
$self->_inc_test_count;
|
|
my $item = from_json($res->decoded_content);
|
|
like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
|
|
$self->_inc_test_count;
|
|
}
|
|
|
|
if($res->code =~ /^2\d\d$/) {
|
|
my $item = from_json($res->decoded_content);
|
|
push @items, $item;
|
|
if($ref) {
|
|
$self->_test_item($item, \%args, $name, $ref);
|
|
} else {
|
|
ok(exists $item->{total_count}, "$name - presence of total_count in collection");
|
|
$self->_inc_test_count;
|
|
if($item->{total_count}) {
|
|
ok(exists $item->{_embedded}, "$name - presence of _embedded in collection on data");
|
|
$self->_inc_test_count;
|
|
}
|
|
if(exists $args{expected_count}) {
|
|
is($item->{total_count}, $args{expected_count}, "$name - total_count value");
|
|
$self->_inc_test_count;
|
|
}
|
|
foreach my $subitem(@{ $item->{_embedded}->{"ngcp:".$self->resource} }) {
|
|
$self->_test_item($subitem, \%args, $name, undef);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return \@items;
|
|
}
|
|
|
|
|
|
sub test_post {
|
|
my ($self, %args) = @_;
|
|
my $testname = $self->resource . ' - ' . ($args{name} // 'test_post');
|
|
|
|
my $input = $self->_process_input(\%args);
|
|
|
|
my @data = @{ $input->{data} };
|
|
my @expected = @{ $input->{expected} };
|
|
|
|
for(my $i = 0; $i < @data; ++$i) {
|
|
|
|
my $d = $self->_get_replaced_data($self->data, $data[$i]);
|
|
my $e = $expected[$i];
|
|
my $name = "$testname $i";
|
|
|
|
# fire request and test result
|
|
my $res = $self->client->_post('api/'.$self->resource, $d);
|
|
$self->_push_request($name, $res);
|
|
is($res->code, $e->{code}, $name . " - code");
|
|
$self->_inc_test_count;
|
|
if(exists $e->{reason_re}) {
|
|
like($res->message, qr/$e->{reason_re}/, $name . " - message");
|
|
$self->_inc_test_count;
|
|
} elsif(exists $e->{error_re}) {
|
|
ok(!$res->is_success, $name . " - is an error");
|
|
$self->_inc_test_count;
|
|
my $item = from_json($res->decoded_content);
|
|
like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
|
|
$self->_inc_test_count;
|
|
}
|
|
# copy created item into cache
|
|
if($res->code =~ /^2\d\d$/) {
|
|
my $item;
|
|
if($res->decoded_content) {
|
|
# TODO: do we need to extract from JSON::HAL?
|
|
$item = from_json($res->decoded_content);
|
|
} else {
|
|
my $id = $res->header('Location');
|
|
$id =~ s/^.+\/([^\/]+)$/$1/;
|
|
$res = $self->client->_get('api/'.$self->resource.'/'.$id);
|
|
$self->_push_request($name, $res);
|
|
unless($res->is_success) {
|
|
die "Failed to re-fetch created resource " . $self->resource . " with id $id\n";
|
|
}
|
|
# TODO: do we need to extract from JSON::HAL?
|
|
$item = from_json($res->decoded_content);
|
|
}
|
|
$self->push_created_item($item);
|
|
$self->_test_fields($args{skip_test_fields}, $args{expected_fields}, $d, $item, $name);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub test_put {
|
|
my ($self, %args) = @_;
|
|
my $testname = $self->resource . ' - ' . ($args{name} // 'test_put');
|
|
my $item = $args{item};
|
|
|
|
my $input = $self->_process_input(\%args);
|
|
|
|
my @data = @{ $input->{data} };
|
|
my @expected = @{ $input->{expected} };
|
|
|
|
my @items = ();
|
|
|
|
for(my $i = 0; $i < @data; ++$i) {
|
|
|
|
my $d = $self->_get_replaced_data($item, $data[$i]);
|
|
$self->_test->debug("replaced data: " . Dumper $d);
|
|
my $e = $expected[$i];
|
|
my $name = "$testname $i";
|
|
|
|
# fire request and test result
|
|
my $res = $self->client->_put('api/'.$self->resource.'/'.$d->{id}, $d);
|
|
$self->_push_request($name, $res);
|
|
is($res->code, $e->{code}, $name . " - code");
|
|
$self->_inc_test_count;
|
|
if(exists $e->{reason_re}) {
|
|
like($res->message, qr/$e->{reason_re}/, $name . " - message");
|
|
$self->_inc_test_count;
|
|
} elsif(exists $e->{error_re}) {
|
|
ok(!$res->is_success, $name . " - is an error");
|
|
$self->_inc_test_count;
|
|
my $item = from_json($res->decoded_content);
|
|
like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
|
|
$self->_inc_test_count;
|
|
}
|
|
if($res->code =~ /^2\d\d$/ && $res->decoded_content) {
|
|
# TODO: do we need to extract from JSON::HAL?
|
|
my $resitem = from_json($res->decoded_content);
|
|
push @items, $resitem;
|
|
$self->_test_fields($args{skip_test_fields}, $args{expected_fields}, $d, $resitem, $name);
|
|
} else {
|
|
push @items, $item;
|
|
}
|
|
}
|
|
return \@items;
|
|
}
|
|
|
|
|
|
sub test_patch {
|
|
my ($self, %args) = @_;
|
|
my $testname = $self->resource . ' - ' . ($args{name} // 'test_patch');
|
|
my $item = $args{item};
|
|
|
|
my $input = $self->_process_input(\%args);
|
|
|
|
my @data = @{ $input->{data} };
|
|
my @expected = @{ $input->{expected} };
|
|
|
|
my @items = ();
|
|
|
|
for(my $i = 0; $i < @data; ++$i) {
|
|
|
|
my $d = $self->_get_patch_data($item, $data[$i]);
|
|
my $refd = $self->_apply_patch($item, to_json($d));
|
|
my $e = $expected[$i];
|
|
my $name = "$testname $i";
|
|
|
|
# fire request and test result
|
|
my $res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $d);
|
|
$self->_push_request($name, $res);
|
|
is($res->code, $e->{code}, $name . " - code");
|
|
$self->_inc_test_count;
|
|
if(exists $e->{reason_re}) {
|
|
like($res->message, qr/$e->{reason_re}/, $name . " - message");
|
|
$self->_inc_test_count;
|
|
} elsif(exists $e->{error_re}) {
|
|
ok(!$res->is_success, $name . " - is an error");
|
|
$self->_inc_test_count;
|
|
my $item = from_json($res->decoded_content);
|
|
like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
|
|
$self->_inc_test_count;
|
|
}
|
|
if($res->code =~ /^2\d\d$/ && $res->decoded_content) {
|
|
# TODO: do we need to extract from JSON::HAL?
|
|
my $resitem = from_json($res->decoded_content);
|
|
push @items, $resitem;
|
|
$self->_test_fields($args{skip_test_fields}, $args{expected_fields}, $refd, $resitem, $name);
|
|
} else {
|
|
push @items, $item;
|
|
}
|
|
}
|
|
return \@items;
|
|
}
|
|
|
|
sub test_delete {
|
|
my ($self, %args) = @_;
|
|
my $testname = $self->resource . ' - ' . ($args{name} // 'test_delete');
|
|
my $item = $args{item};
|
|
|
|
my $input = $self->_process_input(\%args);
|
|
|
|
my @data = @{ $input->{data} };
|
|
my @expected = @{ $input->{expected} };
|
|
|
|
my @items = ();
|
|
|
|
for(my $i = 0; $i < @data; ++$i) {
|
|
|
|
my $d = $self->_get_replaced_data($item, $data[$i]);
|
|
my $e = $expected[$i];
|
|
my $name = "$testname $i";
|
|
|
|
# fire request and test result
|
|
my $res = $self->client->_delete('api/'.$self->resource.'/'.$d->{id});
|
|
$self->_push_request($name, $res);
|
|
is($res->code, $e->{code}, $name . " - code");
|
|
$self->_inc_test_count;
|
|
if(exists $e->{reason_re}) {
|
|
like($res->message, qr/$e->{reason_re}/, $name . " - message");
|
|
$self->_inc_test_count;
|
|
} elsif(exists $e->{error_re}) {
|
|
ok(!$res->is_success, $name . " - is an error");
|
|
$self->_inc_test_count;
|
|
my $item = from_json($res->decoded_content);
|
|
like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
|
|
$self->_inc_test_count;
|
|
}
|
|
}
|
|
}
|
|
|
|
=pod
|
|
# TODO: not needed?
|
|
sub _update_created_item {
|
|
my ($self, $item) = @_;
|
|
for(my $i = 0; $i < $self->count_created_items(); ++$i) {
|
|
if($self->created_items->[$i]->{id} eq $item->{id}) {
|
|
print "found old item in cache\n";
|
|
@{$self->created_items}[$i] = $item;
|
|
}
|
|
}
|
|
}
|
|
=cut
|
|
|
|
sub _inc_test_count {
|
|
my ($self) = @_;
|
|
$self->_test->inc_test_count();
|
|
}
|
|
|
|
sub push_created_item {
|
|
my ($self, $item) = @_;
|
|
push @{ $self->created_items }, $item;
|
|
}
|
|
|
|
sub pop_created_item {
|
|
my ($self) = @_;
|
|
my $item = pop @{ $self->created_items };
|
|
return $item;
|
|
}
|
|
|
|
sub shift_created_item {
|
|
my ($self) = @_;
|
|
my $item = shift @{ $self->created_items };
|
|
return $item;
|
|
}
|
|
|
|
sub count_created_items {
|
|
my ($self) = @_;
|
|
return 0 + @{ $self->created_items };
|
|
}
|
|
|
|
sub print_summary {
|
|
my ($self) = @_;
|
|
|
|
diag("Performed Requests:");
|
|
my $i = 0;
|
|
foreach my $r(@{ $self->requests }) {
|
|
$i++;
|
|
diag("$$r{testname}:");
|
|
diag("$$r{method} $$r{uri}");
|
|
diag("$$r{code} $$r{message}");
|
|
diag("rtt: $$r{rtt}");
|
|
diag("");
|
|
}
|
|
diag("Total: $i");
|
|
}
|
|
|
|
sub _push_request {
|
|
my ($self, $name, $res) = @_;
|
|
push @{ $self->requests }, {
|
|
testname => $name,
|
|
method => $res->request->method,
|
|
uri => $res->request->uri,
|
|
code => $res->code,
|
|
message => $res->message,
|
|
rtt => $self->client->last_rtt,
|
|
};
|
|
}
|
|
|
|
|
|
sub DEMOLISH {
|
|
my ($self) = @_;
|
|
my @failed = ();
|
|
my @threads = ();
|
|
if($self->autodelete_created_items) {
|
|
while((my $item = $self->pop_created_item())) {
|
|
my $id = $item->{id};
|
|
my $url = 'api/'.$self->resource.'/'.$id;
|
|
my $res = $self->client->_delete($url);
|
|
unless($res->is_success) {
|
|
my $data = from_json($res->decoded_content);
|
|
if($res->code == 404) {
|
|
$res = $self->client->_patch($url, [{
|
|
op => 'replace',
|
|
path => '/status',
|
|
value => 'terminated'
|
|
}]);
|
|
unless($res->is_success) {
|
|
diag("Failed to both auto-delete or auto-terminate '$url': $$data{message}");
|
|
push @failed, $id;
|
|
} else {
|
|
#print "+++++ $url successfully terminated\n";
|
|
}
|
|
}
|
|
} else {
|
|
#print "+++++ $url successfully deleted\n";
|
|
}
|
|
|
|
$self->_push_request('autodelete', $res);
|
|
}
|
|
if(@failed) {
|
|
diag("failed to auto-delete the following " .
|
|
$self->resource . ": " . (join ', ', @failed));
|
|
}
|
|
}
|
|
if($self->print_summary_on_finish) {
|
|
$self->print_summary();
|
|
}
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
NGCP::Test::Resource
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
NGCP Panel test resource
|
|
|
|
=head1 AUTHOR
|
|
|
|
Sipwise Development Team
|
|
|
|
=head1 LICENSE
|
|
|
|
This library is free software. You can redistribute it and/or modify
|
|
it under the same terms as Perl itself.
|
|
|
|
=cut
|