Change-Id: I782bd536fe6dc18d35857519f463329fba0c9da2changes/90/1490/6
parent
a35314d0aa
commit
11979e04ad
@ -0,0 +1,330 @@
|
||||
package Test::DeepHashUtils;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Data::Dumper;
|
||||
|
||||
require Exporter;
|
||||
|
||||
our @ISA = qw(Exporter);
|
||||
our %EXPORT_TAGS = ( 'all' => [ qw( reach slurp nest deepvalue ) ] );
|
||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
||||
our @EXPORT = ();
|
||||
our $VERSION = '0.03';
|
||||
|
||||
|
||||
my $C;
|
||||
|
||||
# Recursive version of C<each>;
|
||||
sub reach {
|
||||
my $ref = shift;
|
||||
if (ref $ref eq 'HASH') {
|
||||
|
||||
if (defined $C->{$ref}{v}) {
|
||||
if (ref $C->{$ref}{v} eq 'HASH' || ref $C->{$ref}{v} eq 'ARRAY') {
|
||||
if (my @rec = reach($C->{$ref}{v})) {
|
||||
if (defined $C->{$ref}{k}) {
|
||||
return ($C->{$ref}{k},@rec);
|
||||
}
|
||||
if (defined $C->{$ref}{i}) {
|
||||
return ($C->{$ref}{i},@rec);
|
||||
}
|
||||
return @rec;
|
||||
}
|
||||
}
|
||||
undef $C->{$ref}{v};
|
||||
}
|
||||
|
||||
|
||||
if (my ($k,$v) = each %$ref) {
|
||||
$C->{$ref}{v} = $v;
|
||||
$C->{$ref}{k} = $k;
|
||||
return ($k,reach($v));
|
||||
}
|
||||
return ();
|
||||
|
||||
} elsif (ref $ref eq 'ARRAY') {
|
||||
|
||||
if (defined $C->{$ref}{v}) {
|
||||
if (ref $C->{$ref}{v} eq 'HASH' ||
|
||||
ref $C->{$ref}{v} eq 'ARRAY') {
|
||||
|
||||
if (my @rec = reach($C->{$ref}{v})) {
|
||||
if (defined $C->{$ref}{i}) {
|
||||
return $C->{$ref}{i},@rec;
|
||||
}
|
||||
if (defined $C->{$ref}{k}) {
|
||||
return $C->{$ref}{k},@rec;
|
||||
}
|
||||
return @rec;
|
||||
}
|
||||
}
|
||||
undef $C->{$ref}{v};
|
||||
}
|
||||
|
||||
if(!(defined $C->{$ref}{i})){
|
||||
$C->{$ref}{i} = 0;
|
||||
}else{
|
||||
$C->{$ref}{i}++;
|
||||
}
|
||||
if (my $v = $ref->[$C->{$ref}{i}]) {
|
||||
$C->{$ref}{v} = $v;
|
||||
return ($C->{$ref}{i}, reach($v));
|
||||
}
|
||||
|
||||
return ();
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
|
||||
|
||||
# run C<reach> over entire hash and return the final list of values at once
|
||||
sub slurp {
|
||||
my $ref = shift;
|
||||
my @h;
|
||||
while (my @a = reach($ref)) {
|
||||
push @h,\@a;
|
||||
}
|
||||
return @h;
|
||||
}
|
||||
|
||||
|
||||
# Define nested hash keys from the given list of values
|
||||
sub nest {
|
||||
my $hr = shift;
|
||||
my $ref = $hr;
|
||||
while ( @_ ) {
|
||||
my $key = shift @_;
|
||||
if (@_ > 1) {
|
||||
$ref = ('HASH' eq ref $ref ? $ref->{$key} : ('ARRAY' eq ref $ref ? $ref->[$key]:undef) ) ;
|
||||
$ref ||= {};
|
||||
} else {
|
||||
my $value = shift;
|
||||
if('HASH' eq ref $ref){
|
||||
$ref->{$key} = $value;
|
||||
}elsif('ARRAY' eq ref $ref){
|
||||
$ref->[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $hr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Return value at the end of the given nested hash keys and/or array indexes
|
||||
sub deepvalue {
|
||||
my $hr = shift;
|
||||
while (@_) {
|
||||
my $key = shift;
|
||||
if (ref $hr eq 'HASH') {
|
||||
return unless ($hr = $hr->{$key});
|
||||
} elsif (ref $hr eq 'ARRAY') {
|
||||
return unless ($hr = $hr->[$key]);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return $hr;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test::DeepHashUtils - functions for iterating over and working with nested hashes
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Deep::Hash::Utils qw(reach slurp nest deepvalue);
|
||||
|
||||
my %hash = (
|
||||
A => {
|
||||
B => {
|
||||
W => 1,
|
||||
X => 2,
|
||||
},
|
||||
C => {
|
||||
Y => 3,
|
||||
Z => 4,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
while (my @list = reach(\%hash)) {
|
||||
print "@list";
|
||||
}
|
||||
|
||||
for my $a (sort {$a->[2] cmp $b->[2]} slurp(\%hash)) {
|
||||
print "@$a";
|
||||
}
|
||||
|
||||
|
||||
|
||||
my %new_hash = ();
|
||||
|
||||
nest(\%new_hash,1,2,3,4,5);
|
||||
|
||||
my $value = deepvalue(\%new_hash,1,2,3,4);
|
||||
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module provides functions for accessing and modifying values in deeply nested data structures
|
||||
|
||||
|
||||
=head3 C<reach>
|
||||
|
||||
reach HASHREF
|
||||
|
||||
Iterate over each nested data structure contained in the given hash. Returns an array of each nested key/value set.
|
||||
|
||||
Just as C<each> lets you iterate over the keys and values in a single hash, C<reach> provides an iterator over any recursively nested data structures.
|
||||
|
||||
This helps avoid the need to use layers of nested loops in order to iterate over all entities in nested hashes and arrays.
|
||||
|
||||
The reference passed to C<reach> can contain any combination of nested hashes and arrays. Hash keys and values will be ordered in the same manner as when using C<each>, C<keys>, or C<values>.
|
||||
|
||||
use Deep::Hash::Utils qw(reach slurp nest);
|
||||
$\ = "\n";
|
||||
|
||||
my %hash = (
|
||||
A => {
|
||||
B => {
|
||||
W => 1,
|
||||
X => 2,
|
||||
},
|
||||
C => {
|
||||
Y => 3,
|
||||
Z => 4,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
while (my @list = reach(\%hash)) {
|
||||
print "@list";
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
Outputs:
|
||||
|
||||
A C Z 4
|
||||
A C Y 3
|
||||
A B W 1
|
||||
A B X 2
|
||||
|
||||
|
||||
|
||||
=head3 C<slurp>
|
||||
|
||||
slurp HASHREF
|
||||
|
||||
Returns a list of arrays generated by C<reach> at once.
|
||||
Use this if you want the same result of C<reach> with the ability to sort each layer of keys.
|
||||
|
||||
for my $a (sort {$a->[2] cmp $b->[2]} slurp(\%hash)) {
|
||||
print "@$a";
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
Output:
|
||||
|
||||
A B W 1
|
||||
A B X 2
|
||||
A C Y 3
|
||||
A C Z 4
|
||||
|
||||
|
||||
|
||||
=head3 C<nest>
|
||||
|
||||
nest HASHREF, LIST
|
||||
|
||||
define nested hash keys with a given list
|
||||
|
||||
use Data::Dumper;
|
||||
|
||||
my %new_hash = ();
|
||||
nest(\%new_hash,1,2,3,4,5);
|
||||
|
||||
print Dumper \%new_hash;
|
||||
|
||||
__END__
|
||||
|
||||
Output:
|
||||
|
||||
$VAR1 = {
|
||||
'1' => {
|
||||
'2' => {
|
||||
'3' => {
|
||||
'4' => 5
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
=head3 C<deepvalue>
|
||||
|
||||
deepvalue HASHREF, LIST
|
||||
|
||||
retrieve deeply nested values with a list of keys:
|
||||
|
||||
my %new_hash = (
|
||||
'1' => {
|
||||
'2' => {
|
||||
'3' => {
|
||||
'4' => 5
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
print Dumper deepvalue(\%new_hash,1,2,3,4);
|
||||
|
||||
print Dumper deepvalue(\%new_hash,1,2);
|
||||
|
||||
__END__
|
||||
|
||||
Output:
|
||||
|
||||
$VAR1 = 5;
|
||||
|
||||
$VAR1 = {
|
||||
'3' => {
|
||||
'4' => 5
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 REPOSITORY
|
||||
|
||||
L<https://github.com/neilbowers/perl-deep-hash-utils>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Chris Becker, E<lt>clbecker@gmail.com<gt>
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2009 by Chris Becker
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself, either Perl version 5.8.5 or,
|
||||
at your option, any later version of Perl 5 you may have available.
|
||||
|
||||
|
||||
|
||||
|
||||
=cut
|
||||
@ -0,0 +1,546 @@
|
||||
package Test::FakeData;
|
||||
|
||||
use strict;
|
||||
|
||||
use Sipwise::Base;
|
||||
use Test::Collection;
|
||||
use JSON;
|
||||
use Test::More;
|
||||
use File::Basename;
|
||||
use Data::Dumper;
|
||||
use Test::DeepHashUtils qw(reach nest deepvalue);
|
||||
use Clone qw/clone/;
|
||||
|
||||
has 'test_machine' =>(
|
||||
is => 'rw',
|
||||
isa => 'Test::Collection',
|
||||
default => sub { Test::Collection->new () },
|
||||
);
|
||||
has 'created' => (
|
||||
is => 'rw',
|
||||
isa => 'HashRef',
|
||||
default => sub { {} },
|
||||
);
|
||||
has 'loaded' => (
|
||||
is => 'rw',
|
||||
isa => 'HashRef',
|
||||
default => sub { {} },
|
||||
);
|
||||
has 'data_default' => (
|
||||
is => 'rw',
|
||||
isa => 'HashRef',
|
||||
builder => 'build_data_default',
|
||||
);
|
||||
has 'data' => (
|
||||
is => 'rw',
|
||||
isa => 'HashRef',
|
||||
builder => 'build_data',
|
||||
);
|
||||
has 'FLAVOUR' => (
|
||||
is => 'rw',
|
||||
isa => 'Str',
|
||||
);
|
||||
#TODO: optimization - pre load and predelete should be done only for required collections and dependencies
|
||||
has 'work_collections' => (
|
||||
is => 'rw',
|
||||
isa => 'ArrayRef',
|
||||
default => sub { [] },
|
||||
);
|
||||
sub build_data_default{
|
||||
return {
|
||||
'products' => [
|
||||
{
|
||||
id => 1,
|
||||
reseller_id => undef,
|
||||
class => 'pstnpeering',
|
||||
handle => 'PSTN_PEERING',
|
||||
name => 'PSTN Peering',
|
||||
on_sale => 1,
|
||||
price => undef,
|
||||
weight => undef,
|
||||
billing_profile_id => undef,
|
||||
},{
|
||||
id => 2,
|
||||
reseller_id => undef,
|
||||
class => 'sippeering',
|
||||
handle => 'SIP_PEERING',
|
||||
name => 'PSTN Peering',
|
||||
on_sale => 1,
|
||||
price => undef,
|
||||
weight => undef,
|
||||
billing_profile_id => undef,
|
||||
},{
|
||||
id => 3,
|
||||
reseller_id => undef,
|
||||
class => 'reseller',
|
||||
handle => 'VOIP_RESELLER',
|
||||
name => 'VoIP Reseller',
|
||||
on_sale => 1,
|
||||
price => undef,
|
||||
weight => undef,
|
||||
billing_profile_id => undef,
|
||||
},
|
||||
],
|
||||
'contracts' => {
|
||||
id => 1,
|
||||
customer_id => undef,
|
||||
reseller_id => undef,
|
||||
contact_id => undef,
|
||||
order_id => undef,
|
||||
status => 'active',
|
||||
modify_timestamp => '0',
|
||||
create_timestamp => '0',
|
||||
activate_timestamp => '0',
|
||||
terminate_timestamp => undef,
|
||||
},
|
||||
'resellers' => {
|
||||
id => 1,
|
||||
contract_id => 1,
|
||||
name => 'default',
|
||||
status => 'active',
|
||||
},
|
||||
'billing_mappings' => {
|
||||
id => 1,
|
||||
start_date => undef,
|
||||
end_date => undef,
|
||||
billing_profile_id => undef,
|
||||
contract_id => 1,
|
||||
product_id => 3,
|
||||
},
|
||||
'billing_profiles' => {
|
||||
id => 1,
|
||||
reseller_id => 1,
|
||||
handle => 'default',
|
||||
name => 'Default Billing Profile',
|
||||
prepaid => 1,
|
||||
interval_charge => 0,
|
||||
interval_free_time => 0,
|
||||
interval_free_cash => 0,
|
||||
interval_unit => 'month',
|
||||
interval_count => 1,
|
||||
currency => undef,
|
||||
vat_rate => 0,
|
||||
vat_included => 0,
|
||||
},
|
||||
'billing_zones' => {
|
||||
id => 1,
|
||||
billing_profile_id => 1,
|
||||
zone => 'Free Default Zone',
|
||||
detail => 'All Destinations',
|
||||
},
|
||||
'billing_fees' => {
|
||||
id => 1,
|
||||
billing_profile_id => 1,
|
||||
billing_zone_id => 1,
|
||||
destination => '.*',
|
||||
type => 'call',
|
||||
onpeak_init_rate => 0,
|
||||
onpeak_init_interval => 600,
|
||||
onpeak_follow_rate => 0,
|
||||
onpeak_follow_interval => 600,
|
||||
offpeak_init_rate => 0,
|
||||
offpeak_init_interval => 600,
|
||||
offpeak_follow_rate => 0,
|
||||
offpeak_follow_interval => 600,
|
||||
},
|
||||
'domains' => {
|
||||
domain => 'voip.sipwise.local',
|
||||
local => 1,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sub build_data{
|
||||
my ($self) = @_;
|
||||
my $data = {
|
||||
'systemcontacts' => {
|
||||
'data' => {
|
||||
email => 'api_test_reseller@reseller.invalid',
|
||||
firstname => 'api_test first',
|
||||
lastname => 'api_test last',
|
||||
},
|
||||
'query' => ['email'],
|
||||
'delete_potentially_dependent' => 1,
|
||||
},
|
||||
'customercontacts' => {
|
||||
'data' => {
|
||||
firstname => 'api_test cust_contact_first',
|
||||
lastname => 'api_test cust_contact_last',
|
||||
email => 'api_test_cust_contact@custcontact.invalid',
|
||||
reseller_id => sub { return shift->create('resellers',@_); },
|
||||
},
|
||||
'query' => ['email'],
|
||||
'delete_potentially_dependent' => 1,
|
||||
},
|
||||
'contracts' => {
|
||||
'data' => {
|
||||
contact_id => sub { return shift->create('systemcontacts',@_); },
|
||||
status => 'active',
|
||||
external_id => 'api_test',
|
||||
#type => sub { return value_request('contracts','type',['reseller']); },
|
||||
type => 'reseller',
|
||||
billing_profile_id => sub { return shift->create('billingprofiles',@_); },
|
||||
},
|
||||
'default' => 'contracts',
|
||||
'query' => ['external_id'],
|
||||
'no_delete_available' => 1,
|
||||
},
|
||||
'resellers' => {
|
||||
'data' => {
|
||||
contract_id => sub { return shift->create('contracts', @_ ); },
|
||||
name => 'api_test test reseller',
|
||||
status => 'active',
|
||||
},
|
||||
'default' => 'resellers',
|
||||
'query' => ['name'],
|
||||
'no_delete_available' => 1,
|
||||
},
|
||||
'customers' => {
|
||||
'data' => {
|
||||
status => 'active',
|
||||
contact_id => sub { return shift->create('customercontacts',@_); },
|
||||
billing_profile_id => sub { return shift->create('billingprofiles',@_); },
|
||||
max_subscribers => undef,
|
||||
external_id => 'api_test customer',
|
||||
type => 'pbxaccount',#sipaccount
|
||||
},
|
||||
'query' => ['external_id'],
|
||||
'no_delete_available' => 1,
|
||||
},
|
||||
'billingprofiles' => {
|
||||
'data' => {
|
||||
name => 'api_test test profile',
|
||||
handle => 'api_test_testprofile',
|
||||
reseller_id => sub { return shift->create('resellers',@_); },
|
||||
},
|
||||
'default' => 'billing_profiles',
|
||||
'query' => ['handle'],
|
||||
'no_delete_available' => 1,
|
||||
},
|
||||
'subscribers' => {
|
||||
'data' => {
|
||||
administrative => 0,
|
||||
customer_id => sub { return shift->create('customers',@_); },
|
||||
primary_number => { ac => 111, cc=> 111, sn => 111 },
|
||||
alias_numbers => [ { ac => 11, cc=> 11, sn => 11 } ],
|
||||
username => 'api_test_username',
|
||||
password => 'api_test_password',
|
||||
webusername => 'api_test_webusername',
|
||||
webpassword => undef,
|
||||
domain_id => sub { return shift->create('domains',@_); },,
|
||||
#domain_id =>
|
||||
email => undef,
|
||||
external_id => undef,
|
||||
is_pbx_group => 1,
|
||||
is_pbx_pilot => 1,
|
||||
pbx_extension => '111',
|
||||
pbx_group_ids => [],
|
||||
pbx_groupmember_ids => [],
|
||||
profile_id => sub { return shift->create('subscriberprofiles',@_); },
|
||||
status => 'active',
|
||||
pbx_hunt_policy => 'parallel',
|
||||
pbx_hunt_timeout => '15',
|
||||
},
|
||||
'query' => ['username'],
|
||||
},
|
||||
'domains' => {
|
||||
'data' => {
|
||||
domain => 'api_test_domain.api_test_domain',
|
||||
reseller_id => sub { return shift->create('resellers',@_); },
|
||||
},
|
||||
'query' => ['domain'],
|
||||
},
|
||||
'subscriberprofilesets' => {
|
||||
'data' => {
|
||||
name => 'api_test_subscriberprofileset',
|
||||
reseller_id => sub { return shift->create('resellers',@_); },
|
||||
description => 'api_test_subscriberprofileset',
|
||||
},
|
||||
'query' => ['name'],
|
||||
},
|
||||
'subscriberprofiles' => {
|
||||
'data' => {
|
||||
name => 'api_test subscriberprofile',
|
||||
profile_set_id => sub { return shift->create('subscriberprofilesets',@_); },
|
||||
description => 'api_test subscriberprofile',
|
||||
},
|
||||
'query' => ['name'],
|
||||
},
|
||||
'pbxdevicemodels' => {
|
||||
'data' => {
|
||||
json => {
|
||||
model => "api_test ATA111",
|
||||
#reseller_id=1 is very default, as is seen from the base initial script
|
||||
#reseller_id => "1",
|
||||
reseller_id => sub { return shift->create('resellers',@_); },
|
||||
vendor =>"Cisco",
|
||||
#3.7relative tests
|
||||
type => "phone",
|
||||
connectable_models => [],
|
||||
extensions_num => "2",
|
||||
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 => "",
|
||||
#TODO:implement checking against this number in the controller and api
|
||||
#/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 api_test",
|
||||
#TODO: test duplicate creation #"id"=>1311,
|
||||
},
|
||||
{
|
||||
"keys"=>[
|
||||
{y => "390", labelpos => "left", x => "510"},
|
||||
{y => "350", labelpos => "left", x => "510"}
|
||||
],
|
||||
can_private => "1",
|
||||
can_shared => "0",
|
||||
#TODO: If I'm right - now we don't check field values against this, because test for pbxdevice xreation is OK
|
||||
can_blf => "0",
|
||||
name => "Extra Ports api_test",
|
||||
#TODO: test duplicate creation #"id"=>1311,
|
||||
}
|
||||
]
|
||||
},
|
||||
#TODO: can check big files
|
||||
#front_image => [ dirname($0).'/resources/api_devicemodels_front_image.jpg' ],
|
||||
front_image => [ dirname($0).'/resources/empty.txt' ],
|
||||
},
|
||||
'query' => [ ['model','json','model'] ],
|
||||
'create_special'=> sub {
|
||||
my ($self,$name) = @_;
|
||||
my $prev_params = $self->test_machine->get_cloned('content_type');
|
||||
@{$self->test_machine->content_type}{qw/POST PUT/} = (('multipart/form-data') x 2);
|
||||
$self->test_machine->check_create_correct(1);
|
||||
$self->test_machine->set(%$prev_params);
|
||||
},
|
||||
'no_delete_available' => 1,
|
||||
},
|
||||
'pbxdeviceconfigs' => {
|
||||
'data' => {
|
||||
device_id => sub { return shift->create('pbxdevicemodels',@_); },
|
||||
version => 'api_test 1.1',
|
||||
content_type => 'text/plain',
|
||||
},
|
||||
'query' => ['version'],
|
||||
'create_special'=> sub {
|
||||
my ($self,$name) = @_;
|
||||
my $prev_params = $self->test_machine->get_cloned('content_type','QUERY_PARAMS');
|
||||
$self->test_machine->content_type->{POST} = $self->data->{$name}->{data}->{content_type};
|
||||
$self->test_machine->QUERY_PARAMS($self->test_machine->hash2params($self->data->{$name}->{data}));
|
||||
$self->test_machine->check_create_correct(1, sub {return 'test_api_empty_config';} );
|
||||
$self->test_machine->set(%$prev_params);
|
||||
},
|
||||
'no_delete_available' => 1,
|
||||
},
|
||||
'pbxdeviceprofiles' => {
|
||||
'data' => {
|
||||
config_id => sub { return shift->create('pbxdeviceconfigs',@_); },
|
||||
name => 'api_test profile 1.1',
|
||||
},
|
||||
'query' => ['name'],
|
||||
'no_delete_available' => 1,
|
||||
},
|
||||
'pbxdevices' => {
|
||||
'data' => {
|
||||
profile_id => sub { return shift->create('pbxdeviceprofiles',@_); },
|
||||
customer_id => sub { return shift->create('customers',@_); },
|
||||
identifier => 'aaaabbbbcccc',
|
||||
station_name => 'api_test_vun',
|
||||
lines=>[{
|
||||
linerange => 'Phone Ports api_test',
|
||||
type => 'private',
|
||||
key_num => '0',
|
||||
subscriber_id => sub { return shift->create('subscribers',@_); },
|
||||
extension_unit => '1',
|
||||
extension_num => '1',#to handle some the same extensions devices
|
||||
},{
|
||||
linerange => 'Extra Ports api_test',
|
||||
type => 'blf',
|
||||
key_num => '1',
|
||||
subscriber_id => sub { return shift->create('subscribers',@_); },
|
||||
extension_unit => '2',
|
||||
}],
|
||||
},
|
||||
'query' => ['station_name'],
|
||||
},
|
||||
};
|
||||
foreach my $collection_name( keys %$data ){
|
||||
if($self->FLAVOUR && exists $data->{$collection_name}->{flavour} && exists $data->{$collection_name}->{flavour}->{$self->FLAVOUR}){
|
||||
$data = {%$data, %{$data->{$collection_name}->{flavour}->{$self->FLAVOUR}}},
|
||||
}
|
||||
}
|
||||
$self->clear_db($data,[qw/contracts systemcontacts customercontacts/]);
|
||||
#incorrect place, leave it for the next timeframe to work on it
|
||||
$self->load_db($data);
|
||||
return $data;
|
||||
}
|
||||
sub load_db{
|
||||
my($self,$data) = @_;
|
||||
$data //= $self->data;
|
||||
foreach my $collection_name( keys %$data ){
|
||||
#print "collection_name=$collection_name;\n";
|
||||
if((!exists $self->loaded->{$collection_name}) && $data->{$collection_name}->{query}){
|
||||
my(undef,$content) = $self->search_item($collection_name,$data);
|
||||
if($content->{total_count}){
|
||||
my $values = $content->{_embedded}->{$self->test_machine->get_hal_name};
|
||||
$values = ('HASH' eq ref $values) ? [$values] : $values;
|
||||
$self->loaded->{$collection_name} = [ map {
|
||||
{
|
||||
location => $_->{_links}->{self}->{href},
|
||||
content => $_,
|
||||
}
|
||||
} @$values ];
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
sub clear_db{
|
||||
my($self,$data,$order_array) = @_;
|
||||
$order_array //= [];
|
||||
my $order_hash = {};
|
||||
@$order_hash{(keys %$data)} = (0) x (keys %$data);
|
||||
@$order_hash{@$order_array} = (1..$#$order_array+1);
|
||||
my @undeletable_items = ();
|
||||
foreach my $collection_name (sort {$order_hash->{$a} <=> $order_hash->{$b}} keys %$data ){
|
||||
if((!$data->{$collection_name}->{query})){
|
||||
next;
|
||||
}
|
||||
my(undef,$content) = $self->search_item($collection_name,$data);
|
||||
if($content->{total_count}){
|
||||
my $values = $content->{_links}->{$self->test_machine->get_hal_name};
|
||||
$values =
|
||||
('HASH' eq ref $values) ? [$values] : $values;
|
||||
my @locations = map {$_->{href}} @$values;
|
||||
if($data->{$collection_name}->{no_delete_available}){
|
||||
push @undeletable_items, @locations;
|
||||
}else{
|
||||
if($data->{$collection_name}->{delete_potentially_dependent}){
|
||||
foreach( @locations ){
|
||||
if(!$self->test_machine->clear_test_data_dependent($_)){
|
||||
push @undeletable_items, $_;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$self->test_machine->clear_test_data_all([ @locations ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(@undeletable_items){
|
||||
print "We have test items, which can't delete through API:\n";
|
||||
print Dumper [ @undeletable_items ];
|
||||
}
|
||||
return;
|
||||
}
|
||||
sub search_item{
|
||||
my($self,$collection_name,$data) = @_;
|
||||
$data //= $self->data;
|
||||
my $item = $data->{$collection_name};
|
||||
if(!$item->{query}){
|
||||
return;
|
||||
}
|
||||
$self->test_machine->name($collection_name);
|
||||
my $query_string = join('&', map {
|
||||
my @deep_keys = ('ARRAY' eq ref $_) ? @$_:($_);
|
||||
my $field_name = ( @deep_keys > 1 ) ? shift @deep_keys : $deep_keys[0];
|
||||
$field_name.'='.deepvalue($item->{data},@deep_keys);
|
||||
} @{$item->{query}}
|
||||
);
|
||||
my($res, $content, $req) = $self->test_machine->check_item_get($self->test_machine->get_uri_get($query_string));
|
||||
return ($res, $content, $req);
|
||||
}
|
||||
sub create{
|
||||
my($self, $name, $parents_in, $field_path, $params) = @_;
|
||||
$parents_in //= {};
|
||||
if($self->loaded->{$name} || $self->created->{$name}){
|
||||
return $self->get_id($name);
|
||||
}
|
||||
if($parents_in->{$name}){
|
||||
if($self->data->{$name}->{default}){
|
||||
$self->data->{$name}->{process_cycled} = {'parents'=>$parents_in,'field_path'=>$field_path};
|
||||
return $self->data_default->{$self->data->{$name}->{default}}->{id};
|
||||
}else{
|
||||
die('Data absence', Dumper([$name,$parents_in]));
|
||||
}
|
||||
}
|
||||
$self->process($name, $parents_in);
|
||||
#create itself
|
||||
my $data = clone($self->data->{$name}->{data});
|
||||
$self->test_machine->set(
|
||||
name => $name,
|
||||
DATA_ITEM => $data,
|
||||
);
|
||||
if(exists $self->data->{$name}->{create_special} && 'CODE' eq ref $self->data->{$name}->{create_special}){
|
||||
$self->data->{$name}->{create_special}->($self,$name);
|
||||
}else{
|
||||
$self->test_machine->check_create_correct(1);
|
||||
}
|
||||
$self->created->{$name} = [values %{$self->test_machine->DATA_CREATED->{ALL}}];
|
||||
|
||||
if($self->data->{$name}->{process_cycled}){
|
||||
my %parents_cycled_ordered = reverse %{$self->data->{$name}->{process_cycled}->{parents}};
|
||||
my $last_parent = -1 + ( scalar values (%parents_cycled_ordered) );
|
||||
my $uri = $self->test_machine->get_uri_collection($parents_cycled_ordered{$last_parent}).$self->get_id($parents_cycled_ordered{$last_parent});
|
||||
$self->test_machine->request_patch([ {
|
||||
op => 'replace',
|
||||
path => join('/',('',@{$self->data->{$name}->{process_cycled}->{field_path}})),
|
||||
value => $self->get_id($name) } ],
|
||||
$uri
|
||||
);
|
||||
delete $self->data->{$name}->{process_cycled};
|
||||
}
|
||||
return $self->get_id($name);
|
||||
}
|
||||
|
||||
sub process{
|
||||
my($self, $name, $parents_in) = @_;
|
||||
$parents_in //= {};
|
||||
my $parents = {%{$parents_in}};
|
||||
$parents->{$name} //= scalar values %$parents_in;
|
||||
while (my @keys_and_value = reach($self->data->{$name}->{data})){
|
||||
my $field_value = pop @keys_and_value;
|
||||
if('CODE' eq ref $field_value ){
|
||||
my $value = $field_value->($self,$parents,[@keys_and_value]);
|
||||
nest( $self->data->{$name}->{data}, @keys_and_value, $value );
|
||||
}
|
||||
}
|
||||
return $self->data->{$name}->{data};
|
||||
}
|
||||
sub get_id{
|
||||
my($self, $name) = @_;
|
||||
my $id = $self->test_machine->get_id_from_created($self->created->{$name}->[0])
|
||||
|| $self->test_machine->get_id_from_created($self->loaded->{$name}->[0]);
|
||||
return $id
|
||||
}
|
||||
sub DEMOLISH{
|
||||
my($self) = @_;
|
||||
( 'ARRAY' eq ref$self->created ) and ( $self->test_machine->clear_test_data_all([ map {$_->{location}} @$self->created ]) );
|
||||
}
|
||||
1;
|
||||
__END__
|
||||
|
||||
Further improvements:
|
||||
Really it would be much more correct to use collection clases with ALL their own test machine initialization for data creation. just will call proper collection class. It will allow to keep data near releveant tests, and don't duplicate test_machine params in the FakeData.
|
||||
|
||||
Optimizations:
|
||||
1.make wrapper for data creation/deletion for all tests.
|
||||
2.Load/Clear only relevant tests data
|
||||
|
||||
@ -1,143 +0,0 @@
|
||||
#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,104 @@
|
||||
#use Sipwise::Base;
|
||||
use strict;
|
||||
|
||||
#use Moose;
|
||||
use Sipwise::Base;
|
||||
use Test::Collection;
|
||||
use Test::FakeData;
|
||||
use Net::Domain qw(hostfqdn);
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Request::Common;
|
||||
use JSON;
|
||||
use Test::More;
|
||||
use Data::Dumper;
|
||||
|
||||
|
||||
#init test_machine
|
||||
my $fake_data = Test::FakeData->new;
|
||||
my $test_machine = 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)};
|
||||
$test_machine->KEEP_CREATED( 1 );
|
||||
$test_machine->DATA_ITEM_STORE($fake_data->process('pbxdevicemodels'));
|
||||
|
||||
|
||||
my $connactable_devices={};
|
||||
|
||||
foreach my $type(qw/extension phone/){
|
||||
#last;#skip classic tests
|
||||
$test_machine->form_data_item( sub {$_[0]->{json}->{type} = $type;} );
|
||||
# create 3 & 3 new billing models from DATA_ITEM
|
||||
$test_machine->check_create_correct( 3, sub{ $_[0]->{json}->{model} .= $type."TEST_".$_[1]->{i}; } );
|
||||
#print Dumper $test_machine->DATA_CREATED->{ALL};
|
||||
$connactable_devices->{$type}->{data} = [ values %{$test_machine->DATA_CREATED->{ALL}}];
|
||||
$connactable_devices->{$type}->{ids} = [ map {$test_machine->get_id_from_created($_)} @{$connactable_devices->{$type}->{data}}];
|
||||
}
|
||||
sub get_connectable_type{
|
||||
my $type = shift;
|
||||
return ('extension' eq $type) ? 'phone' : 'extension';
|
||||
}
|
||||
foreach my $type(qw/extension phone/){
|
||||
#last;#skip classic tests
|
||||
$test_machine->form_data_item( sub {$_[0]->{json}->{type} = $type;} );
|
||||
# create 3 next new models from DATA_ITEM
|
||||
$test_machine->check_create_correct( 3, sub{
|
||||
$_[0]->{json}->{model} .= $type."TEST_".($_[1]->{i} + 3);
|
||||
$_[0]->{json}->{connactable_devices} = $connactable_devices->{ get_connectable_type( $type) }->{ids};
|
||||
} );
|
||||
$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 the reseller_id");
|
||||
foreach(qw/vendor model/){
|
||||
ok(exists $item_first_get->{$_}, "check existence of $_");
|
||||
}
|
||||
# check if we have the proper links
|
||||
}
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
#pbxdevicemodels doesn't have DELETE method
|
||||
#`echo 'delete from autoprov_devices where model like "%api_test %" or model like "patched model%";'|mysql provisioning`;
|
||||
done_testing;
|
||||
|
||||
# vim: set tabstop=4 expandtab:
|
||||
Loading…
Reference in new issue