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.
1298 lines
44 KiB
1298 lines
44 KiB
package Test::Collection;
|
|
#todo: later package should be split into 2: apiclient and testcollection
|
|
#testcollection will keep object of the apiclient
|
|
|
|
use strict;
|
|
use Test::More;
|
|
use Moose;
|
|
use JSON;
|
|
use LWP::UserAgent;
|
|
use Config::General;
|
|
use HTTP::Request::Common;
|
|
use Net::Domain qw(hostfqdn);
|
|
use URI;
|
|
use URI::Escape;
|
|
use Clone qw/clone/;
|
|
use File::Basename;
|
|
use Test::HTTPRequestAsCurl;
|
|
use Data::Dumper;
|
|
use File::Slurp qw/write_file/;
|
|
use Storable;
|
|
use Carp qw(cluck longmess shortmess);
|
|
use IO::Uncompress::Unzip;
|
|
|
|
has 'crt_path' => (
|
|
is => 'ro',
|
|
isa => 'Str',
|
|
lazy => 1,
|
|
default => sub {'/tmp/apicert.pem';},
|
|
);
|
|
|
|
has 'data_cache_file' => (
|
|
is => 'ro',
|
|
isa => 'Str',
|
|
lazy => 1,
|
|
default => sub {'/tmp/ngcp-api-test-data-cache';},
|
|
);
|
|
has 'cache_data' => (
|
|
is => 'ro',
|
|
isa => 'Bool',
|
|
lazy => 1,
|
|
default => $ENV{API_CACHE_FAKE_DATA} // 0,
|
|
);
|
|
has 'local_test' => (
|
|
is => 'rw',
|
|
isa => 'Str',
|
|
default => sub {$ENV{LOCAL_TEST} // ''},
|
|
);
|
|
|
|
has 'DEBUG' => (
|
|
is => 'rw',
|
|
isa => 'Bool',
|
|
default => 0,
|
|
);
|
|
has 'DEBUG_ONLY' => (
|
|
is => 'rw',
|
|
isa => 'Bool',
|
|
default => 0,
|
|
);
|
|
has 'QUIET_DELETION' => (
|
|
is => 'rw',
|
|
isa => 'Bool',
|
|
default => 1,
|
|
);
|
|
has 'ALLOW_EMPTY_COLLECTION' => (
|
|
is => 'rw',
|
|
isa => 'Bool',
|
|
default => 0,
|
|
);
|
|
has 'IS_EMPTY_COLLECTION' => (
|
|
is => 'rw',
|
|
isa => 'Bool',
|
|
default => 0,
|
|
);
|
|
has 'NO_ITEM_MODULE' => (
|
|
is => 'rw',
|
|
isa => 'Bool',
|
|
default => 0,
|
|
);
|
|
has 'catalyst_config' => (
|
|
is => 'rw',
|
|
isa => 'HashRef',
|
|
lazy => 1,
|
|
builder => 'init_catalyst_config',
|
|
);
|
|
has 'panel_config' => (
|
|
is => 'rw',
|
|
isa => 'HashRef',
|
|
);
|
|
has 'runas_role' => (
|
|
is => 'rw',
|
|
isa => 'Str',
|
|
default => 'default',
|
|
);
|
|
has 'ua' => (
|
|
is => 'rw',
|
|
isa => 'LWP::UserAgent',
|
|
lazy => 1,
|
|
builder => 'init_ua',
|
|
);
|
|
has 'base_uri' => (
|
|
is => 'ro',
|
|
isa => 'Str',
|
|
lazy => 1,
|
|
default => sub {
|
|
$_[0]->local_test
|
|
? ( length($_[0]->local_test)>1 ? $_[0]->local_test : 'https://127.0.0.1:1443' )
|
|
: $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443');
|
|
},
|
|
);
|
|
has 'name' => (
|
|
is => 'rw',
|
|
isa => 'Str',
|
|
);
|
|
#has 'subscriber_user' => (
|
|
# is => 'rw',
|
|
# isa => 'Str',
|
|
#);
|
|
#has 'subscriber_pass' => (
|
|
# is => 'rw',
|
|
# isa => 'Str',
|
|
#);
|
|
has 'embedded_resources' => (
|
|
is => 'rw',
|
|
isa => 'ArrayRef',
|
|
default => sub { [] },
|
|
);
|
|
has 'methods' => (
|
|
is => 'rw',
|
|
isa => 'HashRef',
|
|
default => sub { {
|
|
'collection' =>{
|
|
'all' => {map {$_ => 1} qw/GET HEAD OPTIONS POST/},
|
|
'allowed' => {map {$_ => 1} qw/GET HEAD OPTIONS POST/}, #some default
|
|
},
|
|
'item' =>{
|
|
'all' => {map {$_ => 1} qw/GET HEAD OPTIONS PUT PATCH POST DELETE/},
|
|
'allowed' => {map {$_ => 1} qw/GET HEAD OPTIONS PUT PATCH DELETE/}, #some default
|
|
},
|
|
} },
|
|
);
|
|
has 'content_type' => (
|
|
is => 'rw',
|
|
isa => 'HashRef',
|
|
default => sub {{
|
|
POST => 'application/json',
|
|
PUT => 'application/json',
|
|
PATCH => 'application/json-patch+json',
|
|
}},
|
|
);
|
|
#state variables - smth like predefined stash
|
|
has 'DATA_ITEM' => (
|
|
is => 'rw',
|
|
isa => 'Ref',
|
|
);
|
|
has 'DATA_ITEM_STORE' => (
|
|
is => 'rw',
|
|
isa => 'Ref',
|
|
);
|
|
after 'DATA_ITEM_STORE' => sub {
|
|
my $self = shift;
|
|
if(@_){
|
|
#$self->DATA_ITEM($self->DATA_ITEM_STORE);
|
|
$self->form_data_item;
|
|
}
|
|
};
|
|
has 'DATA_CREATED' => (
|
|
is => 'rw',
|
|
isa => 'HashRef',
|
|
builder => 'clear_data_created',
|
|
);
|
|
has 'KEEP_CREATED' =>(
|
|
is => 'rw',
|
|
isa => 'Bool',
|
|
default => 1,
|
|
);
|
|
has 'DATA_LOADED' => (
|
|
is => 'rw',
|
|
isa => 'HashRef',
|
|
default => sub{{}},
|
|
);
|
|
has 'URI_CUSTOM' =>(
|
|
is => 'rw',
|
|
isa => 'Str',
|
|
);
|
|
has 'QUERY_PARAMS' =>(
|
|
is => 'rw',
|
|
isa => 'Str',
|
|
default => '',
|
|
);
|
|
has 'URI_CUSTOM_STORE' =>(
|
|
is => 'rw',
|
|
isa => 'Str',
|
|
);
|
|
before 'URI_CUSTOM' => sub {
|
|
my $self = shift;
|
|
if(@_){
|
|
if($self->URI_CUSTOM_STORE){
|
|
die('Attempt to set custom uri second time without restore. Custom uri is not a stack. Clear or restore it first, please.');
|
|
}else{
|
|
$self->URI_CUSTOM_STORE($self->URI_CUSTOM);
|
|
}
|
|
}
|
|
};
|
|
has 'ENCODE_CONTENT' => (
|
|
is => 'rw',
|
|
isa => 'Str',
|
|
default => 'json',
|
|
);
|
|
sub set{
|
|
my $self = shift;
|
|
my %params = @_;
|
|
my $prev_state = {};
|
|
while (my ($variable, $value) = each %params){
|
|
$prev_state->{$variable} = $self->$variable;
|
|
$self->$variable($value);
|
|
}
|
|
return $prev_state;
|
|
}
|
|
sub get_cloned{
|
|
my $self = shift;
|
|
my @params = @_;
|
|
my $state = {};
|
|
foreach my $variable (@params){
|
|
$state->{$variable} = clone( $self->$variable );
|
|
}
|
|
return $state;
|
|
}
|
|
sub init_catalyst_config{
|
|
my $self = shift;
|
|
my $config;
|
|
my $restored;
|
|
if($self->cache_data){
|
|
$restored = $self->get_cached_data;
|
|
if($restored->{loaded} && $restored->{loaded}->{metaconfigdefs}){
|
|
$config = $restored->{loaded}->{metaconfigdefs}->{content};
|
|
}
|
|
}
|
|
if(!$config){
|
|
my($res,$list_collection,$req) = $self->check_item_get($self->normalize_uri('/api/metaconfigdefs/'));
|
|
my $location;
|
|
($config,$location) = $self->get_hal_from_collection($list_collection);
|
|
if($self->cache_data){
|
|
$restored->{loaded} //= {};
|
|
$restored->{loaded}->{metaconfigdefs} = { content => $config, location => $location };
|
|
store $restored, $self->data_cache_file;
|
|
}
|
|
}
|
|
$self->{catalyst_config} = $config;
|
|
$self->{panel_config} = $config->{file};
|
|
return $self->{catalyst_config};
|
|
}
|
|
sub init_ua {
|
|
my $self = shift;
|
|
my $ua = LWP::UserAgent->new;
|
|
my $uri = $self->base_uri;
|
|
$uri =~ s/^https?:\/\///;
|
|
my($user,$pass,$role,$realm) = $self->get_role_credentials();
|
|
$ua->credentials( $uri, $realm, $user, $pass);
|
|
$ua->ssl_opts(
|
|
verify_hostname => 0,
|
|
SSL_verify_mode => 0,
|
|
);
|
|
$self->init_ssl_cert($ua);
|
|
return $ua;
|
|
}
|
|
sub init_ssl_cert {
|
|
my ($self, $ua) = @_;
|
|
unless(-f $self->crt_path) {
|
|
my $res = $ua->post(
|
|
$self->base_uri . '/api/admincerts/',
|
|
Content_Type => 'application/json',
|
|
Content => '{}'
|
|
);
|
|
unless($res->is_success) {
|
|
die "failed to fetch client certificate: " . $res->status_line . "\n";
|
|
}
|
|
my $zip = $res->decoded_content;
|
|
my $z = IO::Uncompress::Unzip->new(\$zip, MultiStream => 0, Append => 1);
|
|
my $data;
|
|
while(!$z->eof() && (my $hdr = $z->getHeaderInfo())) {
|
|
unless($hdr->{Name} =~ /\.pem$/) {
|
|
# wrong file, just read stream, clear buffer and try next
|
|
while($z->read($data) > 0) {}
|
|
$data = undef;
|
|
$z->nextStream();
|
|
next;
|
|
}
|
|
while($z->read($data) > 0) {}
|
|
last;
|
|
}
|
|
$z->close();
|
|
unless($data) {
|
|
die "failed to find PEM file in client certificate zip file\n";
|
|
}
|
|
open my $fh, ">:raw", $self->crt_path or die "failed to open " . $self->crt_path . ": $!\n";
|
|
print $fh $data;
|
|
close $fh;
|
|
}
|
|
$ua->ssl_opts(
|
|
SSL_cert_file => $self->crt_path,
|
|
SSL_key_file => $self->crt_path,
|
|
);
|
|
}
|
|
sub runas {
|
|
my $self = shift;
|
|
my($role_in,$uri) = @_;
|
|
$uri //= $self->base_uri;
|
|
$uri =~ s/^https?:\/\///;
|
|
my($user,$pass,$role,$realm) = $self->get_role_credentials($role_in);
|
|
$self->runas_role($role);
|
|
$self->ua->credentials( $uri, $realm, $user, $pass);
|
|
}
|
|
sub get_role_credentials{
|
|
my $self = shift;
|
|
my($role) = @_;
|
|
my($user,$pass);
|
|
$role //= $self->runas_role // 'default';
|
|
my $realm;
|
|
if($role eq 'default' || $role eq 'admin'){
|
|
$user //= $ENV{API_USER} // 'administrator';
|
|
$pass //= $ENV{API_PASS} // 'administrator';
|
|
$realm = 'api_admin_http';
|
|
}elsif($role eq 'reseller'){
|
|
$user //= $ENV{API_USER_RESELLER} // 'api_test';
|
|
$pass //= $ENV{API_PASS_RESELLER} // 'api_test';
|
|
$realm = 'api_admin_http';
|
|
#}elsif($role eq 'subscriber'){
|
|
## I suggest here the same way as for admin and reseller - trough ENV variables
|
|
# $user //= $self->subscriber_user;
|
|
# $pass //= $self->subscriber_pass;
|
|
# $realm = 'subscriber';
|
|
}
|
|
return($user,$pass,$role,$realm);
|
|
}
|
|
sub clear_data_created{
|
|
my($self) = @_;
|
|
$self->DATA_CREATED({
|
|
ALL => {},
|
|
FIRST => undef,
|
|
});
|
|
return $self->DATA_CREATED;
|
|
}
|
|
sub form_data_item{
|
|
my($self, $data_cb, $data_cb_data) = @_;
|
|
$self->{DATA_ITEM} ||= clone($self->DATA_ITEM_STORE);
|
|
(defined $data_cb) and $data_cb->($self->DATA_ITEM,$data_cb_data);
|
|
return $self->DATA_ITEM;
|
|
}
|
|
sub get_id_from_created{
|
|
my($self, $created_info) = @_;
|
|
my $id = $created_info->{location} || '';
|
|
$id=~s/.*?\D(\d+)$/$1/gis;
|
|
return $id;
|
|
}
|
|
sub get_hal_name{
|
|
my($self,$name) = @_;
|
|
$name //= $self->name;
|
|
return "ngcp:".$name;
|
|
}
|
|
sub restore_uri_custom{
|
|
my($self) = @_;
|
|
$self->URI_CUSTOM($self->URI_CUSTOM_STORE);
|
|
$self->URI_CUSTOM_STORE(undef);
|
|
}
|
|
sub get_uri_collection{
|
|
my($self,$name) = @_;
|
|
$name //= $self->name;
|
|
return $self->normalize_uri("/api/".$name.($name ? "/" : "").($self->QUERY_PARAMS ? "?".$self->QUERY_PARAMS : ""));
|
|
}
|
|
sub get_uri_get{
|
|
my($self,$query_string, $name) = @_;
|
|
$name //= $self->name;
|
|
return $self->normalize_uri("/api/".$name.($query_string ? '/?' : '/' ).$query_string);
|
|
}
|
|
sub get_uri{
|
|
my($self,$add,$name) = @_;
|
|
$add //= '';
|
|
$name //= $self->name;
|
|
return $self->normalize_uri("/api/".$name.'/'.$add);
|
|
}
|
|
sub get_uri_item{
|
|
my($self,$name,$item) = @_;
|
|
my $resuri;
|
|
$item ||= $self->get_item_hal($name);
|
|
$resuri = $self->normalize_uri('/'.( $item->{location} // '' ));
|
|
return $resuri;
|
|
}
|
|
sub get_item_hal{
|
|
my($self,$name,$uri, $reload) = @_;
|
|
$name ||= $self->name;
|
|
my $resitem ;
|
|
if(!$uri && !$reload){
|
|
if(( $name eq $self->name ) && $self->DATA_CREATED->{FIRST}){
|
|
$resitem = $self->get_created_first;
|
|
}
|
|
if(!$resitem && $self->DATA_LOADED->{$name} && @{$self->DATA_LOADED->{$name}}){
|
|
$resitem = $self->DATA_LOADED->{$name}->[0];
|
|
}
|
|
}
|
|
if(!$resitem){
|
|
my ($reshal, $location,$total_count,$reshal_collection);
|
|
$uri //= $self->get_uri_collection($name)."?page=1&rows=1";
|
|
#print "uri=$uri;";
|
|
my($res,$list_collection,$req) = $self->check_item_get($self->normalize_uri($uri));
|
|
($reshal,$location,$total_count,$reshal_collection) = $self->get_hal_from_collection($list_collection,$name);
|
|
if($total_count || ('HASH' eq ref $reshal->{content} && $reshal->{content}->{total_count})){
|
|
$self->IS_EMPTY_COLLECTION(0);
|
|
$resitem = {
|
|
num => 1,
|
|
content => $reshal,
|
|
res => $res,
|
|
req => $req,
|
|
location => $location,
|
|
total_count => $total_count,
|
|
content_collection => $reshal_collection,
|
|
};
|
|
$self->DATA_LOADED->{$name} ||= [];
|
|
push @{$self->DATA_LOADED->{$name}}, $resitem;
|
|
}else{
|
|
$self->IS_EMPTY_COLLECTION(1);
|
|
}
|
|
}
|
|
return $resitem;
|
|
}
|
|
sub get_hal_from_collection{
|
|
my($self,$list_collection,$name,$number) = @_;
|
|
$number //= 0;
|
|
my $hal_name = $self->get_hal_name($name);
|
|
my($reshal,$reshal_collection,$location,$total_count);
|
|
$reshal_collection = $list_collection;
|
|
if( $list_collection->{_embedded} && ref $list_collection->{_embedded}->{$hal_name} eq 'ARRAY') {
|
|
#print Dumper ["get_hal_from_collection","1"];
|
|
$reshal = $list_collection->{_embedded}->{$hal_name}->[$number];
|
|
$location = $reshal->{_links}->{self}->{href};
|
|
$total_count = $reshal->{total_count} // $list_collection->{total_count};
|
|
} elsif( $list_collection->{_embedded} && ref $list_collection->{_embedded}->{$hal_name} eq 'HASH') {
|
|
#print Dumper ["get_hal_from_collection","2"];
|
|
$reshal = $list_collection->{_embedded}->{$hal_name};
|
|
$location = $reshal->{_links}->{self}->{href};
|
|
$total_count = $list_collection->{total_count};
|
|
#todo: check all collections
|
|
} elsif(ref $list_collection->{_links}->{$hal_name} eq "HASH") {
|
|
#found first subscriber
|
|
#print Dumper ["get_hal_from_collection","3"];
|
|
$reshal = $list_collection;
|
|
$location = $reshal->{_links}->{$hal_name}->{href};
|
|
$total_count = $reshal->{total_count};
|
|
} elsif( ref $list_collection eq 'HASH' && $list_collection->{_links}->{self}->{href}) {
|
|
#preferencedefs collection
|
|
#or empty collection, see "/api/pbxdeviceprofiles/?name=two"
|
|
$reshal = $list_collection;
|
|
$location = $reshal->{_links}->{self}->{href};
|
|
$total_count = $reshal->{total_count};
|
|
#print Dumper ["get_hal_from_collection","4",$total_count];
|
|
}
|
|
return ($reshal,$location,$total_count,$reshal_collection);
|
|
}
|
|
sub get_collection_hal{
|
|
my($self,$name, $uri, $reload, $page, $rows) = @_;
|
|
my (@reshals, $location,$total_count,$reshal_collection,$rescollection,$firstitem,$res,$list_collection,$req);
|
|
|
|
$name ||= $self->name;
|
|
if(!$uri || $uri !~/rows=\d+/){
|
|
if(!$rows){
|
|
$firstitem = $self->get_item_hal($name, $uri, $reload);
|
|
$rows = $total_count = $firstitem->{total_count};
|
|
}
|
|
if(!$rows){
|
|
return;
|
|
}
|
|
$page ||= 1;
|
|
$uri //= $self->get_uri_collection($name);
|
|
$uri .= ( ($uri =~/\?/) ? '&' : '?')."page=$page&rows=$rows";
|
|
}
|
|
if(!$firstitem || ($page != 1 || $rows != 1)){
|
|
($res,$list_collection,$req) = $self->check_item_get($self->normalize_uri($uri));
|
|
($reshals[0],$location,$total_count,$reshal_collection) = $self->get_hal_from_collection($list_collection,$name);
|
|
}else{
|
|
#the only risk here is that we get reshal_collection as content_collection, although potentially they may differ.
|
|
($res,$list_collection,$req,$reshals[0],$location,$total_count,$reshal_collection) = @{$firstitem}{qw/res content_collection req content location total_count content_collection/};
|
|
}
|
|
#print Dumper ["get_collection_hal",$total_count];
|
|
if($total_count){
|
|
$self->IS_EMPTY_COLLECTION(0);
|
|
#$self->DATA_LOADED->{$name} ||= [];
|
|
$rescollection = {
|
|
total_count => $total_count,
|
|
content => $reshal_collection,
|
|
res => $res,
|
|
req => $req,
|
|
collection => [],
|
|
};
|
|
my $add_item = sub{
|
|
my ($number,$location) = @_;
|
|
my $resitem = {
|
|
num => $number,
|
|
content => $reshals[$number],
|
|
location => $location,
|
|
};
|
|
#while no caching here
|
|
#push @{$self->DATA_LOADED->{$name}}, $resitem;
|
|
push @{$rescollection->{collection}}, $resitem;
|
|
};
|
|
$add_item->(0);
|
|
for(my $i=1; $i<$total_count; $i++){
|
|
($reshals[$i],$location) = $self->get_hal_from_collection($reshal_collection,$name,$i);
|
|
$add_item->($i,$location);
|
|
}
|
|
}else{
|
|
$self->IS_EMPTY_COLLECTION(1);
|
|
}
|
|
return $rescollection;
|
|
}
|
|
sub get_created_first{
|
|
my($self) = @_;
|
|
return $self->DATA_CREATED->{FIRST} ? $self->DATA_CREATED->{ALL}->{$self->DATA_CREATED->{FIRST}} : undef;
|
|
}
|
|
sub get_uri_current{
|
|
my($self) = @_;
|
|
$self->URI_CUSTOM and return $self->URI_CUSTOM;
|
|
return $self->get_uri_item;
|
|
}
|
|
|
|
sub encode_content{
|
|
my($self,$content, $type) = @_;
|
|
$type //= $self->ENCODE_CONTENT;
|
|
my %json_types = (
|
|
'application/json' => 1,
|
|
'application/json-patch+json' => 1,
|
|
'json' => 1,
|
|
);
|
|
#print "1. content=$content;\n\n";
|
|
if($content){
|
|
if( $json_types{$type} && (('HASH' eq ref $content) ||('ARRAY' eq ref $content)) ){
|
|
return JSON::to_json($content);
|
|
}elsif('multipart/form-data' eq $type
|
|
&& 'HASH' eq ref $content
|
|
&& $content->{json}
|
|
&& (('HASH' eq ref $content->{json}) || ( 'ARRAY' eq ref $content->{json} ) )
|
|
){
|
|
$content->{json} = JSON::to_json($content->{json});
|
|
return [
|
|
%{$content},
|
|
];
|
|
}
|
|
}
|
|
#print "2. content=$content;\n\n";
|
|
return $content;
|
|
}
|
|
sub request{
|
|
my($self,$req) = @_;
|
|
|
|
my $credentials = {};
|
|
(@$credentials{qw/user password/},undef,undef) = $self->get_role_credentials();
|
|
if($self->DEBUG){
|
|
my $curl = Test::HTTPRequestAsCurl::as_curl($req, credentials => $credentials );
|
|
print $req->as_string;
|
|
print "$curl\n\n";
|
|
}
|
|
if(!$self->DEBUG_ONLY){
|
|
my $res = $self->ua->request($req);
|
|
#draft of the debug mode
|
|
if($self->DEBUG){
|
|
if($res->code >= 400){
|
|
print Dumper $req;
|
|
print Dumper $res;
|
|
print Dumper $self->get_response_content($res);
|
|
#die;
|
|
}
|
|
}
|
|
return $res;
|
|
}
|
|
}
|
|
|
|
sub request_process{
|
|
my($self,$req) = @_;
|
|
#print $req->as_string;
|
|
my $res = $self->ua->request($req);
|
|
my $rescontent = $self->get_response_content($res);
|
|
return ($res,$rescontent,$req);
|
|
}
|
|
sub get_request_get{
|
|
my($self, $uri, $headers) = @_;
|
|
$headers ||= {};
|
|
$uri = $self->normalize_uri($uri);
|
|
my $req = HTTP::Request->new('GET', $uri);
|
|
foreach my $key (keys %$headers){
|
|
$req->header($key => $headers->{$key});
|
|
}
|
|
return $req ;
|
|
}
|
|
sub get_request_put{
|
|
my($self,$content,$uri) = @_;
|
|
$uri ||= $self->get_uri_current;
|
|
$uri = $self->normalize_uri($uri);
|
|
#This is for multipart/form-data cases
|
|
$content = $self->encode_content($content, $self->content_type->{PUT});
|
|
my $req = POST $uri,
|
|
Content_Type => $self->content_type->{PUT},
|
|
$content ? ( Content => $content ) : ();
|
|
$req->method('PUT');
|
|
$req->header('Prefer' => 'return=representation');
|
|
return $req;
|
|
}
|
|
sub get_request_patch{
|
|
my($self,$uri) = @_;
|
|
$uri ||= $self->get_uri_current;
|
|
$uri = $self->normalize_uri($uri);
|
|
my $req = HTTP::Request->new('PATCH', $uri);
|
|
$req->header('Prefer' => 'return=representation');
|
|
$req->header('Content-Type' => $self->content_type->{PATCH} );
|
|
return $req;
|
|
}
|
|
sub request_put{
|
|
my($self,$content,$uri) = @_;
|
|
$uri ||= $self->get_uri_current;
|
|
my $req = $self->get_request_put( $content, $self->normalize_uri($uri) );
|
|
my $res = $self->request($req);
|
|
if($res){
|
|
my $rescontent = $self->get_response_content($res);
|
|
return wantarray ? ($res,$rescontent,$req) : $res;
|
|
}
|
|
}
|
|
sub request_patch{
|
|
my($self,$content, $uri, $req) = @_;
|
|
$uri ||= $self->get_uri_current;
|
|
$req ||= $self->get_request_patch($uri);
|
|
#patch is always a json
|
|
$content = $self->encode_content($content, $self->content_type->{PATCH});
|
|
$content and $req->content($content);
|
|
my $res = $self->request($req);
|
|
if($res){
|
|
my $rescontent = $self->get_response_content($res);
|
|
#print Dumper [$res,$rescontent,$req];
|
|
return wantarray ? ($res,$rescontent,$req) : $res;
|
|
}
|
|
}
|
|
|
|
sub request_post{
|
|
my($self, $content, $uri, $req) = @_;
|
|
$uri ||= $self->get_uri_collection;
|
|
$uri = $self->normalize_uri($uri);
|
|
$content = $self->encode_content($content, $self->content_type->{POST} );
|
|
#form-data is set automatically, despite on $self->content_type->{POST}
|
|
$req ||= POST $uri,
|
|
Content_Type => $self->content_type->{POST},
|
|
Content => $content;
|
|
$req->header('Prefer' => 'return=representation');
|
|
my $res = $self->request($req);
|
|
my $rescontent = $self->get_response_content($res);
|
|
return wantarray ? ($res,$rescontent,$req,$content) : $res;
|
|
};
|
|
sub request_options{
|
|
my ($self,$uri) = @_;
|
|
# OPTIONS tests
|
|
my $req = HTTP::Request->new('OPTIONS', $self->normalize_uri($uri));
|
|
my $res = $self->request($req);
|
|
my $content = $self->get_response_content($res);
|
|
return($res,$content,$req);
|
|
}
|
|
|
|
sub request_delete{
|
|
my ($self,$uri) = @_;
|
|
# DELETE tests
|
|
#no auto rows for deletion
|
|
my $name = $self->name // '';
|
|
my $del_uri = $self->normalize_uri($uri);
|
|
my $req = HTTP::Request->new('DELETE', $del_uri);
|
|
my $res = $self->request($req);
|
|
if($res->code == 404){
|
|
#todo: if fake data will provide tree of the cascade deletion - it can be checked here, I think
|
|
diag($name.": Item $del_uri is absent already.");
|
|
}elsif($res->code == 204){
|
|
diag($name.": Item $del_uri deleted.");
|
|
}elsif(!$self->QUIET_DELETION){
|
|
$self->http_code_msg(204, "$name: check response from DELETE $uri", $res);
|
|
}
|
|
my $content = $self->get_response_content($res);
|
|
if($self->cache_data){
|
|
#my $restored = (-e $self->data_cache_file) ? retrieve($self->data_cache_file) : {};
|
|
#if('204' eq $res->code){
|
|
# $restored->{deleted}->{204}->{$uri} = 1;
|
|
#}
|
|
#$restored->{deleted}->{all}->{$uri} = [$res->code,$res->message];
|
|
#store $restored, $self->data_cache_file;
|
|
$self->replace_cached_data(sub{
|
|
my $restored = shift;
|
|
if('204' eq $res->code){
|
|
$restored->{deleted}->{204}->{$uri} = 1;
|
|
}
|
|
$restored->{deleted}->{all}->{$uri} = [$res->code,$res->message];
|
|
});
|
|
}
|
|
return($req,$res,$content);
|
|
}
|
|
|
|
sub request_get{
|
|
my($self,$uri,$req,$headers) = @_;
|
|
$uri = $self->normalize_uri($uri);
|
|
$req //= $self->get_request_get($uri,$headers);
|
|
my $res = $self->request($req);
|
|
my $content = $self->get_response_content($res);
|
|
return wantarray ? ($res, $content, $req) : $res;
|
|
}
|
|
sub get_response_content{
|
|
my($self,$res) = @_;
|
|
my $content = '';
|
|
if($res->decoded_content){
|
|
eval { $content = JSON::from_json($res->decoded_content); };
|
|
}
|
|
#print "get_response_content;\n";
|
|
#print Dumper [caller];
|
|
#print Dumper $content;
|
|
return $content;
|
|
}
|
|
sub normalize_uri{
|
|
my($self,$uri) = @_;
|
|
$uri ||= $self->get_uri_current // '';
|
|
if($uri !~/^http/i){
|
|
$uri = $self->base_uri.$uri;
|
|
}
|
|
return $uri;
|
|
}
|
|
############## end of test machine
|
|
############## start of test collection
|
|
|
|
#---------------- options, methods, hal format
|
|
|
|
sub check_options_collection{
|
|
my ($self, $uri) = @_;
|
|
# OPTIONS tests
|
|
$uri //= $self->get_uri_collection;
|
|
my $req = HTTP::Request->new('OPTIONS', $uri );
|
|
my $res = $self->request($req);
|
|
is($res->header('Accept-Post'), "application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-".$self->name, "$self->{name}: check Accept-Post header in options response");
|
|
$self->check_methods($res,'collection');
|
|
}
|
|
sub check_options_item{
|
|
my ($self,$uri) = @_;
|
|
# OPTIONS tests
|
|
$uri ||= $self->get_uri_current;
|
|
if(!$self->IS_EMPTY_COLLECTION){
|
|
my $req = HTTP::Request->new('OPTIONS', $uri);
|
|
my $res = $self->request($req);
|
|
$self->check_methods($res,'item');
|
|
}
|
|
}
|
|
sub check_methods{
|
|
my($self, $res, $area) = @_;
|
|
my $opts = $self->get_response_content($res);
|
|
$self->http_code_msg(200, "check $area options request", $res,$opts);
|
|
my @hopts = split /\s*,\s*/, $res->header('Allow');
|
|
ok(exists $opts->{methods} && ref $opts->{methods} eq "ARRAY", "$self->{name}: check for valid 'methods' in body");
|
|
foreach my $opt(keys %{$self->methods->{$area}->{all}} ) {
|
|
if(exists $self->methods->{$area}->{allowed}->{$opt}){
|
|
ok(grep(/^$opt$/, @hopts), "$self->{name}: check for existence of '$opt' in Allow header");
|
|
ok(grep(/^$opt$/, @{ $opts->{methods} }), "$self->{name}: check for existence of '$opt' in body");
|
|
}else{
|
|
ok(!grep(/^$opt$/, @hopts), "$self->{name}: check for absence of '$opt' in Allow header");
|
|
ok(!grep(/^$opt$/, @{ $opts->{methods} }), "$self->{name}: check for absence of '$opt' in body");
|
|
}
|
|
}
|
|
}
|
|
|
|
sub check_list_collection{
|
|
my($self, $check_embedded_cb) = @_;
|
|
my $nexturi = $self->get_uri_collection."?page=1&rows=5";
|
|
my @href = ();
|
|
do {
|
|
#print "nexturi=$nexturi;\n";
|
|
my ($res,$list_collection) = $self->check_item_get($nexturi);
|
|
my $selfuri = $self->normalize_uri($list_collection->{_links}->{self}->{href});
|
|
is($selfuri, $nexturi, "$self->{name}: check _links.self.href of collection");
|
|
my $colluri = URI->new($selfuri);
|
|
if(($list_collection->{total_count} && $list_collection->{total_count} > 0 ) || !$self->ALLOW_EMPTY_COLLECTION){
|
|
ok($list_collection->{total_count} > 0, "$self->{name}: check 'total_count' of collection");
|
|
}
|
|
|
|
my %q = $colluri->query_form;
|
|
ok(exists $q{page} && exists $q{rows}, "$self->{name}: check existence of 'page' and 'row' in 'self'");
|
|
my $page = int($q{page});
|
|
my $rows = int($q{rows});
|
|
ok($rows != 0, "check existance of the 'rows'");
|
|
if($page == 1) {
|
|
ok(!exists $list_collection->{_links}->{prev}->{href}, "$self->{name}: check absence of 'prev' on first page");
|
|
} else {
|
|
ok(exists $list_collection->{_links}->{prev}->{href}, "$self->{name}: check existence of 'prev'");
|
|
}
|
|
if(($rows != 0) && ($list_collection->{total_count} / $rows) <= $page) {
|
|
ok(!exists $list_collection->{_links}->{next}->{href}, "$self->{name}: check absence of 'next' on last page");
|
|
} else {
|
|
ok(exists $list_collection->{_links}->{next}->{href}, "$self->{name}: check existence of 'next'");
|
|
}
|
|
|
|
if($list_collection->{_links}->{next}->{href}) {
|
|
$nexturi = $self->normalize_uri($list_collection->{_links}->{next}->{href});
|
|
} else {
|
|
$nexturi = undef;
|
|
}
|
|
|
|
my $hal_name = $self->get_hal_name;
|
|
if(($list_collection->{total_count} && $list_collection->{total_count} > 0 ) || !$self->ALLOW_EMPTY_COLLECTION){
|
|
ok(((ref $list_collection->{_links}->{$hal_name} eq "ARRAY" ) ||
|
|
(ref $list_collection->{_links}->{$hal_name} eq "HASH" ) ), "$self->{name}: check if 'ngcp:".$self->name."' is array/hash-ref");
|
|
}
|
|
|
|
|
|
# it is really strange - we check that the only element of the _links will be hash - and after this treat _embedded as hash too
|
|
#the only thing that saves us - we really will not get into the if ever
|
|
if(ref $list_collection->{_links}->{$hal_name} eq "HASH") {
|
|
$self->check_embedded($list_collection->{_embedded}->{$hal_name}, $check_embedded_cb);
|
|
push @href, $list_collection->{_links}->{$hal_name}->{href};
|
|
} else {
|
|
foreach my $item_c(@{ $list_collection->{_links}->{$hal_name} }) {
|
|
push @href, $item_c->{href};
|
|
}
|
|
foreach my $item_c(@{ $list_collection->{_embedded}->{$hal_name} }) {
|
|
# these relations are only there if we have zones/fees, which is not the case with an empty model
|
|
$self->check_embedded($item_c, $check_embedded_cb);
|
|
push @href, $item_c->{_links}->{self}->{href};
|
|
}
|
|
}
|
|
} while($nexturi);
|
|
return \@href;
|
|
}
|
|
|
|
sub check_created_listed{
|
|
my($self,$listed) = @_;
|
|
my $created_items = clone($self->DATA_CREATED->{ALL});
|
|
$listed //= [];#to avoid error about not array reference
|
|
$created_items //= [];
|
|
foreach (@$listed){
|
|
delete $created_items->{$_};
|
|
}
|
|
is(scalar(keys %{$created_items}), 0, "$self->{name}: check if all created test items have been found in the list");
|
|
if(scalar(keys %{$created_items})){
|
|
#print Dumper $created_items;
|
|
#print Dumper $listed;
|
|
}
|
|
}
|
|
|
|
sub check_embedded {
|
|
my($self, $embedded, $check_embedded_cb) = @_;
|
|
defined $check_embedded_cb and $check_embedded_cb->($embedded);
|
|
foreach my $embedded_name(@{$self->embedded_resources}){
|
|
ok(exists $embedded->{_links}->{'ngcp:'.$embedded_name}, "$self->{name}: check presence of ngcp:$embedded_name relation");
|
|
}
|
|
}
|
|
|
|
#------------------- put bundle -----------
|
|
|
|
#------------------- put bundle -----------
|
|
|
|
sub check_put_content_type_empty{
|
|
my($self) = @_;
|
|
# check if it fails without content type
|
|
my $req = $self->get_request_put;
|
|
$req->remove_header('Content-Type');
|
|
$req->remove_header('Prefer');
|
|
$req->header('Prefer' => "return=minimal");
|
|
my($res,$content) = $self->request_process($req);
|
|
$self->http_code_msg(415, "check put missing content type", $res, $content);
|
|
}
|
|
sub check_put_content_type_wrong{
|
|
my($self) = @_;
|
|
# check if it fails with unsupported content type
|
|
my $req = $self->get_request_put;
|
|
$req->remove_header('Content-Type');
|
|
$req->header('Content-Type' => 'application/xxx');
|
|
my($res,$content) = $self->request_process($req);
|
|
$self->http_code_msg(415, "check put invalid content type", $res, $content);
|
|
}
|
|
sub check_put_prefer_wrong{
|
|
my($self) = @_;
|
|
# check if it fails with invalid Prefer
|
|
my $req = $self->get_request_put;
|
|
$req->remove_header('Prefer');
|
|
$req->header('Prefer' => "return=invalid");
|
|
my($res,$content) = $self->request_process($req);
|
|
$self->http_code_msg(400, "check put invalid prefer", $res, $content);
|
|
}
|
|
|
|
sub check_put_body_empty{
|
|
my($self) = @_;
|
|
# check if it fails with missing body
|
|
my $req = $self->get_request_put;
|
|
#$req->remove_header('Prefer');
|
|
#$req->header('Prefer' => "return=representation");
|
|
my($res,$content) = $self->request_process($req);
|
|
$self->http_code_msg(400, "check put no body", $res, $content);
|
|
}
|
|
|
|
sub check_put_bundle{
|
|
my($self) = @_;
|
|
$self->check_put_content_type_empty;
|
|
$self->check_put_content_type_wrong;
|
|
$self->check_put_prefer_wrong;
|
|
$self->check_put_body_empty;
|
|
}
|
|
|
|
#-------------------- patch bundle -------
|
|
sub check_patch_correct{
|
|
my($self,$content) = @_;
|
|
my ($res,$rescontent,$req) = $self->request_patch( $content );
|
|
$self->http_code_msg(200, "check patched item", $res, $rescontent);
|
|
is($rescontent->{_links}->{self}->{href}, $self->uri2location($req->uri), "$self->{name}: check patched self link");
|
|
is($rescontent->{_links}->{collection}->{href}, '/api/'.$self->name.'/', "$self->{name}: check patched collection link");
|
|
return ($res,$rescontent,$req);
|
|
}
|
|
|
|
sub check_patch_prefer_wrong{
|
|
my($self) = @_;
|
|
my $req = $self->get_request_patch;
|
|
$req->remove_header('Prefer');
|
|
$req->header('Prefer' => 'return=minimal');
|
|
my ($res,$content) = $self->request_process($req);
|
|
$self->http_code_msg(415, "check patch invalid prefer", $res, $content);
|
|
}
|
|
sub check_patch_content_type_empty{
|
|
my($self) = @_;
|
|
my $req = $self->get_request_patch;
|
|
$req->remove_header('Content-Type');
|
|
my ($res,$content) = $self->request_process($req);
|
|
$self->http_code_msg(415, "check patch missing media type", $res, $content);
|
|
}
|
|
|
|
sub check_patch_content_type_wrong{
|
|
my($self) = @_;
|
|
my $req = $self->get_request_patch;
|
|
$req->remove_header('Content-Type');
|
|
$req->header('Content-Type' => 'application/xxx');
|
|
my($res,$content) = $self->request_process($req);
|
|
$self->http_code_msg(415, "check patch invalid media type", $res, $content);
|
|
}
|
|
|
|
sub check_patch_body_empty{
|
|
my($self) = @_;
|
|
my ($res,$content,$req) = $self->request_patch;
|
|
$self->http_code_msg(400, "check patch missing body", $res, $content);
|
|
like($content->{message}, qr/is missing a message body/, "$self->{name}: check patch missing body response");
|
|
}
|
|
|
|
sub check_patch_body_notarray{
|
|
my($self) = @_;
|
|
my ($res,$content,$req) = $self->request_patch(
|
|
{ foo => 'bar' },
|
|
);
|
|
$self->http_code_msg(400, "check patch no array body", $res, $content);
|
|
like($content->{message}, qr/must be an array/, "$self->{name}: check patch missing body response");
|
|
}
|
|
|
|
sub check_patch_op_missed{
|
|
my($self) = @_;
|
|
my ($res,$content,$req) = $self->request_patch(
|
|
[{ foo => 'bar' }],
|
|
);
|
|
$self->http_code_msg(400, "check patch no op in body", $res, $content);
|
|
like($content->{message}, qr/must have an 'op' field/, "$self->{name}: check patch no op in body response");
|
|
}
|
|
|
|
sub check_patch_op_wrong{
|
|
my($self) = @_;
|
|
my ($res,$content,$req) = $self->request_patch(
|
|
[{ op => 'bar' }],
|
|
);
|
|
$self->http_code_msg(400, "check patch invalid op in body", $res, $content);
|
|
like($content->{message}, qr/Invalid PATCH op /, "$self->{name}: check patch no op in body response");
|
|
}
|
|
|
|
sub check_patch_opreplace_paramsmiss{
|
|
my($self) = @_;
|
|
my ($res,$content,$req) = $self->request_patch(
|
|
[{ op => 'replace' }],
|
|
);
|
|
$self->http_code_msg(400, "check patch missing fields for op", $res, $content);
|
|
like($content->{message}, qr/Missing PATCH keys /, "$self->{name}: check patch missing fields for op response");
|
|
}
|
|
|
|
sub check_patch_opreplace_paramsextra{
|
|
my($self) = @_;
|
|
my ($res,$content,$req) = $self->request_patch(
|
|
[{ op => 'replace', path => '/foo', value => 'bar', invalid => 'sna' }],
|
|
);
|
|
$self->http_code_msg(400, "check patch extra fields for op", $res, $content);
|
|
like($content->{message}, qr/Invalid PATCH key /, "$self->{name}: check patch extra fields for op response");
|
|
}
|
|
sub check_patch_path_wrong{
|
|
my($self) = @_;
|
|
my ($res,$content,$req) = $self->request_patch(
|
|
[ { op => 'replace', path => '/some/path', value => 'invalid' } ],
|
|
);
|
|
$self->http_code_msg(422, "check patched invalid path", $res, $content);
|
|
}
|
|
|
|
sub check_patch_bundle{
|
|
my($self) = @_;
|
|
#$self->check_patch_prefer_wrong;
|
|
$self->check_patch_content_type_wrong;
|
|
$self->check_patch_content_type_empty;
|
|
$self->check_patch_body_empty;
|
|
$self->check_patch_body_notarray;
|
|
$self->check_patch_op_missed;
|
|
$self->check_patch_op_wrong;
|
|
$self->check_patch_opreplace_paramsmiss;
|
|
$self->check_patch_opreplace_paramsextra;
|
|
$self->check_patch_path_wrong;
|
|
}
|
|
sub check_bundle{
|
|
my($self) = @_;
|
|
$self->check_options_collection();
|
|
# iterate over collection to check next/prev links and status
|
|
my $listed=[];
|
|
if($self->methods->{collection}->{allowed}->{GET}){
|
|
$listed = $self->check_list_collection();
|
|
$self->check_created_listed($listed);
|
|
}
|
|
# test model item
|
|
if(@$listed && !$self->NO_ITEM_MODULE){
|
|
$self->check_options_item;
|
|
if(!$self->IS_EMPTY_COLLECTION){
|
|
if(exists $self->methods->{'item'}->{allowed}->{'PUT'}){
|
|
$self->check_put_bundle;
|
|
}
|
|
if(exists $self->methods->{'item'}->{allowed}->{'PATCH'}){
|
|
$self->check_patch_bundle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub check_item_get{
|
|
my($self, $uri, $msg) = @_;
|
|
$msg //= '';
|
|
$uri ||= $self->get_uri_current;
|
|
$uri = $self->normalize_uri($uri);
|
|
my ($res, $content, $req) = $self->request_get($uri);
|
|
$self->http_code_msg(200, $msg.($msg?": ":"")."fetch uri: $uri", $res);
|
|
return wantarray ? ($res, $content, $req) : $res;
|
|
}
|
|
sub process_data{
|
|
my($self, $data_cb, $data_in, $data_cb_data) = @_;
|
|
my $data = $data_in || clone($self->DATA_ITEM);
|
|
defined $data_cb and $data_cb->($data, $data_cb_data);
|
|
return $data;
|
|
}
|
|
sub get_item_post_content{
|
|
my($self, $data_cb, $data_in, $data_cb_data) = @_;
|
|
my $data = $self->process_data($data_cb, $data_in, $data_cb_data);
|
|
#print Dumper $data;
|
|
my $content = {
|
|
$data->{json} ? ( json => JSON::to_json(delete $data->{json}) ) : (),
|
|
%$data,
|
|
};
|
|
return $content;
|
|
}
|
|
sub check_item_post{
|
|
my($self, $data_cb, $data_in, $data_cb_data) = @_;
|
|
my $content = $self->get_item_post_content($data_cb, $data_in, $data_cb_data);
|
|
#print Dumper $content;
|
|
my ($res,$rescontent,$req) = $self->request_post($content);#,$uri,$req
|
|
return wantarray ? ($res,$rescontent,$req,$content) : $res;
|
|
};
|
|
sub check_item_delete{
|
|
my($self, $uri, $msg) = @_;
|
|
my $name = $self->name // '';
|
|
$uri = $self->normalize_uri($uri);
|
|
my ($req,$res,$content) = $self->request_delete($uri);#,$uri,$req
|
|
$self->http_code_msg(204, "$name: check delete item $uri",$res,$content);
|
|
return ($req,$res,$content);
|
|
};
|
|
sub check_create_correct{
|
|
my($self, $number, $uniquizer_cb) = @_;
|
|
if(!$self->KEEP_CREATED){
|
|
$self->clear_data_created;
|
|
}
|
|
$self->DATA_CREATED->{ALL} //= {};
|
|
my @created = ();
|
|
for(my $i = 1; $i <= $number; ++$i) {
|
|
my $created_info={};
|
|
my ($res, $content, $req, $content_post) = $self->check_item_post( $uniquizer_cb , undef, { i => $i } );
|
|
$self->http_code_msg(201, "create test item '".$self->name."' $i",$res,$content);
|
|
my $location = $res->header('Location');
|
|
if($location){
|
|
#some interfaces (e.g. subscribers) don't provide hal after creation - is it correct, by the way?
|
|
my $get ={};
|
|
if(!$content){
|
|
@$get{qw/res_get content_get req_get/} = $self->check_item_get($location,"no object returned after POST");
|
|
}
|
|
$created_info = {
|
|
num => $i,
|
|
content => $content ? $content : $get->{content_get},
|
|
res => $res,
|
|
req => $req,
|
|
location => $location,
|
|
content_post => $content_post,
|
|
%$get,
|
|
};
|
|
push @created, $created_info;
|
|
$self->DATA_CREATED->{ALL}->{$location} = $created_info;
|
|
$self->DATA_CREATED->{FIRST} = $location unless $self->DATA_CREATED->{FIRST};
|
|
}
|
|
}
|
|
return \@created;
|
|
}
|
|
|
|
sub clear_test_data_all{
|
|
my($self,$uri,$strict) = @_;
|
|
my $name = $self->name // '';
|
|
my @uris = $uri ? (('ARRAY' eq ref $uri) ? @$uri : ($uri)) : keys %{ $self->DATA_CREATED->{ALL} };
|
|
foreach my $del_uri(@uris){
|
|
$del_uri = $self->normalize_uri($del_uri);
|
|
my($req,$res,$content);
|
|
if($strict){#for particular deletion test
|
|
($req,$res,$content) = $self->check_item_delete($del_uri);
|
|
}else{
|
|
($req,$res,$content) = $self->request_delete($del_uri);
|
|
}
|
|
}
|
|
$self->clear_data_created();
|
|
return \@uris;
|
|
}
|
|
sub clear_test_data_dependent{
|
|
my($self,$uri,$strict) = @_;
|
|
my $name = $self->name // '';
|
|
my $del_uri = $self->normalize_uri($uri);
|
|
my($req,$res,$content);
|
|
if($strict){#for particular deletion test
|
|
($req,$res,$content) = $self->check_item_delete($del_uri);
|
|
}else{
|
|
($req,$res,$content) = $self->request_delete($del_uri);
|
|
}
|
|
return ('204' eq $res->code);
|
|
}
|
|
|
|
sub check_get2put{
|
|
my($self, $put_in, $get_in) = @_;
|
|
|
|
my($put_out,$get_out);
|
|
|
|
$get_in //= {};
|
|
$put_in //= {};
|
|
$get_in->{ignore_fields} //= [];
|
|
$put_in->{ignore_fields} //= [];
|
|
$get_in->{uri} //= $put_in->{uri};
|
|
$put_in->{uri} //= $get_in->{uri};
|
|
|
|
@$get_out{qw/response content request/} = $self->check_item_get($get_in->{uri});
|
|
$put_out->{content_in} = clone($get_out->{content});
|
|
delete $put_out->{content_in}->{_links};
|
|
delete $put_out->{content_in}->{_embedded};
|
|
# check if put is ok
|
|
(defined $put_in->{data_cb}) and $put_in->{data_cb}->($put_out->{content_in});
|
|
@{$put_out}{qw/response content request/} = $self->request_put( $put_out->{content_in}, $put_in->{uri} );
|
|
foreach my $field (@{$get_in->{ignore_fields}}, @{$put_in->{ignore_fields}}){
|
|
delete $get_out->{content}->{$field};
|
|
delete $put_out->{content}->{$field};
|
|
}
|
|
$self->http_code_msg(200, "check_get2put: check put successful", $put_out->{response}, $put_out->{content} );
|
|
is_deeply($get_out->{content}, $put_out->{content}, "$self->{name}: check_get2put: check put if unmodified put returns the same");
|
|
return ($put_out,$get_out);
|
|
}
|
|
|
|
sub check_put2get{
|
|
my($self, $put_in, $get_in, $check_cb_or_switch) = @_;
|
|
|
|
my($put_out,$get_out);
|
|
|
|
$get_in //= {};
|
|
$put_in->{uri} //= $put_in->{location};
|
|
$get_in->{uri} //= $put_in->{uri};
|
|
$put_in->{uri} //= $get_in->{uri};
|
|
$get_out->{uri} = $get_in->{uri};
|
|
$put_in->{data_in} //= $put_in->{content};
|
|
$put_out->{content_in} = $self->process_data($put_in->{data_cb}, $put_in->{data_in});
|
|
@{$put_out}{qw/response content request/} = $self->request_put( $put_out->{content_in}, $put_in->{uri} );
|
|
$self->http_code_msg(200, "check_put2get: check put successful",$put_out->{response}, $put_out->{content});
|
|
|
|
@{$get_out}{qw/response content request/} = $self->check_item_get($get_out->{uri});
|
|
delete $get_out->{content}->{_links};
|
|
delete $get_out->{content}->{_embedded};
|
|
delete $put_out->{content_in}->{_links};
|
|
delete $put_out->{content_in}->{_embedded};
|
|
my $item_id = delete $get_out->{content}->{id};
|
|
my $item_id_in = delete $put_out->{content_in}->{id};
|
|
if('CODE' eq ref $check_cb_or_switch){
|
|
$check_cb_or_switch->($put_out,$get_out);
|
|
}
|
|
if(!$check_cb_or_switch || 'CODE' eq ref $check_cb_or_switch){
|
|
is_deeply($get_out->{content}, $put_out->{content_in}, "$self->{name}: check_put2get: check PUTed item against GETed item");
|
|
}
|
|
$get_out->{content}->{id} = $item_id;
|
|
$put_out->{content_in}->{id} = $item_id_in;
|
|
return ($put_out,$get_out);
|
|
}
|
|
|
|
sub check_post2get{
|
|
my($self, $post_in, $get_in) = @_;
|
|
$get_in //= {};
|
|
#$post = {data_in=>,data_cb=>};
|
|
#$get = {uri=>}
|
|
#return
|
|
#$post={response,content,request,data,location}
|
|
#$get=={response,content,request,uri}
|
|
my($post_out,$get_out);
|
|
@{$post_out}{qw/response content request data/} = $self->check_item_post( $post_in->{data_cb}, $post_in->{data_in} );
|
|
$self->http_code_msg(201, "check_post2get: POST item '".$self->name."' for check_post2get", @{$post_out}{qw/response content/});
|
|
$post_out->{location} = $self->normalize_uri(($post_out->{response}->header('Location') // ''));
|
|
|
|
$get_out->{uri} = $get_in->{uri} // $post_out->{location};
|
|
@{$get_out}{qw/response content request/} = $self->check_item_get( $get_out->{uri}, "check_post2get: fetch POSTed test '".$self->name."'" );
|
|
|
|
delete $get_out->{content}->{_links};
|
|
my $item_id = delete $get_out->{content}->{id};
|
|
is_deeply($post_out->{data}, $get_out->{content}, "$self->{name}: check_post2get: check POSTed '".$self->name."' against fetched");
|
|
$get_out->{content}->{id} = $item_id;
|
|
|
|
return ($post_out, $get_out);
|
|
}
|
|
sub put_and_get{
|
|
my($self, $put_in, $get_in) = @_;
|
|
my($put_out,$put_get_out,$get_out);
|
|
@{$put_out}{qw/response content request/} = $self->request_put($put_in->{content},$put_in->{uri});
|
|
@{$put_get_out}{qw/response content request/} = $self->check_item_get($put_in->{uri});
|
|
@{$get_out}{qw/response content request/} = $self->check_item_get($get_in->{uri});
|
|
delete $put_get_out->{content_in}->{_links};
|
|
delete $put_get_out->{content_in}->{_embedded};
|
|
is_deeply($put_in->{content}, $put_get_out->{content}, "$self->{name}: check that '$put_in->{uri}' was updated on put;");
|
|
return ($put_out,$put_get_out,$get_out);
|
|
}
|
|
|
|
####--------------------------utils
|
|
sub hash2params{
|
|
my($self,$hash) = @_;
|
|
return join '&', map {$_.'='.uri_escape($hash->{$_})} keys %{ $hash };
|
|
}
|
|
sub resource_fill_file{
|
|
my($self,$filename,$data) = @_;
|
|
$data //= 'aaa';
|
|
write_file($filename,$data);
|
|
}
|
|
sub resource_clear_file{
|
|
my $cmd = "echo -n '' > $_[1]";
|
|
print "cmd=$cmd;\n";
|
|
`$cmd`;
|
|
}
|
|
sub get_id_from_hal{
|
|
my($self,$hal,$name) = @_;
|
|
$name //= $self->name;
|
|
(my ($id)) = $hal->{_embedded}->{'ngcp:'.$name}->{_links}{self}{href} =~ m!${name}/([0-9]*)$!;
|
|
return $id;
|
|
}
|
|
sub uri2location{
|
|
my($self,$uri) = @_;
|
|
$uri=~s/^.*?(\/api\/.*$)/$1/;
|
|
return $uri;
|
|
}
|
|
sub http_code_msg{
|
|
my($self,$code,$message,$res,$content) = @_;
|
|
my $message_res;
|
|
my $name = $self->{name} // '';
|
|
$message //= '';
|
|
if ( ($res->code < 300) || ( $code >= 300 ) ) {
|
|
my $res_message = $res->message // '';
|
|
my $content_message = 'HASH' eq ref $content ? $content->{message} // '' : '' ;
|
|
$message_res = $message.' (' . $res_message . ': ' . $content_message . ')';
|
|
} else {
|
|
$content //= $self->get_response_content($res);
|
|
if (defined $content && $content && defined $content->{message}) {
|
|
$message_res = "$name: ".$message . ' (' . $res->message . ': ' . $content->{message} . ')';
|
|
} else {
|
|
$message_res = "$name: ".$message . ' (' . $res->message . ')';
|
|
}
|
|
}
|
|
$code and is($res->code, $code, $message_res);
|
|
}
|
|
sub get_cached_data{
|
|
my($self) = @_;
|
|
return (-e $self->data_cache_file) ? retrieve($self->data_cache_file) : {};
|
|
}
|
|
|
|
sub replace_cached_data{
|
|
my($self,$data_callback,$restored) = @_;
|
|
$restored //= $self->get_cached_data;
|
|
$data_callback->($restored);
|
|
store $restored,$self->data_cache_file;
|
|
return $restored;
|
|
}
|
|
|
|
sub clear_cache{
|
|
my($self) = @_;
|
|
if(-e $self->data_cache_file){
|
|
my $cmd = "rm ".$self->data_cache_file;
|
|
`$cmd`;
|
|
}
|
|
}
|
|
|
|
1;
|