parent
c52e8da48f
commit
ec9c71dbe3
@ -0,0 +1,68 @@
|
||||
package NGCP::Panel::Utils::Device;
|
||||
|
||||
|
||||
use strict;
|
||||
|
||||
sub process_connectable_models{
|
||||
my ($c, $just_created, $devmod, $connectable_models) = @_;
|
||||
my $schema = $c->model('DB');
|
||||
if($connectable_models){
|
||||
my @columns = ('device_id' , 'extension_id');
|
||||
if('extension' eq $devmod->type){
|
||||
#extension can be connected to other extensions? If I remember right - yes.
|
||||
@columns = reverse @columns;
|
||||
}else{
|
||||
#we defenitely can't connect phone to phone
|
||||
my $phone2phone = $schema->resultset('autoprov_devices')->search_rs({
|
||||
'type' => 'phone',
|
||||
'id' => { 'in' => $connectable_models },
|
||||
});
|
||||
if($phone2phone->first){
|
||||
die("Phone can't be connected to the phone as extension.");
|
||||
}
|
||||
}
|
||||
if(!$just_created){
|
||||
#we don't need to clear old relations, because we just created this device
|
||||
$schema->resultset('autoprov_device_extensions')->search_rs({
|
||||
$columns[0] => $devmod->id,
|
||||
})->delete;
|
||||
}
|
||||
foreach my $connected_id(@$connectable_models){
|
||||
if($devmod->id == $connected_id){
|
||||
die("Device can't be connected to itself as extension.");
|
||||
}
|
||||
$schema->resultset('autoprov_device_extensions')->create({
|
||||
$columns[0] => $devmod->id,
|
||||
$columns[1] => $connected_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
NGCP::Panel::Utils::Device
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Diffrent business logic method for pbx devices
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 process_connectable_models
|
||||
|
||||
Process data tolink devices and extensions in the DB.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Irina Peshinskaya C<< <ipeshinskaya@sipwise.com> >>
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
# vim: set tabstop=4 expandtab:
|
@ -0,0 +1,586 @@
|
||||
package NGCP::Panel::Utils::Test::Collection;
|
||||
|
||||
use strict;
|
||||
use Test::More;
|
||||
use Moose;
|
||||
use JSON;
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Request::Common;
|
||||
use Net::Domain qw(hostfqdn);
|
||||
use URI;
|
||||
use Clone qw/clone/;
|
||||
|
||||
use Data::Dumper;
|
||||
|
||||
|
||||
has 'ua' => (
|
||||
is => 'rw',
|
||||
isa => 'LWP::UserAgent',
|
||||
builder => '_init_ua',
|
||||
);
|
||||
has 'base_uri' => (
|
||||
is => 'ro',
|
||||
isa => 'Str',
|
||||
default => 'https://192.168.56.7:1444' || $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443'),
|
||||
);
|
||||
has 'name' => (
|
||||
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 'URI_CUSTOM' =>(
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
);
|
||||
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 _init_ua {
|
||||
my $self = shift;
|
||||
my $valid_ssl_client_cert = $ENV{API_SSL_CLIENT_CERT} ||
|
||||
"/etc/ngcp-panel/api_ssl/NGCP-API-client-certificate.pem";
|
||||
my $valid_ssl_client_key = $ENV{API_SSL_CLIENT_KEY} ||
|
||||
$valid_ssl_client_cert;
|
||||
my $ssl_ca_cert = $ENV{ API_SSL_CA_CERT} || "/etc/ngcp-panel/api_ssl/api_ca.crt";
|
||||
my $ua = LWP::UserAgent->new;
|
||||
$ua->ssl_opts(
|
||||
SSL_cert_file => $valid_ssl_client_cert,
|
||||
SSL_key_file => $valid_ssl_client_key,
|
||||
SSL_ca_file => $ssl_ca_cert,
|
||||
);
|
||||
#$ua->credentials( $self->base_uri, '', 'administrator', 'administrator' );
|
||||
#$ua->ssl_opts(
|
||||
# verify_hostname => 0,
|
||||
# SSL_verify_mode => 0x00,
|
||||
#);
|
||||
return $ua;
|
||||
};
|
||||
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_hal_name{
|
||||
my($self) = @_;
|
||||
return "ngcp:".$self->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) = @_;
|
||||
return $self->base_uri."/api/".$self->name.($self->name ? "/" : "");
|
||||
}
|
||||
sub get_uri_firstitem{
|
||||
my($self) = @_;
|
||||
if(!$self->DATA_CREATED->{FIRST}){
|
||||
my($res,$list_collection,$req) = $self->check_item_get($self->get_uri_collection."?page=1&rows=1");
|
||||
my $hal_name = $self->get_hal_name;
|
||||
if(ref $list_collection->{_links}->{$hal_name} eq "HASH") {
|
||||
$self->DATA_CREATED->{FIRST} = $list_collection->{_links}->{$hal_name}->{href};
|
||||
} else {
|
||||
$self->DATA_CREATED->{FIRST} = $list_collection->{_embedded}->{$hal_name}->[0]->{_links}->{self}->{href};
|
||||
}
|
||||
}
|
||||
$self->DATA_CREATED->{FIRST} //= '';
|
||||
return $self->base_uri.'/'.$self->DATA_CREATED->{FIRST};
|
||||
}
|
||||
|
||||
sub get_uri_current{
|
||||
my($self) = @_;
|
||||
$self->URI_CUSTOM and return $self->URI_CUSTOM;
|
||||
return $self->get_uri_firstitem;
|
||||
}
|
||||
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 "content=$content;\n\n";
|
||||
if($content){
|
||||
if( $json_types{$type} && (('HASH' eq ref $content) ||('ARRAY' eq ref $content)) ){
|
||||
return JSON::to_json($content);
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
sub request{
|
||||
my($self,$req) = @_;
|
||||
#print $req->as_string;
|
||||
$self->ua->request($req);
|
||||
}
|
||||
|
||||
sub get_request_put{
|
||||
my($self,$content,$uri) = @_;
|
||||
$uri ||= $self->get_uri_current;
|
||||
#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->{POST},
|
||||
$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;
|
||||
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, $uri );
|
||||
my $res = $self->request($req);
|
||||
#print Dumper $res;
|
||||
|
||||
my $err = $res->decoded_content ? JSON::from_json($res->decoded_content) : '';
|
||||
return wantarray ? ($res,$err,$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);
|
||||
my $err = $res->decoded_content ? JSON::from_json($res->decoded_content) : '';
|
||||
#print Dumper [$res,$err,$req];
|
||||
return ($res,$err,$req);
|
||||
}
|
||||
|
||||
sub request_post{
|
||||
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);
|
||||
my $content = {
|
||||
$data->{json} ? ( json => JSON::to_json(delete $data->{json}) ) : (),
|
||||
%$data,
|
||||
};
|
||||
$content = $self->encode_content($content, $self->content_type->{POST} );
|
||||
#form-data is set automatically, despite on $self->content_type->{POST}
|
||||
my $req = POST $self->get_uri_collection,
|
||||
Content_Type => $self->content_type->{POST},
|
||||
Content => $content;
|
||||
my $res = $self->request($req);
|
||||
my $err = $res->decoded_content ? JSON::from_json($res->decoded_content) : '';
|
||||
return ($res,$err,$req);
|
||||
};
|
||||
|
||||
|
||||
|
||||
sub request_options{
|
||||
my ($self,$uri) = @_;
|
||||
# OPTIONS tests
|
||||
$uri ||= $self->get_uri_current;
|
||||
my $req = HTTP::Request->new('OPTIONS', $uri);
|
||||
my $res = $self->request($req);
|
||||
my $content = $res->decoded_content ? JSON::from_json($res->decoded_content) : '';
|
||||
return($req,$res,$content);
|
||||
}
|
||||
sub request_delete{
|
||||
my ($self,$uri) = @_;
|
||||
# DELETE tests
|
||||
#no auto rows for deletion
|
||||
my $req = HTTP::Request->new('DELETE', $uri);
|
||||
my $res = $self->request($req);
|
||||
my $content = $res->decoded_content ? JSON::from_json($res->decoded_content) : '';
|
||||
return($req,$res,$content);
|
||||
}
|
||||
sub check_options_collection{
|
||||
my ($self) = @_;
|
||||
# OPTIONS tests
|
||||
my $req = HTTP::Request->new('OPTIONS', $self->get_uri_collection );
|
||||
my $res = $self->request($req);
|
||||
is($res->header('Accept-Post'), "application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-".$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;
|
||||
my $req = HTTP::Request->new('OPTIONS', $uri);
|
||||
my $res = $self->request($req);
|
||||
$self->check_methods($res,'item');
|
||||
}
|
||||
sub check_methods{
|
||||
my($self, $res, $area) = @_;
|
||||
is($res->code, 200, "check $area options request");
|
||||
my $opts = JSON::from_json($res->decoded_content);
|
||||
my @hopts = split /\s*,\s*/, $res->header('Allow');
|
||||
ok(exists $opts->{methods} && ref $opts->{methods} eq "ARRAY", "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), "check for existence of '$opt' in Allow header");
|
||||
ok(grep(/^$opt$/, @{ $opts->{methods} }), "check for existence of '$opt' in body");
|
||||
}else{
|
||||
ok(!grep(/^$opt$/, @hopts), "check for absence of '$opt' in Allow header");
|
||||
ok(!grep(/^$opt$/, @{ $opts->{methods} }), "check for absence of '$opt' in body");
|
||||
}
|
||||
}
|
||||
}
|
||||
sub check_create_correct{
|
||||
my($self, $number, $uniquizer_cb, $keep_data) = @_;
|
||||
if(!$keep_data){
|
||||
$self->clear_data_created;
|
||||
}
|
||||
$self->DATA_CREATED->{ALL} //= {};
|
||||
for(my $i = 1; $i <= $number; ++$i) {
|
||||
my ($res, $err) = $self->request_post( $uniquizer_cb , undef, { i => $i} );
|
||||
is($res->code, 201, "create test item $i");
|
||||
my $location = $res->header('Location');
|
||||
if($location){
|
||||
$self->DATA_CREATED->{ALL}->{$location} = $i;
|
||||
$self->DATA_CREATED->{FIRST} = $location unless $self->DATA_CREATED->{FIRST};
|
||||
}
|
||||
}
|
||||
}
|
||||
sub check_delete_use_created{
|
||||
my($self,$uri) = @_;
|
||||
my @uris = $uri ? ($uri) : keys $self->DATA_CREATED->{ALL};
|
||||
foreach my $del_uri(@uris){
|
||||
my($req,$res,$content) = $self->request_delete($self->base_uri.$del_uri);
|
||||
is($res->code, 204, "check delete item $del_uri");
|
||||
}
|
||||
}
|
||||
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 = $self->ua->get($nexturi);
|
||||
is($res->code, 200, "fetch collection page");
|
||||
my $list_collection = JSON::from_json($res->decoded_content);
|
||||
my $selfuri = $self->base_uri . $list_collection->{_links}->{self}->{href};
|
||||
is($selfuri, $nexturi, "check _links.self.href of collection");
|
||||
my $colluri = URI->new($selfuri);
|
||||
|
||||
ok($list_collection->{total_count} > 0, "check 'total_count' of collection");
|
||||
|
||||
my %q = $colluri->query_form;
|
||||
ok(exists $q{page} && exists $q{rows}, "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}, "check absence of 'prev' on first page");
|
||||
} else {
|
||||
ok(exists $list_collection->{_links}->{prev}->{href}, "check existence of 'prev'");
|
||||
}
|
||||
if(($rows != 0) && ($list_collection->{total_count} / $rows) <= $page) {
|
||||
ok(!exists $list_collection->{_links}->{next}->{href}, "check absence of 'next' on last page");
|
||||
} else {
|
||||
ok(exists $list_collection->{_links}->{next}->{href}, "check existence of 'next'");
|
||||
}
|
||||
|
||||
if($list_collection->{_links}->{next}->{href}) {
|
||||
$nexturi = $self->base_uri . $list_collection->{_links}->{next}->{href};
|
||||
} else {
|
||||
$nexturi = undef;
|
||||
}
|
||||
|
||||
my $hal_name = $self->get_hal_name;
|
||||
ok(((ref $list_collection->{_links}->{$hal_name} eq "ARRAY" ) ||
|
||||
(ref $list_collection->{_links}->{$hal_name} eq "HASH" ) ), "check if 'ngcp:".$self->name."' is array/hash-ref");
|
||||
|
||||
my $check_embedded = sub {
|
||||
my($embedded) = @_;
|
||||
defined $check_embedded_cb and $check_embedded_cb->($embedded);
|
||||
foreach my $embedded_name(@{$self->embedded_resources}){
|
||||
ok(exists $embedded->{_links}->{'ngcp:'.$embedded_name}, "check presence of ngcp:$embedded_name relation");
|
||||
}
|
||||
};
|
||||
|
||||
# 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") {
|
||||
$check_embedded->($list_collection->{_embedded}->{$hal_name});
|
||||
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
|
||||
$check_embedded->($item_c);
|
||||
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, "check if all created test items have been foundin the list");
|
||||
}
|
||||
|
||||
sub check_item_get{
|
||||
my($self,$uri) = @_;
|
||||
$uri ||= $self->get_uri_current;
|
||||
my $req = HTTP::Request->new('GET', $uri);
|
||||
my $res = $self->request($req);
|
||||
is($res->code, 200, "fetch one item");
|
||||
my $err = $res->decoded_content ? JSON::from_json($res->decoded_content) : '';
|
||||
return wantarray ? ($res, $err, $req) : $res;
|
||||
}
|
||||
|
||||
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 = $self->request($req);
|
||||
is($res->code, 415, "check put missing content type");
|
||||
}
|
||||
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 = $self->request($req);
|
||||
is($res->code, 415, "check put invalid content type");
|
||||
}
|
||||
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 = $self->request($req);
|
||||
is($res->code, 400, "check put invalid prefer");
|
||||
}
|
||||
|
||||
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 = $self->request($req);
|
||||
is($res->code, 400, "check put no body");
|
||||
}
|
||||
sub check_get2put{
|
||||
my($self,$put_data_cb, $uri) = @_;
|
||||
#$req->remove_header('Prefer');
|
||||
#$req->header('Prefer' => "return=representation");
|
||||
# PUT same result again
|
||||
my ($get_res, $item_first_get, $get_req) = $self->check_item_get($uri);
|
||||
my $item_first_put = clone($item_first_get);
|
||||
delete $item_first_put->{_links};
|
||||
delete $item_first_put->{_embedded};
|
||||
# check if put is ok
|
||||
(defined $put_data_cb) and $put_data_cb->($item_first_put);
|
||||
my ($put_res,$item_put_result) = $self->request_put( $item_first_put, $uri );
|
||||
is($put_res->code, 200, "check put successful");
|
||||
is_deeply($item_first_get, $item_put_result, "check put if unmodified put returns the same");
|
||||
}
|
||||
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;
|
||||
}
|
||||
sub check_patch_correct{
|
||||
my($self,$content) = @_;
|
||||
my ($res,$mod_model,$req) = $self->request_patch( $content );
|
||||
is($res->code, 200, "check patched item");
|
||||
is($mod_model->{_links}->{self}->{href}, $self->DATA_CREATED->{FIRST}, "check patched self link");
|
||||
is($mod_model->{_links}->{collection}->{href}, '/api/'.$self->name.'/', "check patched collection link");
|
||||
return ($res,$mod_model,$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 = $self->request($req);
|
||||
is($res->code, 415, "check patch invalid prefer");
|
||||
}
|
||||
sub check_patch_content_type_empty{
|
||||
my($self) = @_;
|
||||
my $req = $self->get_request_patch;
|
||||
$req->remove_header('Content-Type');
|
||||
my $res = $self->request($req);
|
||||
is($res->code, 415, "check patch missing media type");
|
||||
}
|
||||
|
||||
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 = $self->request($req);
|
||||
is($res->code, 415, "check patch invalid media type");
|
||||
}
|
||||
|
||||
sub check_patch_body_empty{
|
||||
my($self) = @_;
|
||||
my ($res,$content,$req) = $self->request_patch;
|
||||
is($res->code, 400, "check patch missing body");
|
||||
like($content->{message}, qr/is missing a message body/, "check patch missing body response");
|
||||
}
|
||||
|
||||
sub check_patch_body_notarray{
|
||||
my($self) = @_;
|
||||
my ($res,$content,$req) = $self->request_patch(
|
||||
{ foo => 'bar' },
|
||||
);
|
||||
is($res->code, 400, "check patch no array body");
|
||||
like($content->{message}, qr/must be an array/, "check patch missing body response");
|
||||
}
|
||||
|
||||
sub check_patch_op_missed{
|
||||
my($self) = @_;
|
||||
my ($res,$content,$req) = $self->request_patch(
|
||||
[{ foo => 'bar' }],
|
||||
);
|
||||
is($res->code, 400, "check patch no op in body");
|
||||
like($content->{message}, qr/must have an 'op' field/, "check patch no op in body response");
|
||||
}
|
||||
|
||||
sub check_patch_op_wrong{
|
||||
my($self) = @_;
|
||||
my ($res,$content,$req) = $self->request_patch(
|
||||
[{ op => 'bar' }],
|
||||
);
|
||||
is($res->code, 400, "check patch invalid op in body");
|
||||
like($content->{message}, qr/Invalid PATCH op /, "check patch no op in body response");
|
||||
}
|
||||
|
||||
sub check_patch_opreplace_paramsmiss{
|
||||
my($self) = @_;
|
||||
my ($res,$content,$req) = $self->request_patch(
|
||||
[{ op => 'replace' }],
|
||||
);
|
||||
is($res->code, 400, "check patch missing fields for op");
|
||||
like($content->{message}, qr/Missing PATCH keys /, "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' }],
|
||||
);
|
||||
is($res->code, 400, "check patch extra fields for op");
|
||||
like($content->{message}, qr/Invalid PATCH key /, "check patch extra fields for op response");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
sub check_bundle{
|
||||
my($self) = @_;
|
||||
$self->check_options_collection;
|
||||
# iterate over collection to check next/prev links and status
|
||||
my $listed = $self->check_list_collection();
|
||||
$self->check_created_listed($listed);
|
||||
# test model item
|
||||
$self->check_options_item;
|
||||
$self->check_put_bundle;
|
||||
$self->check_patch_bundle;
|
||||
}
|
||||
1;
|
@ -0,0 +1,311 @@
|
||||
/**
|
||||
* jQuery dump plugin. Inspect object properties in a popup window.
|
||||
*
|
||||
* Copyright (c) 2012 Block Alexander
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*/
|
||||
(function ($) {
|
||||
var __jqdump__dump_OFF = false; // global OFF switch
|
||||
var position = { left: 100, top: 120 }; // popup window position
|
||||
var defaultDepth = 2; // default dump depth
|
||||
|
||||
/**
|
||||
* Dump object content in popup window with prettyprint and subnavigation
|
||||
*
|
||||
* <example>
|
||||
* // dump navigator properties
|
||||
* $.dump(window.navigator, 1, "window.navigator");
|
||||
*
|
||||
* // this may be used to disable all following function calls
|
||||
* $.dump("off");
|
||||
* </example>
|
||||
*
|
||||
* @param(obj): object to dump, or "off" string to disable all dump calls
|
||||
* @param(iDepth): number, optional dumping depth, default 2
|
||||
* @param(sHistoryPath): string, optional global properties path relative to the initial dump object value, used for display only.
|
||||
* @returns: null
|
||||
*/
|
||||
$.dump = function (obj, iDepth, sHistoryPath) {
|
||||
if (typeof(obj) == "string" && /^off$/i.test(obj)) { __jqdump__dump_OFF = true; }
|
||||
return __jqdump__dump(obj, iDepth, sHistoryPath);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dump object content in popup window with prettyprint and subnavigation
|
||||
*
|
||||
* <example>
|
||||
* $("#element").dump(1);
|
||||
* // same as
|
||||
* $.dump($("#element"), 1, "(#element)");
|
||||
* </example>
|
||||
*
|
||||
* Call type: window.dump/$.dump( ... )
|
||||
* @param(iDepth): number, optional dumping depth, default 2
|
||||
* @returns: null
|
||||
*/
|
||||
$.fn.dump = function (iDepth) {
|
||||
return __jqdump__dump(this, iDepth, this.selector? "("+ this.selector +")" : "");
|
||||
};
|
||||
|
||||
/**
|
||||
* Dump object content in popup window with prettyprint and subnavigation
|
||||
*
|
||||
* @param(obj): object to dump
|
||||
* @param(iDepth): number, optional dumping depth, default 2
|
||||
* @param(sHistoryPath): string, optional global properties path relative to the initial dump object value, used for display only.
|
||||
* @returns: null
|
||||
*/
|
||||
function __jqdump__dump (obj, iDepth, sHistoryPath) {
|
||||
if (__jqdump__dump_OFF) { return null; }
|
||||
|
||||
// store current object value to allow continious/recursive dump browsing via window.opener.__jqdump__
|
||||
__jqdump__ = {data: obj, dump: __jqdump__dump };
|
||||
|
||||
// provide defaults as needed
|
||||
iDepth = (typeof(iDepth) == "number" && iDepth > 0? parseInt(iDepth, 10) : defaultDepth);
|
||||
sHistoryPath = (typeof(sHistoryPath) == "string" && sHistoryPath.length > 0? sHistoryPath : "OBJECT");
|
||||
|
||||
// adjust new window position
|
||||
position = { top: (position.top - 30) % 120, left: (position.left - 10) % 100 };
|
||||
|
||||
// try to popup blank page
|
||||
var dumpWindow = window.open("about:blank", "_blank"
|
||||
, "width=600,height=800,menubar=0,left="+ position.left +",top="+ position.top
|
||||
+",location=0,toolbar=0,status=0,scrollbars=1,resizable=1", true);
|
||||
|
||||
// popup blocked?
|
||||
if (!dumpWindow || dumpWindow.closed == true) {
|
||||
if (confirm("Dump Window couldn't showup cause of popup blocker.\nContinue using current window?")) {
|
||||
dumpWindow = window;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// fill the page with dump content
|
||||
dumpWindow.document.write("<html><head><title>"+ sHistoryPath +" @ "+ DateToHMSMString(new Date()) +"</title>"
|
||||
+"<meta http-equiv='Content-Type' content='text/html;charset=utf-8'/>"
|
||||
+"<style type='text/css'>"
|
||||
+" body{background-color:#fff;color:#000;font-size:12px;margin-top:24px;}"
|
||||
+" #toolbar{position:fixed;top:0px;right:0px;z-index:9999;}"
|
||||
+" span.p.a:hover,span.p.a:hover+span {background-color: #B5F5FF;}"
|
||||
+" a{text-decoration:none;}"
|
||||
+" a:hover{text-decoration:underline;}"
|
||||
+" .h{display:none;}" // hidden element
|
||||
+" .a{cursor:pointer;}" // link like element
|
||||
+" .s{color:#740;}" // string
|
||||
+" .k{color:#427;font-weight:bold;font-style:italic;}" // key-word
|
||||
+" .c{color:#666;font-style:italic;}" // comment
|
||||
+" .u{color:#259;}" // reserved value
|
||||
+" .p{color:#155;font-weight:bold;}" // punctuation
|
||||
+" .d{color:#800;font-weight:bold;}" // diggit
|
||||
+" .e{color:#900;font-style:italic;background-color:#FAA;}" // exception
|
||||
+" .t{color:#080;font-weight:bold;}" // true boolean value
|
||||
+" .f{color:#800;font-weight:bold;}" // false boolean value
|
||||
+" .arg{color:#000;font-weight:normal;}" // number of function arguments
|
||||
+"</style></head><body>"
|
||||
+"<div id='toolbar'>"
|
||||
+" <input type='button' id='btnClose' value='Close' onclick='window.close();'/>"
|
||||
+" <input type='button' value='Collapse All' onclick='ToggleCollapse(true);'/>"
|
||||
+" <input type='button' value='Expand All' onclick='ToggleCollapse(false);'/>"
|
||||
+"</div>"
|
||||
+"<code><pre>"+ sHistoryPath +" <span class='p'>=</span> "
|
||||
+ __jqdump__next(obj, " ", iDepth, "__jqdump__.data", sHistoryPath)
|
||||
+"<span class='p'>;</span></pre></code>"
|
||||
// provide data and code to the parent window
|
||||
+"<sc"+"ript type='text/javascript'><!-"+"-"
|
||||
+"\n __jqdump__ = (window.opener? window.opener.__jqdump__ : window.__jqdump__);"
|
||||
+"\n if (!__jqdump__) { __jqdump__ = {data: null, dump: function (o) { if (JSON) { alert(JSON.stringify(o)); } } }; }"
|
||||
+"\n "
|
||||
// focus the close button to allow fast window close by pressing button [space]
|
||||
+"\n window.onload = function () {"
|
||||
+"\n window.focus();"
|
||||
+"\n document.getElementById('btnClose').focus();"
|
||||
+"\n }"
|
||||
+"\n "
|
||||
+"\n function ToggleCollapse (bCollapse) {"
|
||||
+"\n var span = document.getElementsByTagName('span');"
|
||||
+"\n for (var i = 0; i < span.length; i++) {"
|
||||
+"\n if (span[i].getAttribute('title') != 'collapse/expand' || (i == 1 && bCollapse)) { continue; }"
|
||||
+"\n span[i].nextSibling.className = bCollapse? 'h' : '';"
|
||||
+"\n }"
|
||||
+"\n }"
|
||||
+"\n-"+"-></sc"+"ript></body></html>"
|
||||
);
|
||||
|
||||
// finalize writings
|
||||
dumpWindow.document.close();
|
||||
|
||||
// ensure new window has data and code to continue further dumps
|
||||
dumpWindow.__jqdump__ = {
|
||||
data: __jqdump__.data
|
||||
, dump: __jqdump__.dump
|
||||
//*dbg*/, history: sHistoryPath
|
||||
};
|
||||
|
||||
/**
|
||||
* @param(obj): object to dump
|
||||
* @param(sIndent): string, used for object properties alignment indentation
|
||||
* @param(iDepth): number, dumping depth, defaults to 2
|
||||
* @param(sContextPath): string, evaluable object properties path relative to the current obj value, used onclick event
|
||||
* @param(sHistoryPath): string, global properties path relative to the initial dump obj value, used for display only
|
||||
*/
|
||||
function __jqdump__next (obj, sIndent, iDepth, sContextPath, sHistoryPath) {
|
||||
var objType = typeof(obj);
|
||||
if (null == obj && objType != "undefined") { return "<span class='u'>null</span>"; }
|
||||
|
||||
switch (objType) {
|
||||
case "object": break;
|
||||
case "undefined": return "<span class='u'>undefined</span>";
|
||||
case "string": return obj.length? "\"<span class='s'>"+ htmlEscape(obj) +"</span>\"" : "\"\"";
|
||||
case "number": return "<span class='d'>"+ obj.toString() +"</span>";
|
||||
case "boolean": return "<span class='"+ (obj? "t" : "f") +"'>"+ obj.toString() +"</span>";
|
||||
// allow dumping of function return value (simple function call without arguments)
|
||||
case "function": return format(
|
||||
"<a href='javascript:;' class='k'"
|
||||
+" onclick='__jqdump__.dump((function(){try{return {0}();}catch(xcp){return {EXCEPTION_WRAPPER:xcp.toString()};}})()"
|
||||
+","+ defaultDepth +",this.title);' title='{1}()'>func"+"tion({2})</a>"
|
||||
, sContextPath
|
||||
, sHistoryPath
|
||||
, (obj.length? "<span class='arg'>"+ obj.length +"</span>" : "")
|
||||
);
|
||||
default: return "<span class='e'>/* Unknown object type: {"+ objType +"}*/</span>";
|
||||
}
|
||||
|
||||
if (obj instanceof Date) { return "new Date(\""+ obj.toUTCString() +"\")"; }
|
||||
|
||||
if (iDepth == 0) { // stop here and allow deeper dumping by a click
|
||||
return format("<a href='javascript:;' class='p' title='show more'"
|
||||
+" onclick='__jqdump__.dump((function(){try{return {0};}catch(xcp){return {EXCEPTION_WRAPPER:xcp.toString()};}})(),"
|
||||
+ defaultDepth +",\"{1}\");'"
|
||||
+">{ ... }</a>"
|
||||
, sContextPath
|
||||
, sHistoryPath.replace(/"/g, "\\\""));
|
||||
}
|
||||
|
||||
var bIsArray = (obj instanceof Array)
|
||||
, sNewContextPath, sNewHistoryPath
|
||||
, obja = [], prop;
|
||||
|
||||
var rv = [
|
||||
"<span class='p a' title='collapse/expand'"
|
||||
+" onclick='this.nextSibling.className=(this.nextSibling.className==\"h\"?\"\":\"h\")'"
|
||||
+"> "+ (bIsArray? "[" : "{") +" </span><span>\n"
|
||||
];
|
||||
|
||||
try {
|
||||
// making sorted array of object property names
|
||||
if (bIsArray) {
|
||||
for (var i = 0; i < obj.length; i++) { obja.push(i); }
|
||||
} else {
|
||||
for (prop in obj) { obja.push(prop); }
|
||||
obja.sort(function (a, b) {
|
||||
return (isNaN(a)? (a.toLowerCase() >= b.toLowerCase()? 1 : -1) : (Number(a) >= Number(b))? 1 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
// quering object with names via sorted array
|
||||
for (var c = 0, length = obja.length; c < length; c++) {
|
||||
try {
|
||||
prop = obja[c];
|
||||
// skip self properties
|
||||
if (/__jqdump__/.test(prop)) { continue; }
|
||||
|
||||
if (bIsArray) { // array index:
|
||||
sNewContextPath = format("{0}[\"{1}\"]", sContextPath, prop);
|
||||
sNewHistoryPath = format("{0}[\"{1}\"]", sHistoryPath, prop);
|
||||
rv.push(format(
|
||||
"{0}<span class='a c' onclick='alert(this.title);' title='{1}'>/*{2}*/</span> "
|
||||
, sIndent, sNewHistoryPath, prop
|
||||
));
|
||||
} else {// object property:
|
||||
if (/(\W)|(^\d)/.test(prop)) {//- as string
|
||||
sNewContextPath = format("{0}[\"{1}\"]", sContextPath, prop);
|
||||
sNewHistoryPath = format("{0}[\"{1}\"]", sHistoryPath, htmlEscape(prop));
|
||||
rv.push(format(
|
||||
"{0}<span class='s a' onclick='alert(this.title);' title='{1}'>\"{2}\"</span> <span class='p'>:</span> "
|
||||
, sIndent, sNewHistoryPath, htmlEscape(prop)
|
||||
));
|
||||
} else {//- as conventional variable name
|
||||
sNewContextPath = format("{0}.{1}", sContextPath, prop);
|
||||
sNewHistoryPath = format("{0}.{1}", sHistoryPath, htmlEscape(prop));
|
||||
rv.push(format(
|
||||
"{0}<span class='a' onclick='alert(this.title);' title='{1}'>{2}</span> <span class='p'>:</span> "
|
||||
, sIndent, sNewHistoryPath, htmlEscape(prop)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
rv.push(__jqdump__next(obj[prop], sIndent +" ", iDepth - 1, sNewContextPath, sNewHistoryPath));
|
||||
} catch (xcp) {
|
||||
rv.push(format("<span class='e'>/*{0} - {1}*/</span>", xcp.name, xcp.message));
|
||||
}
|
||||
|
||||
rv.push((c < (obja.length-1)? "<span class='p'>,</span>\n" : "\n"));
|
||||
}
|
||||
} catch (xcp) {
|
||||
rv.push(format("<span class='e'>/*{0} - {1}*/</span>", xcp.name, xcp.message));
|
||||
}
|
||||
|
||||
rv.push(format("{0}</span><span class='p'>{1}</span>", sIndent.replace(" ", ""), (bIsArray? "]" : "}")));
|
||||
|
||||
return rv.join("");
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape native string characters before writing it as html text
|
||||
*/
|
||||
function htmlEscape (str) {
|
||||
// trying to speedup the process by checking the length
|
||||
return str.length? str.replace(/&/g, "&").replace(/\</g, "<").replace(/\>/g, ">") : str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces each format item "{n}" in `this` string with the string equivalent of a corresponding
|
||||
* parameters object's value. For example "{0}" reffers to the second argument, "{1}" to the third.
|
||||
*
|
||||
* @note: format identifier "{i}" may be repeated multiple times in any order as long as it reffers
|
||||
* to corresponding position of the argument
|
||||
* @param(0): first parameter is a format-string, containing meta information of insertion positions
|
||||
* @param(...): any type, other arguments reffered by the format-string will be evaluated to string
|
||||
* @return: string, formating result
|
||||
*/
|
||||
function format (/* ... */) {
|
||||
var match = null, rv = arguments[0];
|
||||
|
||||
for (var c = 1, length = arguments.length; c < length; c++) {
|
||||
match = new RegExp("\\{"+ (c - 1) +"\\}", "g");
|
||||
if (match.test(rv)) {
|
||||
rv = rv.replace(match, typeof(arguments[c]) == "undefined" ? "(undefined)" : arguments[c].toString());
|
||||
}
|
||||
}
|
||||
|
||||
match = null;
|
||||
return rv;
|
||||
};
|
||||
|
||||
/**
|
||||
* Time to string in HH:MM:SS.mmm format
|
||||
* @param(d): date object
|
||||
*/
|
||||
function DateToHMSMString (d) {
|
||||
var iHours = d.getHours(), iMinutes = d.getMinutes(), iSeconds = d.getSeconds(), iMSeconds = d.getMilliseconds();
|
||||
|
||||
return (iHours < 10? "0" : "") + iHours +":"+ (iMinutes < 10? "0" : "") + iMinutes
|
||||
+":"+ (iSeconds < 10? "0" : "") + iSeconds
|
||||
+"."+ (iMSeconds < 100? "0" : "") + (iMSeconds < 10? "0" : "") + iMSeconds;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
})(jQuery);
|
@ -0,0 +1,143 @@
|
||||
#use Sipwise::Base;
|
||||
use strict;
|
||||
|
||||
#use Moose;
|
||||
use Sipwise::Base;
|
||||
extends 'NGCP::Panel::Utils::Test::Collection';
|
||||
use Net::Domain qw(hostfqdn);
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Request::Common;
|
||||
use JSON;
|
||||
use Test::More;
|
||||
use Data::Dumper;
|
||||
use File::Basename;
|
||||
|
||||
#init test_machine
|
||||
my $test_machine = NGCP::Panel::Utils::Test::Collection->new(
|
||||
name => 'pbxdevicemodels',
|
||||
embedded => [qw/pbxdevicefirmwares/]
|
||||
);
|
||||
@{$test_machine->content_type}{qw/POST PUT/} = (('multipart/form-data') x 2);
|
||||
$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
|
||||
$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH)};
|
||||
#for item creation test purposes /post request data/
|
||||
$test_machine->DATA_ITEM_STORE({
|
||||
json => {
|
||||
"model"=>"ATA111",
|
||||
#should be some fake reseller - create reseller/customer/subscriber tests?
|
||||
#"reseller_id"=>"1",
|
||||
"vendor"=>"Cisco",
|
||||
#3.7relative tests
|
||||
"bootstrap_method"=>"http",
|
||||
"bootstrap_uri"=>"",
|
||||
"bootstrap_config_http_sync_method"=>"GET",
|
||||
"bootstrap_config_http_sync_params"=>"[% server.uri %]/\$MA",
|
||||
"bootstrap_config_http_sync_uri"=>"http=>//[% client.ip %]/admin/resync",
|
||||
"bootstrap_config_redirect_panasonic_password"=>"",
|
||||
"bootstrap_config_redirect_panasonic_user"=>"",
|
||||
"bootstrap_config_redirect_polycom_password"=>"",
|
||||
"bootstrap_config_redirect_polycom_profile"=>"",
|
||||
"bootstrap_config_redirect_polycom_user"=>"",
|
||||
"bootstrap_config_redirect_yealink_password"=>"",
|
||||
"bootstrap_config_redirect_yealink_user"=>"",
|
||||
"type"=>"phone",
|
||||
#"connectable_models"=>[702,703,704],
|
||||
"extensions_num"=>"2",
|
||||
#/3.7relative tests
|
||||
"linerange"=>[
|
||||
{
|
||||
"keys"=>[
|
||||
{"y"=>"390","labelpos"=>"left","x"=>"510"},
|
||||
{"y"=>"350","labelpos"=>"left","x"=>"510"}
|
||||
],
|
||||
"can_private"=>"1",
|
||||
"can_shared"=>"0",
|
||||
"can_blf"=>"0",
|
||||
"name"=>"Phone Ports",
|
||||
#test duplicate creation #"id"=>1311,
|
||||
}
|
||||
]
|
||||
},
|
||||
#can check big files
|
||||
#'front_image' => [ dirname($0).'/resources/api_devicemodels_front_image.jpg' ],
|
||||
'front_image' => [ dirname($0).'/resources/empty.txt' ],
|
||||
});
|
||||
|
||||
|
||||
|
||||
##$test_machine->form_data_item( sub {$_[0]->{json}->{type} = "extension";} );
|
||||
##$test_machine->check_create_correct( 1, sub{ $_[0]->{json}->{model} .= "Extension 2".$_[1]->{i}; } );
|
||||
#$test_machine->check_get2put( sub {
|
||||
# $_[0]->{connectable_models} = [670],
|
||||
# $_[0] = {
|
||||
# json => JSON::to_json($_[0]),
|
||||
# 'front_image' => $test_machine->DATA_ITEM_STORE->{front_image}
|
||||
# }; },
|
||||
# $test_machine->get_uri_collection.'449'
|
||||
#);
|
||||
#
|
||||
##test check_patch_prefer_wrong is broken
|
||||
##$test_machine->name('billingprofiles');
|
||||
##$test_machine->check_patch_prefer_wrong;
|
||||
|
||||
|
||||
foreach my $type(qw/phone extension/){
|
||||
#last;#skip classic tests
|
||||
$test_machine->form_data_item( sub {$_[0]->{json}->{type} = $type;} );
|
||||
# create 6 new billing models from DATA_ITEM
|
||||
#$test_machine->check_create_correct( 6, sub{ $_[0]->{json}->{model} .= $type."TEST_".$_[1]->{i}; } );
|
||||
#$test_machine->check_get2put( sub { $_[0] = { json => JSON::to_json($_[0]), 'front_image' => $test_machine->DATA_ITEM_STORE->{front_image} }; } );
|
||||
|
||||
|
||||
$test_machine->check_bundle();
|
||||
|
||||
# try to create model without reseller_id
|
||||
{
|
||||
my ($res, $err) = $test_machine->request_post(sub{delete $_[0]->{json}->{reseller_id};});
|
||||
is($res->code, 422, "create model without reseller_id");
|
||||
is($err->{code}, "422", "check error code in body");
|
||||
ok($err->{message} =~ /field='reseller_id'/, "check error message in body");
|
||||
}
|
||||
# try to create model with empty reseller_id
|
||||
{
|
||||
my ($res, $err) = $test_machine->request_post(sub{$_[0]->{json}->{reseller_id} = undef;});
|
||||
is($res->code, 422, "create model with empty reseller_id");
|
||||
is($err->{code}, "422", "check error code in body");
|
||||
ok($err->{message} =~ /field='reseller_id'/, "check error message in body");
|
||||
}
|
||||
# try to create model with invalid reseller_id
|
||||
{
|
||||
my ($res, $err) = $test_machine->request_post(sub{$_[0]->{json}->{reseller_id} = 99999;});
|
||||
is($res->code, 422, "create model with invalid reseller_id");
|
||||
is($err->{code}, "422", "check error code in body");
|
||||
ok($err->{message} =~ /Invalid reseller_id/, "check error message in body");
|
||||
}
|
||||
|
||||
{
|
||||
my (undef, $item_first_get) = $test_machine->check_item_get;
|
||||
ok(exists $item_first_get->{reseller_id} && $item_first_get->{reseller_id}->is_int, "check existence of reseller_id");
|
||||
foreach(qw/vendor model/){
|
||||
ok(exists $item_first_get->{$_}, "check existence of $_");
|
||||
}
|
||||
# check if we have the proper links
|
||||
# TODO: fees, reseller links
|
||||
#ok(exists $new_contract->{_links}->{'ngcp:resellers'}, "check put presence of ngcp:resellers relation");
|
||||
}
|
||||
{
|
||||
my $t = time;
|
||||
my($res,$mod_model) = $test_machine->check_patch_correct( [ { op => 'replace', path => '/model', value => 'patched model '.$t } ] );
|
||||
is($mod_model->{model}, "patched model $t", "check patched replace op");
|
||||
}
|
||||
{
|
||||
my($res) = $test_machine->request_patch( [ { op => 'replace', path => '/reseller_id', value => undef } ] );
|
||||
is($res->code, 422, "check patched undef reseller");
|
||||
}
|
||||
{
|
||||
my($res) = $test_machine->request_patch( [ { op => 'replace', path => '/reseller_id', value => 99999 } ] );
|
||||
is($res->code, 422, "check patched invalid reseller");
|
||||
}
|
||||
}
|
||||
`echo 'delete from autoprov_devices where model like "%TEST\\_%" or model like "patched model%";'|mysql provisioning`;
|
||||
done_testing;
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
@ -0,0 +1,59 @@
|
||||
#use Sipwise::Base;
|
||||
use strict;
|
||||
|
||||
#use Moose;
|
||||
use Sipwise::Base;
|
||||
extends 'NGCP::Panel::Utils::Test::Collection';
|
||||
use Net::Domain qw(hostfqdn);
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Request::Common;
|
||||
use JSON;
|
||||
use Test::More;
|
||||
use Data::Dumper;
|
||||
use File::Basename;
|
||||
use bignum qw/hex/;
|
||||
|
||||
#init test_machine
|
||||
my $test_machine = NGCP::Panel::Utils::Test::Collection->new(
|
||||
name => 'pbxdevices',
|
||||
embedded => [qw/pbxdeviceprofiles customers/]
|
||||
);
|
||||
$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
|
||||
$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
|
||||
#for item creation test purposes /post request data/
|
||||
$test_machine->DATA_ITEM_STORE({
|
||||
#'profile_id' => '151',
|
||||
#somehow should obtain/create test customer with the test subscriber - discuss with alex
|
||||
#'customer_id' => '968',
|
||||
'identifier' => 'aaaabbbbcccc',
|
||||
'station_name' => 'abc',
|
||||
'lines'=>[{
|
||||
'linerange' => 'Phone Ports',
|
||||
'type' => 'private',
|
||||
'key_num' => '0',
|
||||
#somehow should obtain/create test customer with the test subscriber - discuss with alex
|
||||
#'subscriber_id' => '1198',
|
||||
'extension_unit' => '1',
|
||||
#'extension_num' => '1',#to handle some the same extensions devices
|
||||
},{
|
||||
'linerange' => 'Phone Ports',
|
||||
'type' => 'private',
|
||||
'key_num' => '1',
|
||||
#somehow should obtain/create test customer with the test subscriber - discuss with alex
|
||||
#'subscriber_id' => '1198',
|
||||
'extension_unit' => '2',
|
||||
}],
|
||||
});
|
||||
|
||||
|
||||
$test_machine->form_data_item( );
|
||||
# create 3 new billing models from DATA_ITEM
|
||||
#$test_machine->check_create_correct( 3, sub{ $_[0]->{identifier} = sprintf('%x', (hex('0x'.$_[0]->{identifier}) + $_[1]->{i}) ); } );
|
||||
#$test_machine->check_get2put( );
|
||||
$test_machine->check_bundle();
|
||||
$test_machine->check_delete_use_created();
|
||||
|
||||
|
||||
done_testing;
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
After Width: | Height: | Size: 480 KiB |
Loading…
Reference in new issue