and add test script Change-Id: I70407cec591b8d719582dd476e7586496a795d69changes/38/19338/16
parent
ab10122651
commit
469d481b9f
@ -1,161 +1,166 @@
|
||||
package NGCP::Panel::Controller::API::NumbersItem;
|
||||
use NGCP::Panel::Utils::Generic qw(:all);
|
||||
|
||||
use Sipwise::Base;
|
||||
use NGCP::Panel::Utils::Generic qw(:all);
|
||||
|
||||
use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::Numbers/;
|
||||
|
||||
use boolean qw(true);
|
||||
use Data::HAL qw();
|
||||
use Data::HAL::Link qw();
|
||||
use HTTP::Headers qw();
|
||||
use HTTP::Status qw(:constants);
|
||||
use JSON::Types;
|
||||
|
||||
use NGCP::Panel::Utils::ValidateJSON qw();
|
||||
use NGCP::Panel::Utils::DateTime;
|
||||
use Path::Tiny qw(path);
|
||||
use Safe::Isa qw($_isa);
|
||||
require Catalyst::ActionRole::ACL;
|
||||
require NGCP::Panel::Role::HTTPMethods;
|
||||
require Catalyst::ActionRole::RequireSSL;
|
||||
__PACKAGE__->set_config({
|
||||
allowed_roles => {
|
||||
Default => [qw/admin reseller subscriberadmin/],
|
||||
Journal => [qw/admin reseller/],
|
||||
}
|
||||
});
|
||||
|
||||
sub allowed_methods{
|
||||
return [qw/GET PUT PATCH OPTIONS HEAD/];
|
||||
}
|
||||
|
||||
use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::Numbers/;
|
||||
|
||||
sub resource_name{
|
||||
return 'numbers';
|
||||
}
|
||||
|
||||
sub dispatch_path{
|
||||
return '/api/numbers/';
|
||||
}
|
||||
|
||||
sub relation{
|
||||
return 'http://purl.org/sipwise/ngcp-api/#rel-numbers';
|
||||
}
|
||||
|
||||
sub journal_query_params {
|
||||
my($self,$query_params) = @_;
|
||||
return $self->get_journal_query_params($query_params);
|
||||
}
|
||||
|
||||
__PACKAGE__->set_config({
|
||||
allowed_roles => {
|
||||
Default => [qw/admin reseller subscriberadmin/],
|
||||
Journal => [qw/admin reseller/],
|
||||
sub get_journal_methods{
|
||||
return [qw/handle_item_base_journal handle_journals_get handle_journalsitem_get handle_journals_options handle_journalsitem_options handle_journals_head handle_journalsitem_head handle_journalsitem_put handle_journalsitem_patch/];
|
||||
}
|
||||
|
||||
sub update_item_model {
|
||||
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
|
||||
my $schema = $c->model('DB');
|
||||
|
||||
# we maybe want to remove such checks to compare readonly fields:
|
||||
foreach my $field(qw/cc ac sn is_primary/) {
|
||||
unless(($old_resource->{$field} // '') eq ($resource->{$field} // '')
|
||||
or ('JSON::false' eq ref $resource->{$field} and $old_resource->{$field} == 0)
|
||||
or ('JSON::true' eq ref $resource->{$field} and $old_resource->{$field} == 1)) {
|
||||
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Field '$field' is not allowed to be updated via this API endpoint, use /api/subscriber/\$id instead.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sub GET :Allow {
|
||||
my ($self, $c, $id) = @_;
|
||||
{
|
||||
last unless $self->valid_id($c, $id);
|
||||
my $item = $self->item_by_id($c, $id);
|
||||
last unless $self->resource_exists($c, number => $item);
|
||||
|
||||
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
|
||||
s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|r =~
|
||||
s/rel=self/rel="item self"/r;
|
||||
} $hal->http_headers),
|
||||
), $hal->as_json);
|
||||
$c->response->headers($response->headers);
|
||||
$c->response->body($response->content);
|
||||
$form //= $self->get_form($c);
|
||||
return unless $self->validate_form(
|
||||
c => $c,
|
||||
form => $form,
|
||||
resource => $resource,
|
||||
);
|
||||
|
||||
my $sub = $schema->resultset('voip_subscribers')
|
||||
->find($resource->{subscriber_id});
|
||||
unless($sub) {
|
||||
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id', does not exist.");
|
||||
return;
|
||||
}
|
||||
if($c->user->roles eq "subscriberadmin" && $sub->contract_id != $c->user->account_id) {
|
||||
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id', does not exist.");
|
||||
return;
|
||||
}
|
||||
my $old_sub = $schema->resultset('voip_subscribers')->find($old_resource->{subscriber_id});
|
||||
if($old_sub->primary_number_id == $old_resource->{id}) {
|
||||
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Cannot reassign primary number, already at subscriber ".$old_sub->id);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub PUT :Allow {
|
||||
my ($self, $c, $id) = @_;
|
||||
my $guard = $c->model('DB')->txn_scope_guard;
|
||||
{
|
||||
my $preference = $self->require_preference($c);
|
||||
last unless $preference;
|
||||
try {
|
||||
|
||||
my $item = $self->item_by_id($c, $id);
|
||||
last unless $self->resource_exists($c, number => $item);
|
||||
my $resource = $self->get_valid_put_data(
|
||||
# capture old subscriber's aliases
|
||||
my $old_aliases_before = NGCP::Panel::Utils::Events::get_aliases_snapshot(
|
||||
c => $c,
|
||||
id => $id,
|
||||
media_type => 'application/json',
|
||||
schema => $schema,
|
||||
subscriber => $old_sub,
|
||||
);
|
||||
last unless $resource;
|
||||
my $old_resource = { $item->get_inflated_columns };
|
||||
|
||||
my $form = $self->get_form($c);
|
||||
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
|
||||
last unless $item;
|
||||
|
||||
$guard->commit;
|
||||
|
||||
if ('minimal' eq $preference) {
|
||||
$c->response->status(HTTP_NO_CONTENT);
|
||||
$c->response->header(Preference_Applied => 'return=minimal');
|
||||
$c->response->body(q());
|
||||
} else {
|
||||
my $hal = $self->hal_from_item($c, $item, $form);
|
||||
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
|
||||
$hal->http_headers,
|
||||
), $hal->as_json);
|
||||
$c->response->headers($response->headers);
|
||||
$c->response->header(Preference_Applied => 'return=representation');
|
||||
$c->response->body($response->content);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub PATCH :Allow {
|
||||
my ($self, $c, $id) = @_;
|
||||
my $guard = $c->model('DB')->txn_scope_guard;
|
||||
{
|
||||
my $preference = $self->require_preference($c);
|
||||
last unless $preference;
|
||||
# capture new subscriber's aliases
|
||||
my $aliases_before = NGCP::Panel::Utils::Events::get_aliases_snapshot(
|
||||
c => $c,
|
||||
schema => $schema,
|
||||
subscriber => $sub,
|
||||
);
|
||||
|
||||
my $json = $self->get_valid_patch_data(
|
||||
my $oldalias = [$old_sub->voip_numbers->all];
|
||||
$oldalias = [ map {
|
||||
if($_->id == $old_sub->primary_number_id) {
|
||||
# filter primary number
|
||||
();
|
||||
} else {
|
||||
if($_->id == $item->id) {
|
||||
# filter number we're about to remove
|
||||
();
|
||||
} else {
|
||||
# otherwise keep number
|
||||
{ e164 => { cc => $_->cc, ac => $_->ac, sn => $_->sn } };
|
||||
}
|
||||
}
|
||||
} @{ $oldalias } ];
|
||||
|
||||
NGCP::Panel::Utils::Subscriber::update_subscriber_numbers(
|
||||
c => $c,
|
||||
id => $id,
|
||||
media_type => 'application/json-patch+json',
|
||||
schema => $schema,
|
||||
alias_numbers => $oldalias,
|
||||
reseller_id => $old_sub->contract->contact->reseller_id,
|
||||
subscriber_id => $old_sub->id,
|
||||
);
|
||||
last unless $json;
|
||||
|
||||
my $item = $self->item_by_id($c, $id);
|
||||
last unless $self->resource_exists($c, number => $item);
|
||||
my $old_resource = { $item->get_inflated_columns };
|
||||
my $resource = $self->apply_patch($c, $old_resource, $json);
|
||||
last unless $resource;
|
||||
|
||||
my $form = $self->get_form($c);
|
||||
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
|
||||
last unless $item;
|
||||
|
||||
$guard->commit;
|
||||
|
||||
if ('minimal' eq $preference) {
|
||||
$c->response->status(HTTP_NO_CONTENT);
|
||||
$c->response->header(Preference_Applied => 'return=minimal');
|
||||
$c->response->body(q());
|
||||
} else {
|
||||
my $hal = $self->hal_from_item($c, $item, $form);
|
||||
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
|
||||
$hal->http_headers,
|
||||
), $hal->as_json);
|
||||
$c->response->headers($response->headers);
|
||||
$c->response->header(Preference_Applied => 'return=representation');
|
||||
$c->response->body($response->content);
|
||||
}
|
||||
|
||||
my $newalias = [ $sub->voip_numbers->all ];
|
||||
$newalias = [ map {
|
||||
if($_->id == $sub->primary_number_id) {
|
||||
# filter primary number
|
||||
();
|
||||
} else {
|
||||
if($_->id == $item->id) {
|
||||
# filter number we're about to remove
|
||||
();
|
||||
} else {
|
||||
# otherwise keep number
|
||||
{ e164 => { cc => $_->cc, ac => $_->ac, sn => $_->sn } };
|
||||
}
|
||||
}
|
||||
} @{ $newalias } ];
|
||||
push @{ $newalias }, { e164 => { cc => $item->cc, ac => $item->ac, sn => $item->sn } };
|
||||
|
||||
NGCP::Panel::Utils::Subscriber::update_subscriber_numbers(
|
||||
c => $c,
|
||||
schema => $schema,
|
||||
alias_numbers => $newalias,
|
||||
reseller_id => $sub->contract->contact->reseller_id,
|
||||
subscriber_id => $sub->id,
|
||||
);
|
||||
|
||||
# edr events for old sub
|
||||
my $old_sub_profile = $old_sub->provisioning_voip_subscriber->profile_id;
|
||||
NGCP::Panel::Utils::Events::insert_profile_events(
|
||||
c => $c, schema => $schema, subscriber_id => $old_sub->id,
|
||||
old => $old_sub_profile, new => $old_sub_profile,
|
||||
%$old_aliases_before,
|
||||
);
|
||||
|
||||
# edr events for new sub
|
||||
my $new_sub_profile = $sub->provisioning_voip_subscriber->profile_id;
|
||||
NGCP::Panel::Utils::Events::insert_profile_events(
|
||||
c => $c, schema => $schema, subscriber_id => $sub->id,
|
||||
old => $new_sub_profile, new => $new_sub_profile,
|
||||
%$aliases_before,
|
||||
);
|
||||
|
||||
} catch($e) {
|
||||
$c->log->error("failed to update number: $e");
|
||||
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to update number.");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_journal_methods{
|
||||
return [qw/handle_item_base_journal handle_journals_get handle_journalsitem_get handle_journals_options handle_journalsitem_options handle_journals_head handle_journalsitem_head handle_journalsitem_put handle_journalsitem_patch/];
|
||||
# reload item, in case the id changed (which shouldn't happen)
|
||||
$item = $self->_item_rs($c)->find({
|
||||
cc => $item->cc, ac => $item->ac, sn => $item->sn
|
||||
});
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
||||
|
@ -0,0 +1,103 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test::Collection;
|
||||
use Test::FakeData;
|
||||
use Test::More;
|
||||
use Data::Dumper;
|
||||
use Clone qw/clone/;
|
||||
|
||||
#use NGCP::Panel::Utils::Subscriber;
|
||||
|
||||
my $test_machine = Test::Collection->new(
|
||||
name => 'numbers',
|
||||
);
|
||||
my $fake_data = Test::FakeData->new;
|
||||
|
||||
$fake_data->set_data_from_script({
|
||||
'numbers' => {
|
||||
'data' => {
|
||||
subscriber_id => sub { return shift->get_id('subscribers',@_); },
|
||||
cc => 1,
|
||||
ac => 1,
|
||||
sn => 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
my $fake_data_processed = $fake_data->process('numbers');
|
||||
$test_machine->DATA_ITEM_STORE($fake_data_processed);
|
||||
$test_machine->form_data_item();
|
||||
|
||||
{
|
||||
my $ticket = '32913';
|
||||
my $time = time();
|
||||
my $subscriber_test_machine = Test::Collection->new(
|
||||
name => 'subscribers',
|
||||
);
|
||||
$subscriber_test_machine->DATA_ITEM_STORE($fake_data->process('subscribers'));
|
||||
my $subscriber1 = $fake_data->create('subscribers')->[0];
|
||||
my $subscriber2 = $fake_data->create('subscribers')->[0];
|
||||
#print Dumper $subscriber1;
|
||||
$subscriber1->{content}->{alias_numbers} = [
|
||||
{ cc=> '111', ac => $ticket, sn => $time },
|
||||
{ cc=> '112', ac => $ticket, sn => $time },
|
||||
{ cc=> '113', ac => $ticket, sn => $time },
|
||||
];
|
||||
$subscriber2->{content}->{alias_numbers} = [
|
||||
{ cc=> '211', ac => $ticket, sn => $time },
|
||||
{ cc=> '212', ac => $ticket, sn => $time },
|
||||
{ cc=> '213', ac => $ticket, sn => $time },
|
||||
];
|
||||
my ($res,$content,$request);
|
||||
($res,$content,$request) = $subscriber_test_machine->request_put(@{$subscriber1}{qw/content location/});
|
||||
($res,$content,$request) = $subscriber_test_machine->request_put(@{$subscriber2}{qw/content location/});
|
||||
my ($alias1) = $test_machine->get_item_hal('numbers', '/api/numbers/?type=alias&subscriber_id='.$subscriber1->{content}->{id});
|
||||
my ($alias2) = $test_machine->get_item_hal('numbers','/api/numbers/?type=alias&subscriber_id='.$subscriber2->{content}->{id});
|
||||
|
||||
test_numbers_reassign($alias1,$alias2,$subscriber1,$subscriber2);
|
||||
|
||||
my $pbxsubscriberadmin = $fake_data->create('subscribers')->[0];
|
||||
($res) = $subscriber_test_machine->request_patch([
|
||||
{ op => 'replace', path => '/administrative', value => 1 },
|
||||
{ op => 'replace', path => '/webpassword', value => 'pbxadminpwd' },
|
||||
{ op => 'replace', path => '/password', value => 'pbxadminpwd' }
|
||||
] , $pbxsubscriberadmin->{location});
|
||||
$subscriber_test_machine->http_code_msg(200, "PATCH for /pbxsubscriberadmin/", $res);
|
||||
$pbxsubscriberadmin = $subscriber_test_machine->get_item_hal('subscribers', $pbxsubscriberadmin->{location});
|
||||
|
||||
$test_machine->set_subscriber_credentials($pbxsubscriberadmin->{content});
|
||||
$test_machine->runas('subscriber');
|
||||
|
||||
test_numbers_reassign($alias1,$alias2,$subscriber1,$subscriber2);
|
||||
|
||||
$test_machine->runas('admin');
|
||||
|
||||
$subscriber_test_machine->clear_test_data_all();#fake data aren't registered in this test
|
||||
}
|
||||
|
||||
$fake_data->clear_test_data_all();
|
||||
$test_machine->clear_test_data_all();#fake data aren't registered in this test machine, so they will stay.
|
||||
$fake_data->clear_test_data_all();
|
||||
undef $test_machine;
|
||||
undef $fake_data;
|
||||
done_testing;
|
||||
|
||||
sub test_numbers_reassign{
|
||||
my($alias1,$alias2,$subscriber1,$subscriber2) = @_;
|
||||
my $res;
|
||||
$alias1->{content}->{subscriber_id} = $subscriber2->{content}->{id};
|
||||
($res) = $test_machine->request_patch([ { op => 'replace', path => '/subscriber_id', value => $subscriber2->{content}->{id} } ] , $alias1->{location});
|
||||
$test_machine->http_code_msg(200, "PATCH for /numbers/", $res);
|
||||
|
||||
($res) = $test_machine->request_patch([ { op => 'replace', path => '/subscriber_id', value => $subscriber1->{content}->{id} } ] , $alias1->{location});
|
||||
$test_machine->http_code_msg(200, "PATCH for /numbers/", $res);
|
||||
|
||||
($res) = $test_machine->request_patch([ { op => 'replace', path => '/subscriber_id', value => $subscriber1->{content}->{id} } ] , $alias2->{location});
|
||||
$test_machine->http_code_msg(200, "PATCH for /numbers/", $res);
|
||||
|
||||
($res) = $test_machine->request_patch([ { op => 'replace', path => '/subscriber_id', value => $subscriber2->{content}->{id} } ] , $alias2->{location});
|
||||
$test_machine->http_code_msg(200, "PATCH for /numbers/", $res);
|
||||
}
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
Loading…
Reference in new issue