diff --git a/lib/NGCP/Panel/Role/API.pm b/lib/NGCP/Panel/Role/API.pm index d769d77396..68ccad8e2c 100644 --- a/lib/NGCP/Panel/Role/API.pm +++ b/lib/NGCP/Panel/Role/API.pm @@ -860,7 +860,7 @@ sub process_patch_description { if (ref $value_current eq 'ARRAY') { for (my $i=0; $i < @$value_current; $i++) { # 0 if different, 1 if equal - if (compare($value_current->[$i], $value_to_remove)) { + if ($self->compare_patch_value($c, $op, $value_current->[$i], $value_to_remove)) { if ( defined $remove_index ) { if ($found_count == $remove_index) { #if we want to use patch info to try to make clear changes, we shouldn't use replace @@ -880,7 +880,7 @@ sub process_patch_description { last; } } else { #current value is not an array - if (compare($value_current, $value_to_remove)) { + if ($self->compare_patch_value($c, $op, $value_current, $value_to_remove)) { push @$patch_diff, {"op" => "remove", "path" => $op->{path} }; } } @@ -894,6 +894,22 @@ sub process_patch_description { push @$patch, @$patch_diff; } +sub compare_patch_value { + my ($self, $c, $op, $value_current, $value_requested) = @_; + $value_requested //= $op->{value}; + my $value_to_compare; + if ( ref $value_current eq 'HASH' + && ref $value_requested eq 'HASH' + ) { + my @keys = keys %$value_requested; + $value_to_compare = {}; + @{$value_to_compare}{@keys} = @{$value_current}{@keys}; + } else { + $value_to_compare = $value_current; + } + return compare($value_to_compare, $value_requested) +} + sub apply_patch { my ($self, $c, $entity, $json, $optional_field_code_ref) = @_; my $patch = JSON::decode_json($json); diff --git a/sandbox/patch.pl b/sandbox/patch.pl new file mode 100644 index 0000000000..2e6035c655 --- /dev/null +++ b/sandbox/patch.pl @@ -0,0 +1,58 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Data::Dumper; +use NGCP::Schema; +use NGCP::Panel::Utils::Preferences; +use NGCP::Panel::Utils::Generic qw(:all); +use Safe::Isa qw($_isa); + +my $logger = Log::Log4perl->get_logger('NGCP::Panel'); +my $schema = NGCP::Schema->connect(); +my $dbh = $schema->storage->dbh; +use Test::MockObject; +my $c_mock = Test::MockObject->new(); +my $user_mock = Test::MockObject->new(); +$user_mock->set_always( 'roles' => 'admin' ); +$c_mock->set_always( 'log' => $logger )->set_always( 'model' => $schema )->set_always( 'user' => $user_mock ); + + + +my $cnt = 1000; +my $devmod_id = 1; + + +my $time = time; +for(my $i=0; $i<$cnt; $i++){ + my $dev_pref_rs = NGCP::Panel::Utils::Preferences::get_preferences_rs( + c => $c_mock, + type => 'dev', + id => $devmod_id, + ); + my $pref_values = get_inflated_columns_all($dev_pref_rs,'hash' => 'attribute', 'column' => 'value', 'force_array' => 1); +} +print "pure.time=".(time-$time).";\n"; + +my $time = time; +for(my $i=0; $i<$cnt; $i++){ + my $devprof_pref_rs = $c_mock->model('DB') + ->resultset('voip_preferences') + ->search({ + 'profile.id' => $devmod_id, + },{ + prefetch => {'voip_devprof_preferences' => 'profile'}, + }); + my %pref_values; + foreach my $value($devprof_pref_rs->all) { + $pref_values{$value->attribute} = + [ map {$_->value} $value->voip_devprof_preferences->all ]; + } +} +print "DBIx.time=".(time-$time).";\n"; + +#pure.time=5; +#DBIx.time=14; +r +1; \ No newline at end of file diff --git a/t/api-rest/api-patch-extended.t b/t/api-rest/api-patch-extended.t new file mode 100644 index 0000000000..d7df343e5b --- /dev/null +++ b/t/api-rest/api-patch-extended.t @@ -0,0 +1,105 @@ +use strict; +use warnings; + +use Test::Collection; +use Test::FakeData; +use Test::More; +use Data::Dumper; +use Clone qw/clone/; +use feature 'state'; +#use NGCP::Panel::Utils::Subscriber; +#use Data::Compare qw//; + +my $test_machine = Test::Collection->new( + name => 'subscribers', + QUIET_DELETION => 1, +); + +$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)}; +my $fake_data = Test::FakeData->new( + keep_db_data => 1, + test_machine => $test_machine, +); +$fake_data->load_collection_data('subscribers'); +my $fake_data_processed = $fake_data->process('subscribers'); + +my $pilot = $test_machine->get_item_hal('subscribers','/api/subscribers/?customer_id='.$fake_data_processed->{customer_id}.'&'.'is_pbx_pilot=1'); +if((exists $pilot->{total_count} && $pilot->{total_count}) || (exists $pilot->{content}->{total_count} && $pilot->{content}->{total_count} > 0) ){ + $fake_data_processed->{is_pbx_pilot} = 0; + #remove pilot aliases to don't intersect with them. On subscriber termination admin adopt numbers, see ticket#4967 + $test_machine->request_patch( [ { op => 'replace', path => '/alias_numbers', value => [] } ], $pilot->{location} ); +}else{ + undef $pilot; +} +$test_machine->DATA_ITEM_STORE($fake_data_processed); +$test_machine->form_data_item(); + +my $remote_config = $test_machine->init_catalyst_config; +#modify time changes on every data change, and primary_number_id on every primary number change +my $put2get_check_params = { ignore_fields => $fake_data->data->{subscribers}->{update_change_fields} }; + + +{ + my $member_to_terminate; + my $member_to_get_number; + my $pilot_local; + my $alias_numbers = [ + { ac => '115', cc=> 15, sn => '50975' }, + { ac => '116', cc=> 16, sn => '50975' }, + { ac => '117', cc=> 17, sn => '50975' }, + { ac => '118', cc=> 18, sn => '50975' }, + ]; + if(!$pilot) { + diag("50975: create pilot"); + $pilot = $test_machine->check_create_correct( 1, sub{ + my $num = $_[1]->{i}.Test::FakeData::seq; + $_[0]->{username} .= time().'_50975_'.$num ; + $_[0]->{webusername} .= time().'_'.$num; + $_[0]->{pbx_extension} .= '50975'.$num; + $_[0]->{primary_number}->{ac} .= $num; + $_[0]->{is_pbx_group} = 0; + $_[0]->{is_pbx_pilot} = 1; + $_[0]->{alias_numbers} = $alias_numbers; + } )->[0]; + $pilot_local = $pilot; + } else { + $pilot_local = $pilot; + + $test_machine->request_patch( [ { op => 'replace', path => '/alias_numbers', value => $alias_numbers } ], $pilot_local->{location} ); + } + diag("50975: attempt to remove alias_number by partial value"); + $test_machine->request_patch( [ { + op => 'remove', + path => '/alias_numbers', + value => { ac => '115', cc=> '15', sn => '50975' },} ], $pilot_local->{location} ); + my ($aliases) = $test_machine->get_collection_hal('numbers', '/api/numbers/?type=alias&subscriber_id='.$pilot_local->{content}->{id}, 1)->{collection}; + ok(((scalar @$aliases) == 3),"50975: aliases of ".$pilot_local->{content}->{id}." no one is removed without mode hashpartialfit:".(scalar @$aliases )); + #print Dumper $aliases; + diag("50975: attempt to remove wrong value"); + $test_machine->request_patch( [ { + op => 'remove', + path => '/alias_numbers', + value => { ac => '1151', cc=> '15', sn => '50975' },} ], $pilot_local->{location} ); + $aliases = $test_machine->get_collection_hal('numbers', '/api/numbers/?type=alias&subscriber_id='.$pilot_local->{content}->{id}, 1)->{collection}; + ok(((scalar @$aliases) == 3),"50975: aliases of ".$pilot_local->{content}->{id}." no one is removed:".(scalar @$aliases )); +} + +$test_machine->init_ssl_cert(); +$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 number_as_string{ + my ($number_row, %params) = @_; + return 'HASH' eq ref $number_row + ? $number_row->{cc} . ($number_row->{ac} // '') . $number_row->{sn} + : $number_row->cc . ($number_row->ac // '') . $number_row->sn; +} + +# vim: set tabstop=4 expandtab: