MT#16121 Optimize callforwards GET method

Change-Id: I5672073eaa8b5594a3e4d4d2be64054584771d2b
changes/85/3585/9
Irina Peshinskaya 10 years ago
parent d0f0a846c8
commit 7fffffb199

@ -85,9 +85,10 @@ sub GET :Allow {
my $cfs = $self->item_rs($c, "callforwards");
(my $total_count, $cfs) = $self->paginate_order_collection($c, $cfs);
my (@embedded, @links);
my $form = $self->get_form($c);
for my $cf ($cfs->all) {
try {
push @embedded, $self->hal_from_item($c, $cf, "callforwards");
push @embedded, $self->hal_from_item($c, $cf, $form);
push @links, Data::HAL::Link->new(
relation => 'ngcp:'.$self->resource_name,
href => sprintf('%s%s', $self->dispatch_path, $cf->id),

@ -60,7 +60,7 @@ sub GET :Allow {
my $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, subscriber => $item);
my $hal = $self->hal_from_item($c, $item, "callforwards");
my $hal = $self->hal_from_item($c, $item);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
(map { # XXX Data::HAL must be able to generate links with multiple relations
@ -111,7 +111,7 @@ sub PATCH :Allow {
my $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, subs_for_callforwards => $item);
my $old_resource = $self->hal_from_item($c, $item, "callforwards")->resource;
my $old_resource = $self->hal_from_item($c, $item)->resource;
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
@ -119,7 +119,7 @@ sub PATCH :Allow {
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
my $hal = $self->hal_from_item($c, $item, "callforwards");
my $hal = $self->hal_from_item($c, $item);
last unless $self->add_update_journal_item_hal($c,{ hal => $hal, id => $id });
$guard->commit;
@ -129,7 +129,7 @@ sub PATCH :Allow {
$c->response->header(Preference_Applied => 'return=minimal');
$c->response->body(q());
} else {
#my $hal = $self->hal_from_item($c, $callforward, "callforwards");
#my $hal = $self->hal_from_item($c, $callforward);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
$hal->http_headers,
), $hal->as_json);
@ -162,7 +162,7 @@ sub PUT :Allow {
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
my $hal = $self->hal_from_item($c, $item, "callforwards");
my $hal = $self->hal_from_item($c, $item);
last unless $self->add_update_journal_item_hal($c,{ hal => $hal, id => $id });
$guard->commit;
@ -172,7 +172,7 @@ sub PUT :Allow {
$c->response->header(Preference_Applied => 'return=minimal');
$c->response->body(q());
} else {
#my $hal = $self->hal_from_item($c, $callforward, "callforwards");
#my $hal = $self->hal_from_item($c, $callforward);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
$hal->http_headers,
), $hal->as_json);
@ -202,7 +202,7 @@ sub DELETE :Allow {
last unless $self->add_delete_journal_item_hal($c,{ hal_from_item => sub {
my $self = shift;
my ($c) = @_;
return $self->hal_from_item($c,$item,"callforwards"); },
return $self->hal_from_item($c,$item); },
id => $id});
try {

@ -23,15 +23,12 @@ sub get_form {
}
sub hal_from_item {
my ($self, $c, $item, $type) = @_;
my $form;
#my $rwr_form = $self->get_form($c, "rules"); #rules?
my ($self, $c, $item, $form) = @_;
my $type = "callforwards";
my $prov_subs = $item->provisioning_voip_subscriber;
die "no provisioning_voip_subscriber" unless $prov_subs;
my %resource = (subscriber_id => $prov_subs->id);
my $hal = Data::HAL->new(
@ -51,16 +48,9 @@ sub hal_from_item {
],
relation => 'ngcp:'.$self->resource_name,
);
for my $cf_type (qw/cfu cfb cft cfna/) {
my $mapping = $c->model('DB')->resultset('voip_cf_mappings')->search({
subscriber_id => $prov_subs->id,
type => $cf_type,
})->first;
if ($mapping) {
$resource{$cf_type} = $self->_contents_from_cfm($c, $mapping, $item);
} else {
$resource{$cf_type} = {};
}
@resource{qw/cfu cfb cft cfna/} = ({}) x 4;
for my $item_cf ($item->provisioning_voip_subscriber->voip_cf_mappings->all){
$resource{$item_cf->type} = $self->_contents_from_cfm($c, $item_cf, $item);
}
if(keys %{$resource{cft}}){
my $ringtimeout_preference = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
@ -70,6 +60,7 @@ sub hal_from_item {
}
$form //= $self->get_form($c);
$form->clear();
return unless $self->validate_form(
c => $c,
form => $form,
@ -82,13 +73,13 @@ sub hal_from_item {
}
sub item_rs {
my ($self, $c, $type) = @_;
my ($self, $c) = @_;
my $item_rs;
$item_rs = $c->model('DB')->resultset('voip_subscribers')
->search(
{ 'me.status' => { '!=' => 'terminated' } },
{ prefetch => 'provisioning_voip_subscriber',},
{ 'prefetch' => { 'provisioning_voip_subscriber' => 'voip_cf_mappings' },},
);
if($c->user->roles eq "reseller") {
$item_rs = $item_rs->search({
@ -102,10 +93,8 @@ sub item_rs {
}
sub item_by_id {
my ($self, $c, $id, $type) = @_;
my $item_rs = $self->item_rs($c, $type);
return $self->item_rs($c, $type)->search_rs({'me.id' => $id})->first;
my ($self, $c, $id) = @_;
return $self->item_rs($c)->search_rs({'me.id' => $id})->first;
}
sub update_item {

@ -0,0 +1,250 @@
#!/usr/bin/perl
use strict;
use Data::Dumper;
use NGCP::Schema;
use NGCP::Panel::Utils::Subscriber;
use NGCP::Panel::Utils::Preferences;
use Test::More;
use Data::HAL qw();
use Data::HAL::Link qw();
use Safe::Isa qw($_isa);
use NGCP::Panel::Form::CFSimpleAPI;
my $schema = NGCP::Schema->connect();
my $ql_exists = 0;
my ($ql,$ana);
if($ql_exists){
#use DBIx::Class::QueryLog;
#use DBIx::Class::QueryLog::Analyzer;
my $ql = DBIx::Class::QueryLog->new;
$schema->storage->debugobj($ql);
$schema->storage->debug(1);
my $ana = DBIx::Class::QueryLog::Analyzer->new({ querylog => $ql });
$ql->bucket('origin');
}
my $time = time();
print "start;\n";
#print Dumper($schema);
my $item_rs = $schema->resultset('voip_subscribers')->search( {
'me.status' => { '!=' => 'terminated' }
},{
prefetch => { 'provisioning_voip_subscriber'=>'voip_cf_mappings'},
#prefetch => 'provisioning_voip_subscriber',
rows => 200,
},
);
my (@arr_orig,@arr_opt);
for my $item ($item_rs->all) {
# print Dumper({$item->get_inflated_columns});
my %resource = ();
my $prov_subs = $item->provisioning_voip_subscriber;
for my $cf_type (qw/cfu cfb cft cfna/) {
my $mapping = $schema->resultset('voip_cf_mappings')->search({
subscriber_id => $prov_subs->id,
type => $cf_type,
})->first;
if ($mapping) {
$resource{$cf_type} = _contents_from_cfm($mapping, $item);
} else {
$resource{$cf_type} = {};
}
}
if(keys %{$resource{cft}}){
my $ringtimeout_preference = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => undef, attribute => 'ringtimeout', prov_subscriber => $prov_subs, schema => $schema )->first;
$ringtimeout_preference = $ringtimeout_preference ? $ringtimeout_preference->value : undef;
$resource{cft}{ringtimeout} = $ringtimeout_preference;
}
additional_processing($item, \%resource);
push @arr_orig, \%resource;
}
print "1.time=".(time()-$time).";\n";
#exit;
if($ql_exists){
$ql->bucket('optimized');
}
$time = time();
for my $item ($item_rs->all) {
my %resource = ();
my $prov_subs = $item->provisioning_voip_subscriber;
@resource{qw/cfu cfb cft cfna/} = ({}) x 4;
for my $item_cf ($item->provisioning_voip_subscriber->voip_cf_mappings->all){
$resource{$item_cf->type} = _contents_from_cfm($item_cf, $item);
}
if(keys %{$resource{cft}}){
my $ringtimeout_preference = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => undef, attribute => 'ringtimeout', prov_subscriber => $prov_subs, schema => $schema )->first;
$ringtimeout_preference = $ringtimeout_preference ? $ringtimeout_preference->value : undef;
$resource{cft}{ringtimeout} = $ringtimeout_preference;
}
additional_processing($item, \%resource);
push @arr_opt, \%resource;
}
print "2.time=".(time()-$time).";\n";
is_deeply(\@arr_orig, \@arr_opt, "check that arrays are equiv");
#print Dumper[\@arr_orig, \@arr_opt];
if($ql_exists){
print Dumper $ana->get_totaled_queries_by_bucket;
}
sub additional_processing{
my($item,$resource) = @_;
my $type='';
my %resource=%$resource;
my $hal = Data::HAL->new(
links => [
Data::HAL::Link->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => 1,
),
Data::HAL::Link->new(relation => 'collection', href => sprintf("%s", '')),
Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
Data::HAL::Link->new(relation => 'self', href => sprintf("%s%s", '', $item->id)),
Data::HAL::Link->new(relation => "ngcp:$type", href => sprintf("/api/%s/%s", $type, $item->id)),
Data::HAL::Link->new(relation => 'ngcp:subscribers', href => sprintf("/api/subscribers/%d", $item->id)),
],
relation => 'ngcp:callforwards',
);
my $form=NGCP::Panel::Form::CFSimpleAPI->new();
validate_form(
form => $form,
resource => \%resource,
run => 0,
);
$hal->resource(\%resource);
}
sub _contents_from_cfm {
my ($cfm_item, $sub) = @_;
my (@times, @destinations);
my $timeset_item = $cfm_item->time_set;
my $dset_item = $cfm_item->destination_set;
for my $time ($timeset_item ? $timeset_item->voip_cf_periods->all : () ) {
push @times, {$time->get_inflated_columns};
delete @{$times[-1]}{'time_set_id', 'id'};
}
for my $dest ($dset_item ? $dset_item->voip_cf_destinations->all : () ) {
my ($d, $duri) = NGCP::Panel::Utils::Subscriber::destination_to_field($dest->destination);
my $deflated;
if($d eq "uri") {
$deflated = NGCP::Panel::Utils::Subscriber::uri_deflate($duri,$sub) if $d eq "uri";
$d = $dest->destination;
}
push @destinations, {$dest->get_inflated_columns,
destination => $d,
$deflated ? (simple_destination => $deflated) : (),
};
delete @{$destinations[-1]}{'destination_set_id', 'id'};
}
return {times => \@times, destinations => \@destinations};
}
sub validate_form {
my (%params) = @_;
my $resource = $params{resource};
my $form = $params{form};
my $run = $params{run} // 1;
my $exceptions = $params{exceptions} // [];
my $form_params = $params{form_params} // {};
push @{ $exceptions }, "external_id";
my @normalized = ();
# move {xxx_id} into {xxx}{id} for FormHandler
foreach my $key(keys %{ $resource } ) {
my $skip_normalize = grep {/^$key$/} @{ $exceptions };
if($key =~ /^(.+)_id$/ && !$skip_normalize && !exists $resource->{$1}) {
push @normalized, $1;
$resource->{$1}{id} = delete $resource->{$key};
}
}
# remove unknown keys
my %fields = map { $_->name => $_ } $form->fields;
validate_fields($resource, \%fields, $run);
if($run) {
# check keys/vals
$form->process(params => $resource, posted => 1, %{$form_params} );
unless($form->validated) {
my $e = join '; ', map {
sprintf 'field=\'%s\', input=\'%s\', errors=\'%s\'',
($_->parent->$_isa('HTML::FormHandler::Field') ? $_->parent->name . '_' : '') . $_->name,
$_->input // '',
join('', @{ $_->errors })
} $form->error_fields;
return;
}
}
# move {xxx}{id} back into {xxx_id} for DB
foreach my $key(@normalized) {
next unless(exists $resource->{$key});
$resource->{$key . '_id'} = defined($resource->{$key}{id}) ?
int($resource->{$key}{id}) :
$resource->{$key}{id};
delete $resource->{$key};
}
return 1;
}
sub validate_fields {
my ($resource, $fields, $run) = @_;
for my $k (keys %{ $resource }) {
#if($resource->{$k}->$_isa('JSON::XS::Boolean') || $resource->{$k}->$_isa('JSON::PP::Boolean')) {
if($resource->{$k}->$_isa('JSON::PP::Boolean')) {
$resource->{$k} = $resource->{$k} ? 1 : 0;
}
unless(exists $fields->{$k}) {
delete $resource->{$k};
}
$resource->{$k} = DateTime::Format::RFC3339->format_datetime($resource->{$k})
if $resource->{$k}->$_isa('DateTime');
$resource->{$k} = $resource->{$k} + 0
if(defined $resource->{$k} && (
$fields->{$k}->$_isa('HTML::FormHandler::Field::Integer') ||
$fields->{$k}->$_isa('HTML::FormHandler::Field::Money') ||
$fields->{$k}->$_isa('HTML::FormHandler::Field::Float')) &&
(is_int($resource->{$k}) || is_decimal($resource->{$k})));
if (defined $resource->{$k} &&
$fields->{$k}->$_isa('HTML::FormHandler::Field::Repeatable') &&
"ARRAY" eq ref $resource->{$k} ) {
for my $elem (@{ $resource->{$k} }) {
my ($subfield_instance) = $fields->{$k}->fields;
my %subfields = map { $_->name => $_ } $subfield_instance->fields;
validate_fields($elem, \%subfields, $run);
}
}
# only do this for converting back from obj to hal
# otherwise it breaks db fields with the \0 and \1 notation
unless($run) {
$resource->{$k} = $resource->{$k} ? JSON::true : JSON::false
if(defined $resource->{$k} &&
$fields->{$k}->$_isa('HTML::FormHandler::Field::Boolean'));
}
}
return 1;
}
1;

@ -0,0 +1,12 @@
#!/usr/bin/perl
use strict;
use Data::Dumper;
use NGCP::Panel::Form::CFSimpleAPI;
for(my $i=0; $i<200;$i++){
my $form=NGCP::Panel::Form::CFSimpleAPI->new();
}
1;

@ -29,7 +29,7 @@ $fake_data->set_data_from_script({
});
my $test_machine = Test::Collection->new(
name => 'billingfees',
embedded => [qw/billingzones billingprofiles/]
embedded_resources => [qw/billingzones billingprofiles/]
);
$test_machine->DATA_ITEM_STORE($fake_data->process('billingfees'));
$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
@ -115,7 +115,7 @@ $test_machine->check_bundle();
}
{
my($res,$item_put,$req) = $test_machine->check_get2put();
$test_machine->check_embedded($item_put);
$test_machine->check_embedded($item_put->{content});
}
{
my($res,$mod_fee) = $test_machine->check_patch_correct( [ { op => 'replace', path => '/direction', value => 'in' } ] );

@ -1,281 +1,115 @@
use Sipwise::Base;
use Net::Domain qw(hostfqdn);
use LWP::UserAgent;
use JSON qw();
use Test::More;
my $uri = $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443');
my ($netloc) = ($uri =~ m!^https?://(.*)/?.*$!);
my ($ua, $req, $res);
$ua = LWP::UserAgent->new;
$ua->ssl_opts(
verify_hostname => 0,
SSL_verify_mode => 0,
);
my $user = $ENV{API_USER} // 'administrator';
my $pass = $ENV{API_PASS} // 'administrator';
$ua->credentials($netloc, "api_admin_http", $user, $pass);
# OPTIONS tests
{
$req = HTTP::Request->new('OPTIONS', $uri.'/api/callforwards/');
$res = $ua->request($req);
is($res->code, 200, "check options request");
is($res->header('Accept-Post'), "application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-callforwards", "check Accept-Post header in options response");
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(qw( GET HEAD OPTIONS )) {
ok(grep(/^$opt$/, @hopts), "check for existence of '$opt' in Allow header");
ok(grep(/^$opt$/, @{ $opts->{methods} }), "check for existence of '$opt' in body");
}
}
my $t = time;
my $reseller_id = 1;
my $billing_profile_id; #dummy
# collection test
my $firstcf = undef;
my $firstcustomer; #dummy
my $custcontact = undef; #dummy
my @allcustomers = (); #dummy
my $system_contact_id; #dummy
{
# iterate over customers collection to check next/prev links and status
my $nexturi = $uri.'/api/callforwards/?page=1&rows=5';
do {
$res = $ua->get($nexturi);
is($res->code, 200, "fetch cfs page");
my $collection = JSON::from_json($res->decoded_content);
my $selfuri = $uri . $collection->{_links}->{self}->{href};
is($selfuri, $nexturi, "check _links.self.href of collection");
my $colluri = URI->new($selfuri);
cmp_ok($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});
if($page == 1) {
ok(!exists $collection->{_links}->{prev}->{href}, "check absence of 'prev' on first page");
} else {
ok(exists $collection->{_links}->{prev}->{href}, "check existence of 'prev'");
}
if(($collection->{total_count} / $rows) <= $page) {
ok(!exists $collection->{_links}->{next}->{href}, "check absence of 'next' on last page");
} else {
ok(exists $collection->{_links}->{next}->{href}, "check existence of 'next'");
}
use strict;
use warnings;
if($collection->{_links}->{next}->{href}) {
$nexturi = $uri . $collection->{_links}->{next}->{href};
} else {
$nexturi = undef;
}
# TODO: I'd expect that to be an array ref in any case!
ok((ref $collection->{_links}->{'ngcp:callforwards'} eq "ARRAY" ||
ref $collection->{_links}->{'ngcp:callforwards'} eq "HASH"), "check if 'ngcp:callforwards' is array/hash-ref");
# remove any contact we find in the collection for later check
if(ref $collection->{_links}->{'ngcp:callforwards'} eq "HASH") {
ok(exists $collection->{_embedded}->{'ngcp:callforwards'}->{_links}->{'ngcp:callforwards'}, "check presence of ngcp:callforwards relation");
ok(exists $collection->{_embedded}->{'ngcp:callforwards'}->{_links}->{'ngcp:subscribers'}, "check presence of ngcp:subscribers relation");
} else {
foreach my $c(@{ $collection->{_embedded}->{'ngcp:callforwards'} }) {
ok(exists $c->{_links}->{'ngcp:callforwards'}, "check presence of ngcp:callforwards relation");
ok(exists $c->{_links}->{'ngcp:subscribers'}, "check presence of ngcp:subscribers relation");
use Test::More;
use Test::Collection;
use Test::FakeData;
use Data::Dumper;
my $fake_data = Test::FakeData->new;
$fake_data->set_data_from_script({
'callforwards' => {
'data' => {
#not really necessary - there isn't POST method
#subscriber_id => sub { return shift->get_id('subscribers',@_); },
cfu => {
destinations => [
{ destination => "12345", timeout => 200},
],
times => undef,
},
cft => {
destinations => [
{ destination => "5678" },
{ destination => "voicebox", timeout => 500 },
],
ringtimeout => 10,
}
}
} while($nexturi);
}
diag('Note that the next tests require at least one subscriber to be present');
},
},
});
my $test_machine = Test::Collection->new(
name => 'callforwards',
embedded_resources => [qw/subscribers callforwards/],
);
$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS)};
$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
$test_machine->DATA_ITEM_STORE($fake_data->process('callforwards'));
$test_machine->form_data_item( );
SKIP:{
my ($res,$req,$content);
my $cf1 = $test_machine->get_item_hal();
if(!$cf1->{content}->{total_count}){
skip("Testing requires at least one present callforward. No creation is available.",1);
}
# fetch a callforward (subscriber) id for later tests
$req = HTTP::Request->new('GET', $uri.'/api/callforwards/?page=1&rows=1');
$res = $ua->request($req);
is($res->code, 200, "fetch first callforward");
my $cf1 = JSON::from_json($res->decoded_content);
my ($cf1_id) = $cf1->{_embedded}->{'ngcp:callforwards'}->{_links}{self}{href} =~ m!callforwards/([0-9]*)$!;
$test_machine->check_bundle();
cmp_ok ($cf1_id, '>', 0, "should be positive integer");
my($cf1_id) = $test_machine->get_id_from_hal($cf1->{content}); #($cf1,'callforwards');
cmp_ok ($cf1_id, '>', 0, "should be positive integer");
my $cf1single_uri = "/api/callforwards/$cf1_id";
my $cf1single;
(undef, $cf1single) = $test_machine->check_item_get($cf1single_uri,"fetch cf id $cf1_id");
# test cf item
{
$req = HTTP::Request->new('OPTIONS', "$uri/api/callforwards/$cf1_id");
$res = $ua->request($req);
is($res->code, 200, "check options on item");
my @hopts = split /\s*,\s*/, $res->header('Allow');
my $opts = JSON::from_json($res->decoded_content);
ok(exists $opts->{methods} && ref $opts->{methods} eq "ARRAY", "check for valid 'methods' in body");
foreach my $opt(qw( GET HEAD OPTIONS PUT PATCH DELETE )) {
ok(grep(/^$opt$/, @hopts), "check for existence of '$opt' in Allow header");
ok(grep(/^$opt$/, @{ $opts->{methods} }), "check for existence of '$opt' in body");
}
foreach my $opt(qw( POST )) {
ok(!grep(/^$opt$/, @hopts), "check for absence of '$opt' in Allow header");
ok(!grep(/^$opt$/, @{ $opts->{methods} }), "check for absence of '$opt' in body");
}
# get our cf
$req = HTTP::Request->new('GET', "$uri/api/callforwards/$cf1_id");
$res = $ua->request($req);
is($res->code, 200, "fetch cf id $cf1_id");
my $cf1single = JSON::from_json($res->decoded_content);
#check cf structure
is(ref $cf1single, "HASH", "cf should be hash");
ok(exists $cf1single->{cfu}, "cf should have key cfu");
ok(exists $cf1single->{cfb}, "cf should have key cfb");
ok(exists $cf1single->{cft}, "cf should have key cft");
ok(exists $cf1single->{cfna}, "cf should have key cfna");
# write this cf
$req = HTTP::Request->new('PUT', "$uri/api/callforwards/$cf1_id");
$req->header('Prefer' => "return=representation");
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
cfu => {
destinations => [
{ destination => "12345", timeout => 200},
],
times => undef,
},
cft => {
destinations => [
{ destination => "5678" },
{ destination => "voicebox", timeout => 500 },
],
ringtimeout => 10,
}
}));
$res = $ua->request($req);
is($res->code, 200, "write a specific callforward") || diag ($res->message);
my $cf1put = JSON::from_json($res->decoded_content);
is (ref $cf1put, "HASH", "should be hashref");
is ($cf1put->{cfu}{destinations}->[0]->{timeout}, 200, "Check timeout of cft");
is ($cf1put->{cft}{destinations}->[0]->{simple_destination}, "5678", "Check first destination of cft");
like ($cf1put->{cft}{destinations}->[0]->{destination}, qr/^sip:5678@/, "Check first destination of cft (regex, full uri)");
is ($cf1put->{cft}{destinations}->[1]->{destination}, "voicebox", "Check second destination of cft");
#write cf and check written values
my($cf1_put,$cf1_get) = $test_machine->check_put2get({data_in => $test_machine->DATA_ITEM, uri => $cf1single_uri},undef, 1 );
is (ref $cf1_put->{content}, "HASH", "should be hashref");
is ($cf1_put->{content}->{cfu}{destinations}->[0]->{timeout}, 200, "Check timeout of cft");
is ($cf1_put->{content}->{cft}{destinations}->[0]->{simple_destination}, "5678", "Check first destination of cft");
like ($cf1_put->{content}->{cft}{destinations}->[0]->{destination}, qr/^sip:5678@/, "Check first destination of cft (regex, full uri)");
is ($cf1_put->{content}->{cft}{destinations}->[1]->{destination}, "voicebox", "Check second destination of cft");
#write invalid 'timeout'
$req = HTTP::Request->new('PUT', "$uri/api/callforwards/$cf1_id");
$req->header('Prefer' => "return=representation");
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
($res,$content,$req) = $test_machine->request_put({
cfu => {
destinations => [
{ destination => "12345", timeout => "foobar"},
],
times => undef,
},
}));
$res = $ua->request($req);
is($res->code, 422, "create customer with invalid type");
my $err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/Validation failed/, "check error message in body");
}, $cf1single_uri);
$test_machine->http_code_msg(422, "create callforward with invalid timeout", $res, $content);
is($content->{code}, "422", "check error code in body");
like($content->{message}, qr/Validation failed/, "check error message in body");
# get invalid cf
$req = HTTP::Request->new('GET', "$uri/api/callforwards/abc");
$res = $ua->request($req);
($res, $content) = $test_machine->request_get("/api/callforwards/abc");
is($res->code, 400, "try invalid callforward id");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "400", "check error code in body");
like($err->{message}, qr/Invalid id/, "check error message in body");
# PUT same result again
my $old_cf1 = { %$cf1put };
delete $cf1put->{_links};
delete $cf1put->{_embedded};
$req = HTTP::Request->new('PUT', "$uri/api/callforwards/$cf1_id");
# check if it fails without content type
$req->remove_header('Content-Type');
$req->header('Prefer' => "return=minimal");
$res = $ua->request($req);
is($res->code, 415, "check put missing content type");
# check if it fails with unsupported content type
$req->header('Content-Type' => 'application/xxx');
$res = $ua->request($req);
is($res->code, 415, "check put invalid content type");
$req->remove_header('Content-Type');
$req->header('Content-Type' => 'application/json');
is($content->{code}, "400", "check error code in body");
like($content->{message}, qr/Invalid id/, "check error message in body");
# check if it fails with invalid Prefer
$req->header('Prefer' => "return=invalid");
$res = $ua->request($req);
is($res->code, 400, "check put invalid prefer");
my($cf2_put,$cf2_get) = $test_machine->check_put2get({data_in => $cf1_put->{content}, uri => $cf1single_uri},undef, 1 );
is_deeply($cf1_put->{content}, $cf2_put->{content}, "check put if unmodified put returns the same");
$test_machine->check_embedded($cf2_put->{content});
$req->remove_header('Prefer');
$req->header('Prefer' => "return=representation");
# check if it fails with missing body
$res = $ua->request($req);
is($res->code, 400, "check put no body");
# check if put is ok
$req->content(JSON::to_json($cf1put));
$res = $ua->request($req);
is($res->code, 200, "check put successful");
my $new_cf1 = JSON::from_json($res->decoded_content);
is_deeply($old_cf1, $new_cf1, "check put if unmodified put returns the same");
# check if we have the proper links
ok(exists $new_cf1->{_links}->{'ngcp:callforwards'}, "check put presence of ngcp:customercontacts relation");
ok(exists $new_cf1->{_links}->{'ngcp:subscribers'}, "check put presence of ngcp:billingprofiles relation");
$req = HTTP::Request->new('PATCH', "$uri/api/callforwards/$cf1_id");
$req->header('Prefer' => 'return=representation');
$req->header('Content-Type' => 'application/json-patch+json');
$req->content(JSON::to_json(
[ { op => 'replace', path => '/cfu/destinations/0/timeout', value => '123' } ]
));
$res = $ua->request($req);
is($res->code, 200, "check patched cf item");
my $mod_cf1 = JSON::from_json($res->decoded_content);
my $mod_cf1;
($res,$mod_cf1) = $test_machine->check_patch_correct( [ { op => 'replace', path => '/cfu/destinations/0/timeout', value => '123' } ] );
is($mod_cf1->{cfu}{destinations}->[0]->{timeout}, "123", "check patched replace op");
is($mod_cf1->{_links}->{self}->{href}, "/api/callforwards/$cf1_id", "check patched self link");
is($mod_cf1->{_links}->{collection}->{href}, '/api/callforwards/', "check patched collection link");
$req->content(JSON::to_json(
[ { op => 'add', path => '/cfu/destinations/-', value => {destination => 99999} } ]
));
$res = $ua->request($req);
($res,$mod_cf1) = $test_machine->request_patch( [ { op => 'add', path => '/cfu/destinations/-', value => {destination => 99999} } ] );
is($res->code, 200, "check patch, add a cfu destination");
$req->content(JSON::to_json(
[ { op => 'replace', path => '/cfu/destinations/0/timeout', value => "" } ]
));
$res = $ua->request($req);
($res,$mod_cf1) = $test_machine->request_patch( [ { op => 'replace', path => '/cfu/destinations/0/timeout', value => "" } ] );
is($res->code, 422, "check patched undef timeout");
$req->content(JSON::to_json(
[ { op => 'replace', path => '/cfu/destinations/0/timeout', value => 'invalid' } ]
));
$res = $ua->request($req);
($res,$mod_cf1) = $test_machine->request_patch( [ { op => 'replace', path => '/cfu/destinations/0/timeout', value => 'invalid' } ] );
is($res->code, 422, "check patched invalid status");
$req->content(JSON::to_json(
[ { op => 'replace', path => '/some/path', value => 'invalid' } ]
));
$res = $ua->request($req);
is($res->code, 422, "check patched invalid path");
}
done_testing;
1;
# vim: set tabstop=4 expandtab:

@ -31,7 +31,7 @@ $fake_data->set_data_from_script({
});
my $test_machine = Test::Collection->new(
name => 'faxes',
embedded => [qw/subscribers/]
embedded_resources => [qw/subscribers/]
);

@ -80,7 +80,7 @@ $fake_data->set_data_from_script({
});
my $test_machine = Test::Collection->new(
name => 'pbxdevicemodels',
embedded => [qw/pbxdevicefirmwares/]
embedded_resources => [qw/pbxdevicefirmwares/]
);
$test_machine->DATA_ITEM_STORE($fake_data->process('pbxdevicemodels'));
@{$test_machine->content_type}{qw/POST PUT/} = (('multipart/form-data') x 2);

@ -12,7 +12,7 @@ use Test::FakeData;
#init test_machine
my $test_machine = Test::Collection->new(
name => 'pbxdevices',
embedded => [qw/pbxdeviceprofiles customers/],
embedded_resources => [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)};

@ -9,7 +9,7 @@ use Data::Dumper;
#init test_machine
my $test_machine = Test::Collection->new(
name => 'trustedsources',
embedded => [qw/subscribers/],
embedded_resources => [qw/subscribers/],
);
$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)};

@ -673,7 +673,7 @@ 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->DATA_CREATED->{FIRST}, "check patched self link");
is($rescontent->{_links}->{self}->{href}, $self->uri2location($req->uri), "check patched self link");
is($rescontent->{_links}->{collection}->{href}, '/api/'.$self->name.'/', "check patched collection link");
return ($res,$rescontent,$req);
}
@ -754,6 +754,13 @@ sub check_patch_opreplace_paramsextra{
$self->http_code_msg(400, "check patch extra fields for op", $res, $content);
like($content->{message}, qr/Invalid PATCH key /, "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) = @_;
@ -766,6 +773,7 @@ sub check_patch_bundle{
$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) = @_;
@ -878,7 +886,7 @@ sub check_get2put{
}
sub check_put2get{
my($self, $put_in, $get_in) = @_;
my($self, $put_in, $get_in, $nocheck) = @_;
my($put_out,$get_out);
@ -888,7 +896,6 @@ sub check_put2get{
$get_out->{uri} = $get_in->{uri};
$put_out->{content_in} = $self->process_data($put_in->{data_cb}, $put_in->{data_in});
$put_out->{content_in} = JSON::to_json($put_out->{content_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});
@ -896,8 +903,9 @@ sub check_put2get{
delete $get_out->{content}->{_links};
delete $get_out->{content}->{_embedded};
my $item_id = delete $get_out->{content}->{id};
$put_out->{content_in} = JSON::from_json($put_out->{content_in});
is_deeply($put_out->{content_in}, $get_out->{content}, "check_put2get: check PUTed item against POSTed item");
if(!$nocheck){
is_deeply($put_out->{content_in}, $get_out->{content}, "check_put2get: check PUTed item against POSTed item");
}
$get_out->{content}->{id} = $item_id;
return ($put_out,$get_out);
}
@ -954,6 +962,17 @@ sub resource_clear_file{
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;

Loading…
Cancel
Save