diff --git a/lib/NGCP/Panel/Controller/API/BillingZones.pm b/lib/NGCP/Panel/Controller/API/BillingZones.pm index b2fd949522..2e90564b62 100644 --- a/lib/NGCP/Panel/Controller/API/BillingZones.pm +++ b/lib/NGCP/Panel/Controller/API/BillingZones.pm @@ -37,6 +37,17 @@ class_has 'query_params' => ( second => sub {}, }, }, + { + param => 'zone', + description => 'Filter for zone name', + query => { + first => sub { + my $q = shift; + { zone => { like => '%'.$q.'%' } }; + }, + second => sub {}, + }, + }, ]}, ); diff --git a/t/api-billingfees.t b/t/api-billingfees.t index c16855ed9b..1d977db838 100644 --- a/t/api-billingfees.t +++ b/t/api-billingfees.t @@ -1,454 +1,166 @@ +#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 JSON qw(); +use HTTP::Request::Common; +use JSON; use Test::More; - -my $uri = $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443'); - -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, $req, $res); -$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, +use Data::Dumper; + + +#init test_machine +my $fake_data = Test::FakeData->new; +$fake_data->set_data_from_script({ + 'billingfees' => { + data => { + billing_profile_id => sub { return shift->get_id('billingprofiles', @_); }, + billing_zone_id => sub { return shift->get_id('billingzones', @_); }, + destination => "^1234", + direction => "out", + onpeak_init_rate => 1, + onpeak_init_interval => 60, + onpeak_follow_rate => 1, + onpeak_follow_interval => 30, + offpeak_init_rate => 0.5, + offpeak_init_interval => 60, + offpeak_follow_rate => 0.5, + offpeak_follow_interval => 30, + }, + 'query' => ['billing_zone_id'], + }, +}); +my $test_machine = Test::Collection->new( + name => 'billingfees', + embedded => [qw/billingzones billingprofiles/] ); - -# OPTIONS tests +$test_machine->DATA_ITEM_STORE($fake_data->process('billingfees')); +$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)}; +$test_machine->form_data_item( ); +# create 3 new field billing fees from DATA_ITEM +$test_machine->check_create_correct( 3, sub{ $_[0]->{destination} .= $_[1]->{i} ; } ); +$test_machine->check_get2put( ); +$test_machine->check_bundle(); + +# specific tests + +# try to create fee without billing_profile_id { - $req = HTTP::Request->new('OPTIONS', $uri.'/api/billingfees/'); - $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-billingfees", "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 POST )) { - 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 $reseller_id = 1; - -# first, we need a billing profile -$req = HTTP::Request->new('POST', $uri.'/api/billingprofiles/'); -$req->header('Content-Type' => 'application/json'); -$req->header('Prefer' => 'return=representation'); -my $t = time; -$req->content(JSON::to_json({ - reseller_id => $reseller_id, - handle => "testapihandle$t", - name => "test api name $t", -})); -$res = $ua->request($req); -is($res->code, 201, "create test billing profile"); -my $billing_profile_id = $res->header('Location'); -# TODO: get it from body! -$billing_profile_id =~ s/^.+\/(\d+)$/$1/; - -# then, we need a billing zone -$req = HTTP::Request->new('POST', $uri.'/api/billingzones/'); -$req->header('Content-Type' => 'application/json'); -$req->header('Prefer' => 'return=representation'); -$req->content(JSON::to_json({ - billing_profile_id => $billing_profile_id, - zone => "testzone", - detail => "test zone from api", -})); -$res = $ua->request($req); -is($res->code, 201, "create test billing zone"); -my $billing_zone_id = $res->header('Location'); -# TODO: get it from body! -$billing_zone_id =~ s/^.+\/(\d+)$/$1/; - -# collection test -my $firstfee = undef; -my @allfees = (); -{ - # create 6 new billing profiles - my %fees = (); - for(my $i = 1; $i <= 6; ++$i) { - $req = HTTP::Request->new('POST', $uri.'/api/billingfees/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json({ - billing_profile_id => $billing_profile_id, - billing_zone_id => $billing_zone_id, - destination => "^1234$i", - direction => "out", - onpeak_init_rate => 1, - onpeak_init_interval => 60, - onpeak_follow_rate => 1, - onpeak_follow_interval => 30, - offpeak_init_rate => 0.5, - offpeak_init_interval => 60, - offpeak_follow_rate => 0.5, - offpeak_follow_interval => 30, - })); - $res = $ua->request($req); - is($res->code, 201, "create test billing fee $i"); - $fees{$res->header('Location')} = 1; - push @allfees, $res->header('Location'); - $firstfee = $res->header('Location') unless $firstfee; - } - - # try to create fee without billing_profile_id - $req = HTTP::Request->new('POST', $uri.'/api/billingfees/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json({ - #billing_profile_id => $billing_profile_id, - billing_zone_id => $billing_zone_id, - destination => "^1234", - direction => "out", - onpeak_init_rate => 1, - onpeak_init_interval => 60, - onpeak_follow_rate => 1, - onpeak_follow_interval => 30, - offpeak_init_rate => 0.5, - offpeak_init_interval => 60, - offpeak_follow_rate => 0.5, - offpeak_follow_interval => 30, - })); - $res = $ua->request($req); - is($res->code, 422, "create profile without billing_profile_id"); - my $err = JSON::from_json($res->decoded_content); + my ($res, $err) = $test_machine->request_post(sub{delete $_[0]->{billing_profile_id};}); + is($res->code, 422, "create billing zone without billing_profile_id"); is($err->{code}, "422", "check error code in body"); ok($err->{message} =~ /Missing parameter 'billing_profile_id'/, "check error message in body"); - - # try to create fee with invalid billing_profile_id - $req = HTTP::Request->new('POST', $uri.'/api/billingfees/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json({ - billing_profile_id => 99999, - billing_zone_id => $billing_zone_id, - destination => "^1234", - direction => "out", - onpeak_init_rate => 1, - onpeak_init_interval => 60, - onpeak_follow_rate => 1, - onpeak_follow_interval => 30, - offpeak_init_rate => 0.5, - offpeak_init_interval => 60, - offpeak_follow_rate => 0.5, - offpeak_follow_interval => 30, - })); - $res = $ua->request($req); - is($res->code, 422, "create profile with invalid billing_profile_id"); - $err = JSON::from_json($res->decoded_content); +} +# try to create fee with invalid billing_profile_id +{ + my ($res, $err) = $test_machine->request_post(sub{$_[0]->{billing_profile_id} = 99999;}); + is($res->code, 422, "create billing zone with invalid billing_profile_id"); is($err->{code}, "422", "check error code in body"); ok($err->{message} =~ /Invalid 'billing_profile_id'/, "check error message in body"); - - # try to create fee with missing billing_zone_id - $req = HTTP::Request->new('POST', $uri.'/api/billingfees/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json({ - billing_profile_id => $billing_profile_id, - #billing_zone_id => $billing_zone_id, - destination => "^1234", - direction => "out", - onpeak_init_rate => 1, - onpeak_init_interval => 60, - onpeak_follow_rate => 1, - onpeak_follow_interval => 30, - offpeak_init_rate => 0.5, - offpeak_init_interval => 60, - offpeak_follow_rate => 0.5, - offpeak_follow_interval => 30, - })); - $res = $ua->request($req); - is($res->code, 422, "create profile without billing_zone_id"); - $err = JSON::from_json($res->decoded_content); +} +# try to create fee without billing_zone_id +{ + my ($res, $err) = $test_machine->request_post(sub{delete $_[0]->{billing_zone_id};}); + is($res->code, 422, "create billing zone without billing_zone_id"); is($err->{code}, "422", "check error code in body"); ok($err->{message} =~ /Invalid 'billing_zone_id'/, "check error message in body"); - - # try to create fee with invalid billing_zone_id - $req = HTTP::Request->new('POST', $uri.'/api/billingfees/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json({ - billing_profile_id => $billing_profile_id, - billing_zone_id => 99999, - destination => "^1234", - direction => "out", - onpeak_init_rate => 1, - onpeak_init_interval => 60, - onpeak_follow_rate => 1, - onpeak_follow_interval => 30, - offpeak_init_rate => 0.5, - offpeak_init_interval => 60, - offpeak_follow_rate => 0.5, - offpeak_follow_interval => 30, - })); - $res = $ua->request($req); - is($res->code, 422, "create profile without billing_profile_id"); - $err = JSON::from_json($res->decoded_content); +} +# try to create fee with invalid billing_zone_id +{ + my ($res, $err) = $test_machine->request_post(sub{$_[0]->{billing_zone_id} = 99999;}); + is($res->code, 422, "create billing zone with invalid billing_zone_id"); is($err->{code}, "422", "check error code in body"); ok($err->{message} =~ /Invalid 'billing_zone_id'/, "check error message in body"); - - # TODO: check for wrong values in rates, prepaid etc - - # iterate over fees collection to check next/prev links and status - my $nexturi = $uri.'/api/billingfees/?page=1&rows=5'; - do { - $res = $ua->get($nexturi); - is($res->code, 200, "fetch fees 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); - - 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'"); - } - - 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:billingfees'} eq "ARRAY" || - ref $collection->{_links}->{'ngcp:billingfees'} eq "HASH"), "check if 'ngcp:billingfees' is array/hash-ref"); - - # remove any entry we find in the collection for later check - if(ref $collection->{_links}->{'ngcp:billingfees'} eq "HASH") { - ok(exists $collection->{_embedded}->{'ngcp:billingfees'}->{_links}->{'ngcp:billingprofiles'}, "check presence of ngcp:billingprofiles relation"); - ok(exists $collection->{_embedded}->{'ngcp:billingfees'}->{_links}->{'ngcp:billingzones'}, "check presence of ngcp:billingzones relation"); - delete $fees{$collection->{_links}->{'ngcp:billingfees'}->{href}}; - } else { - foreach my $c(@{ $collection->{_links}->{'ngcp:billingfees'} }) { - delete $fees{$c->{href}}; - } - foreach my $c(@{ $collection->{_embedded}->{'ngcp:billingfees'} }) { - ok(exists $c->{_links}->{'ngcp:billingprofiles'}, "check presence of ngcp:billingprofiles relation"); - ok(exists $c->{_links}->{'ngcp:billingzones'}, "check presence of ngcp:billingzones relation"); - - delete $fees{$c->{_links}->{self}->{href}}; - } - } - - } while($nexturi); - - is(scalar(keys %fees), 0, "check if all test billing fees have been found"); - - # try to create fee with implicit zone which already exists - $req = HTTP::Request->new('POST', $uri.'/api/billingfees/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json({ - billing_profile_id => $billing_profile_id, - billing_zone_zone => 'testzone', - billing_zone_detail => 'test zone from api', - destination => "^1234", - direction => "out", - onpeak_init_rate => 1, - onpeak_init_interval => 60, - onpeak_follow_rate => 1, - onpeak_follow_interval => 30, - offpeak_init_rate => 0.5, - offpeak_init_interval => 60, - offpeak_follow_rate => 0.5, - offpeak_follow_interval => 30, - })); - $res = $ua->request($req); +} +# try to create fee with implicit zone which already exists +{ + my $t = time; + my ($res, $err) = $test_machine->request_post(sub{ + delete $_[0]->{billing_zone_id}; + $_[0]->{billing_zone_zone} = 'apitestzone'; + $_[0]->{billing_zone_detail} = 'api_test zone'; + $_[0]->{destination} = "^".$t; + }); is($res->code, 201, "create profile fee with existing implicit zone"); - $req = HTTP::Request->new('GET', $uri.$res->header('Location')); - $res = $ua->request($req); + my($z_fee,$req); + ($res, $z_fee, $req) = $test_machine->request_get($test_machine->base_uri.$res->header('Location')); is($res->code, 200, "fetch profile fee with existing implicit zone"); - my $z_fee = JSON::from_json($res->decoded_content); - ok(exists $z_fee->{billing_zone_id} && $z_fee->{billing_zone_id} == $billing_zone_id, "check if implicit zone returns the correct zone id"); - - $req = HTTP::Request->new('DELETE', $uri.$z_fee->{_links}->{'self'}->{href}); - $res = $ua->request($req); - is($res->code, 204, "delete fee of existing implicit zone"); - - # try to create fee with implicit zone which doesn't exist yet + ok(exists $z_fee->{billing_zone_id} && $z_fee->{billing_zone_id} == $test_machine->DATA_ITEM->{billing_zone_id}, "check if implicit zone returns the correct zone id"); +} +# try to create fee with implicit zone which doesn't exist yet +{ my $t = time; - $req = HTTP::Request->new('POST', $uri.'/api/billingfees/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json({ - billing_profile_id => $billing_profile_id, - billing_zone_zone => 'testzone new'.$t, - billing_zone_detail => 'test zone from api new'.$t, - destination => "^1234", - direction => "out", - onpeak_init_rate => 1, - onpeak_init_interval => 60, - onpeak_follow_rate => 1, - onpeak_follow_interval => 30, - offpeak_init_rate => 0.5, - offpeak_init_interval => 60, - offpeak_follow_rate => 0.5, - offpeak_follow_interval => 30, - })); - $res = $ua->request($req); + my ($res, $err) = $test_machine->request_post(sub{ + delete $_[0]->{billing_zone_id}; + $_[0]->{billing_zone_zone} = 'apitestzone'.$t; + $_[0]->{billing_zone_detail} = 'api_test zone'.$t; + $_[0]->{destination} = "^".$t; + }); is($res->code, 201, "create profile fee with new implicit zone"); - $req = HTTP::Request->new('GET', $uri.$res->header('Location')); - $res = $ua->request($req); + my($z_fee, $req, $content); + ($res, $z_fee, $req) = $test_machine->request_get($test_machine->base_uri.$res->header('Location')); is($res->code, 200, "fetch profile fee with new implicit zone"); - $z_fee = JSON::from_json($res->decoded_content); - ok(exists $z_fee->{billing_zone_id} && $z_fee->{billing_zone_id} > $billing_zone_id, "check if implicit zone returns a new zone id"); + ok(exists $z_fee->{billing_zone_id} && $z_fee->{billing_zone_id} > $test_machine->DATA_ITEM->{billing_zone_id}, "check if implicit zone returns a new zone id"); - $req = HTTP::Request->new('DELETE', $uri.$z_fee->{_links}->{'ngcp:billingzones'}->{href}); - $res = $ua->request($req); + ($req,$res,$content) = $test_machine->request_delete($test_machine->base_uri.$z_fee->{_links}->{'ngcp:billingzones'}->{href}); is($res->code, 204, "delete new implicit zone"); - $req = HTTP::Request->new('GET', $uri.$z_fee->{_links}->{'self'}->{href}); - $res = $ua->request($req); - is($res->code, 404, "check if fee is deleted when zone is deleted"); + ($res) = $test_machine->request_get($test_machine->base_uri.$z_fee->{_links}->{'self'}->{href}); + is($res->code, 404, "check if fee is deleted when zone is deleted"); } - - -# test fee item { - $req = HTTP::Request->new('OPTIONS', $uri.'/'.$firstfee); - $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"); - } - - $req = HTTP::Request->new('GET', $uri.'/'.$firstfee); - $res = $ua->request($req); - is($res->code, 200, "fetch one fee item"); - my $fee = JSON::from_json($res->decoded_content); - ok(exists $fee->{billing_profile_id} && $fee->{billing_profile_id} == $billing_profile_id, "check existence of billing_profile_id"); - ok(exists $fee->{billing_zone_id} && $fee->{billing_zone_id} == $billing_zone_id, "check existence of billing_zone_id"); - ok(exists $fee->{direction} && $fee->{direction} =~ /^(in|out)$/ , "check existence of direction"); - ok(exists $fee->{source} && length($fee->{source}) > 0, "check existence of source"); - ok(exists $fee->{destination} && length($fee->{destination}) > 0, "check existence of destination"); - - # PUT same result again - my $old_fee = { %$fee }; - delete $fee->{_links}; - delete $fee->{_embedded}; - $req = HTTP::Request->new('PUT', $uri.'/'.$firstfee); - - # 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'); - - # check if it fails with invalid Prefer - $req->header('Prefer' => "return=invalid"); - $res = $ua->request($req); - is($res->code, 400, "check put invalid prefer"); - - $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($fee)); - $res = $ua->request($req); - is($res->code, 200, "check put successful"); - - my $new_fee = JSON::from_json($res->decoded_content); - is_deeply($old_fee, $new_fee, "check put if unmodified put returns the same"); - - # check if we have the proper links - ok(exists $new_fee->{_links}->{'ngcp:billingprofiles'}, "check put presence of ngcp:billingprofiles relation"); - ok(exists $new_fee->{_links}->{'ngcp:billingzones'}, "check put presence of ngcp:billingzones relation"); - - $req = HTTP::Request->new('PATCH', $uri.'/'.$firstfee); - $req->header('Prefer' => 'return=representation'); - $req->header('Content-Type' => 'application/json-patch+json'); - $req->content(JSON::to_json( - [ { op => 'replace', path => '/direction', value => 'in' } ] - )); - $res = $ua->request($req); - is($res->code, 200, "check patched fee item"); - my $mod_fee = JSON::from_json($res->decoded_content); + my (undef, $item_first_get) = $test_machine->check_item_get; + ok(exists $item_first_get->{billing_profile_id} && $item_first_get->{billing_profile_id} == $test_machine->DATA_ITEM->{billing_profile_id}, "check existence of billing_profile_id"); + ok(exists $item_first_get->{billing_zone_id} && $item_first_get->{billing_zone_id} == $test_machine->DATA_ITEM->{billing_zone_id}, "check existence of billing_zone_id"); + ok(exists $item_first_get->{direction} && $item_first_get->{direction} =~ /^(in|out)$/ , "check existence of direction"); + ok(exists $item_first_get->{source} && length($item_first_get->{source}) > 0, "check existence of source"); + ok(exists $item_first_get->{destination} && length($item_first_get->{destination}) > 0, "check existence of destination"); +} +{ + my($res,$item_put,$req) = $test_machine->check_get2put(); + $test_machine->check_embedded($item_put); +} +{ + my $t = time; + my($res,$mod_fee) = $test_machine->check_patch_correct( [ { op => 'replace', path => '/direction', value => 'in' } ] ); is($mod_fee->{direction}, "in", "check patched replace op"); - is($mod_fee->{_links}->{self}->{href}, $firstfee, "check patched self link"); - is($mod_fee->{_links}->{collection}->{href}, '/api/billingfees/', "check patched collection link"); - - $req->content(JSON::to_json( - [ { op => 'replace', path => '/billing_profile_id', value => undef } ] - )); - $res = $ua->request($req); +} +{ + my($res) = $test_machine->request_patch( [ { op => 'replace', path => '/billing_profile_id', value => undef } ] ); is($res->code, 422, "check patched undef billing_profile_id"); - - $req->content(JSON::to_json( - [ { op => 'replace', path => '/billing_profile_id', value => 99999 } ] - )); - $res = $ua->request($req); +} +{ + my($res) = $test_machine->request_patch( [ { op => 'replace', path => '/billing_profile_id', value => 99999 } ] ); is($res->code, 422, "check patched invalid billing_profile_id"); - - $req->content(JSON::to_json( - [ { op => 'replace', path => '/billing_zone_id', value => undef } ] - )); - $res = $ua->request($req); +} +{ + my($res) = $test_machine->request_patch( [ { op => 'replace', path => '/billing_zone_id', value => undef } ] ); is($res->code, 422, "check patched undef billing_zone_id"); - - $req->content(JSON::to_json( - [ { op => 'replace', path => '/billing_zone_id', value => 99999 } ] - )); - $res = $ua->request($req); +} +{ + my($res) = $test_machine->request_patch( [ { op => 'replace', path => '/billing_zone_id', value => 99999 } ] ); is($res->code, 422, "check patched invalid billing_zone_id"); } -{ - my $ff; - foreach my $f(@allfees) { - $req = HTTP::Request->new('DELETE', $uri.'/'.$f); - $res = $ua->request($req); - is($res->code, 204, "check delete of fee"); - $ff = $f unless $ff; - } - $req = HTTP::Request->new('GET', $uri.'/'.$ff); - $res = $ua->request($req); - is($res->code, 404, "check if deleted fee is really gone"); +$test_machine->clear_test_data_all(); - $req = HTTP::Request->new('DELETE', $uri.'/api/billingzones/'.$billing_zone_id); - $res = $ua->request($req); +{ + my $uri = $test_machine->base_uri.'/api/billingzones/'.$test_machine->DATA_ITEM->{billing_zone_id}; + my($req,$res,$content) = $test_machine->request_delete($uri); is($res->code, 204, "check delete of zone"); - - $req = HTTP::Request->new('GET', $uri.'/api/billingzones/'.$billing_zone_id); - $res = $ua->request($req); + ($res, $content, $req) = $test_machine->request_get($uri); is($res->code, 404, "check if deleted zone is really gone"); } -done_testing; +#call destructors +$fake_data = undef; +$test_machine = undef; +done_testing; # vim: set tabstop=4 expandtab: diff --git a/t/api-billingzones.t b/t/api-billingzones.t new file mode 100644 index 0000000000..a7b3efab85 --- /dev/null +++ b/t/api-billingzones.t @@ -0,0 +1,46 @@ +#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 $test_machine = Test::Collection->new( + name => 'billingzones', +); +my $fake_data = Test::FakeData->new; + +$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)}; + +$fake_data->set_data_from_script({ + 'billingzones' => { + data => { + billing_profile_id => sub { return shift->get_id('billingprofiles', @_); }, + zone => "apitestzone", + detail => "api_test zone", + }, + 'query' => ['zone'], + }, +}); + +$test_machine->DATA_ITEM_STORE($fake_data->process('billingzones')); +$test_machine->form_data_item( ); + +# create 3 new billing zones from DATA_ITEM +$test_machine->check_create_correct( 3, sub{ $_[0]->{zone} .= $_[1]->{i} ; } ); +$test_machine->check_get2put( ); +$test_machine->check_bundle(); +$test_machine->clear_test_data_all(); +done_testing; + +# vim: set tabstop=4 expandtab: diff --git a/t/api-pbxdevicemodels.t b/t/api-pbxdevicemodels.t index b9b737e58f..a42b1b492e 100644 --- a/t/api-pbxdevicemodels.t +++ b/t/api-pbxdevicemodels.t @@ -11,19 +11,86 @@ use HTTP::Request::Common; use JSON; use Test::More; use Data::Dumper; - +use File::Basename; #init test_machine my $fake_data = Test::FakeData->new; +$fake_data->set_data_from_script({ + '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->get_id('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, + }, +}); my $test_machine = Test::Collection->new( name => 'pbxdevicemodels', embedded => [qw/pbxdevicefirmwares/] ); +$test_machine->DATA_ITEM_STORE($fake_data->process('pbxdevicemodels')); @{$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={}; diff --git a/t/api-pbxdevices.t b/t/api-pbxdevices.t index 7171f1685b..0b7c3a4726 100644 --- a/t/api-pbxdevices.t +++ b/t/api-pbxdevices.t @@ -21,6 +21,31 @@ my $test_machine = Test::Collection->new( $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; +$fake_data->set_data_from_script({ + 'pbxdevices' => { + 'data' => { + profile_id => sub { return shift->get_id('pbxdeviceprofiles',@_); }, + customer_id => sub { return shift->get_id('customers',@_); }, + identifier => 'aaaabbbbcccc', + station_name => 'api_test_vun', + lines=>[{ + linerange => 'Phone Ports api_test', + type => 'private', + key_num => '0', + subscriber_id => sub { return shift->get_id('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->get_id('subscribers',@_); }, + extension_unit => '2', + }], + }, + 'query' => ['station_name'], + }, +}); #for item creation test purposes /post request data/ $test_machine->DATA_ITEM_STORE($fake_data->process('pbxdevices')); diff --git a/t/api-vouchers.t b/t/api-vouchers.t index 85092c2be9..4b5e467670 100644 --- a/t/api-vouchers.t +++ b/t/api-vouchers.t @@ -1,199 +1,73 @@ +#use Sipwise::Base; +use strict; + +#use Moose; use Sipwise::Base; -use Net::Domain qw(hostfqdn); -use LWP::UserAgent; -use JSON qw(); +use Test::Collection; +use Test::FakeData; use Test::More; -use Storable qw(); -use Data::Printer; - -use JSON::PP; -use LWP::Debug; - -BEGIN { - unshift(@INC,'../lib'); -} - -my $json = JSON::PP->new(); -$json->allow_blessed(1); -$json->convert_blessed(1); - -my $is_local_env = $ENV{LOCAL_TEST} // 0; -my $mysql_sqlstrict = 1; #https://bugtracker.sipwise.com/view.php?id=12565 - -use Config::General; -my $catalyst_config; -if ($is_local_env) { - my $panel_config; - for my $path(qw#../ngcp_panel.conf ngcp_panel.conf#) { - if(-f $path) { - $panel_config = $path; - last; - } - } - $panel_config //= '../ngcp_panel.conf'; - $catalyst_config = Config::General->new($panel_config); -} else { - #taken 1:1 from /lib/NGCP/Panel.pm - my $panel_config; - for my $path(qw#/etc/ngcp-panel/ngcp_panel.conf etc/ngcp_panel.conf ngcp_panel.conf#) { - if(-f $path) { - $panel_config = $path; - last; - } - } - $panel_config //= 'ngcp_panel.conf'; - $catalyst_config = Config::General->new($panel_config); -} -my %config = $catalyst_config->getall(); - -my $uri = $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443'); - -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, $req, $res); -$ua = LWP::UserAgent->new; - -if ($is_local_env) { - $ua->ssl_opts( - verify_hostname => 0, - ); - my $realm = $uri; $realm =~ s/^https?:\/\///; - $ua->credentials($realm, "api_admin_http", 'administrator', 'administrator'); - #$ua->timeout(500); #useless, need to change the nginx timeout -} else { - $ua->ssl_opts( - SSL_cert_file => $valid_ssl_client_cert, - SSL_key_file => $valid_ssl_client_key, - SSL_ca_file => $ssl_ca_cert, - ); -} - -my $t = time; -my $default_reseller_id = 1; - -test_voucher(); -done_testing(); - - -sub test_voucher { - my $code = 'testcode'.$t; - my $voucher = { - amount => 100, - code => $code, - customer_id => undef, - reseller_id => $default_reseller_id, - valid_until => '2037-01-01 12:00:00', - }; - $req = HTTP::Request->new('POST', $uri.'/api/vouchers/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json($voucher)); - $res = $ua->request($req); - is($res->code, 201, _get_request_test_message("POST test voucher")); - my $voucher_uri = $uri.'/'.$res->header('Location'); - $req = HTTP::Request->new('GET', $voucher_uri); - $res = $ua->request($req); - is($res->code, 200, _get_request_test_message("fetch POSTed test voucher")); - my $post_voucher = JSON::from_json($res->decoded_content); - delete $post_voucher->{_links}; - my $voucher_id = delete $post_voucher->{id}; - is_deeply($voucher, $post_voucher, "check POSTed voucher against fetched"); - $post_voucher->{id} = $voucher_id; - - $req = HTTP::Request->new('PUT', $voucher_uri); - $req->header('Content-Type' => 'application/json'); - $req->header('Prefer' => 'return=representation'); - $req->content(JSON::to_json($post_voucher)); - $res = $ua->request($req); - is($res->code, 200, _get_request_test_message("PUT test voucher")); - $req = HTTP::Request->new('GET', $voucher_uri); - $res = $ua->request($req); - is($res->code, 200, _get_request_test_message("fetch PUT test voucher")); - my $put_voucher = JSON::from_json($res->decoded_content); - delete $put_voucher->{_links}; - $voucher_id = delete $put_voucher->{id}; - is_deeply($voucher, $put_voucher, "check PUTed voucher against POSTed voucher"); - - $req = HTTP::Request->new('PATCH', $voucher_uri); - $req->header('Content-Type' => 'application/json-patch+json'); - $req->header('Prefer' => 'return=representation'); - $req->content(JSON::to_json([{op=>"replace", path=>"/code", value=>$put_voucher->{code}}])); - $res = $ua->request($req); - is($res->code, 200, _get_request_test_message("PATCH test voucher")); - $req = HTTP::Request->new('GET', $voucher_uri); - $res = $ua->request($req); - is($res->code, 200, _get_request_test_message("fetch PATCH test voucher")); - my $patch_voucher = JSON::from_json($res->decoded_content); - delete $patch_voucher->{_links}; - $voucher_id = delete $patch_voucher->{id}; - is_deeply($voucher, $patch_voucher, "check PATCHed voucher against POSTed voucher"); - - - $req = HTTP::Request->new('POST', $uri.'/api/vouchers/'); - $req->header('Content-Type' => 'application/json'); - $req->content(JSON::to_json($put_voucher)); - $res = $ua->request($req); - is($res->code, 422, _get_request_test_message("POST same voucher code again")); - - $put_voucher->{id} = $voucher_id; - +use Data::Dumper; + + +#init test_machine +my $test_machine = Test::Collection->new( + name => 'vouchers', +); +my $fake_data = Test::FakeData->new; + +$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)}; + +$fake_data->set_data_from_script({ + 'vouchers' => { + data => { + amount => 100, + code => 'apitestcode', + customer_id => undef, + reseller_id => sub { return shift->get_id('resellers', @_); },, + valid_until => '2037-01-01 12:00:00', + }, + 'query' => ['code'], + }, +}); + +$test_machine->DATA_ITEM_STORE($fake_data->process('vouchers')); +$test_machine->form_data_item( ); + +# create 3 new vouchers from DATA_ITEM +$test_machine->check_create_correct( 3, sub{ $_[0]->{code} .= $_[1]->{i} ; } ); +$test_machine->check_get2put(); +$test_machine->check_bundle(); + +my $voucher = $test_machine->{DATA_ITEM}; +print Dumper $voucher; +my $voucher_uri; + +{ + #todo: move request processing results to separate package inside collection, to don't return these chains + my($res_post,$result_item_post,$req_post,$content_post_in,$location_post,$content_get) = $test_machine->check_post2get(); + my($res_put,$result_item_put,$req_put,$item_put_data,$get_res,$result_item_get,$get_req) = $test_machine->check_put2get(undef, undef, $location_post); + + $voucher_uri = $location_post; + $voucher = $result_item_get; -# $req = HTTP::Request->new('PATCH', $billingzone_uri); -# $req->header('Content-Type' => 'application/json-patch+json'); -# $req->header('Prefer' => 'return=representation'); -# $req->content(JSON::to_json( -# [ { op => 'replace', path => '/zone', value => 'AT' } ] -# )); -# $res = $ua->request($req); -# is($res->code, 200, _get_request_test_message("PATCH test billingzone")); -# $req = HTTP::Request->new('GET', $billingzone_uri); -# $res = $ua->request($req); -# is($res->code, 200, _get_request_test_message("fetch PATCHed test billingzone")); -# $billingzone = JSON::from_json($res->decoded_content); - - - # mysql has an issue with datetime overruns,check for max date - $req = HTTP::Request->new('PATCH', $voucher_uri); - $req->header('Content-Type' => 'application/json-patch+json'); - $req->header('Prefer' => 'return=representation'); - $req->content(JSON::to_json( - [ { op => 'replace', path => '/valid_until', value => '2099-01-01 00:00:00' } ] - )); - $res = $ua->request($req); - is($res->code, 422, _get_request_test_message("PATCH too far valid_until in voucher")); - - $req = HTTP::Request->new('DELETE', $voucher_uri); - $res = $ua->request($req); - is($res->code, 204, _get_request_test_message("delete POSTed test voucher")); - $req = HTTP::Request->new('GET', $voucher_uri); - $res = $ua->request($req); - is($res->code, 404, _get_request_test_message("fetch DELETEd test voucher")); + my($res,$result_item,$req) = $test_machine->request_post(undef,$voucher); + $test_machine->http_code_msg(422, "POST same voucher code again", $res, $result_item); } - -sub _to_json { - return $json->encode(shift); +{ + my($res,$content) = $test_machine->request_patch( [ { op => 'replace', path => '/valid_until', value => '2099-01-01 00:00:00' } ] ); + $test_machine->http_code_msg(422, "check patched invalid billing_zone_id",$res,,$content); } -sub _from_json { - return $json->decode(shift); -} +$test_machine->clear_test_data_all(); -sub _get_request_test_message { - my ($message) = @_; - my $code = $res->code; - if ($code == 200 || $code == 201 || $code == 204) { - return $message; - } else { - my $error_content = _from_json($res->content); - if (defined $error_content && defined $error_content->{message}) { - return $message . ' (' . $res->message . ': ' . $error_content->{message} . ')'; - } else { - return $message . ' (' . $res->message . ')'; - } - } +{ + my $uri = $test_machine->get_uri($voucher->{id}); + my($req,$res,$content) = $test_machine->request_delete($uri); + $test_machine->http_code_msg(204, "check delete of voucher", $res, $content); + ($res, $content, $req) = $test_machine->request_get($uri); + is($res->code, 404, "check if deleted voucher is really gone"); } +done_testing; # vim: set tabstop=4 expandtab: diff --git a/t/lib/Test/Collection.pm b/t/lib/Test/Collection.pm index 289391efe8..24b873df99 100644 --- a/t/lib/Test/Collection.pm +++ b/t/lib/Test/Collection.pm @@ -16,15 +16,32 @@ use Clone qw/clone/; use Data::Dumper; +has 'local_test' => ( + is => 'rw', + isa => 'Str', + default => $ENV{LOCAL_TEST} // '', +); +has 'catalyst_config' => ( + is => 'rw', + isa => 'HashRef', +); +has 'panel_config' => ( + is => 'rw', + isa => 'HashRef', +); has 'ua' => ( is => 'rw', isa => 'LWP::UserAgent', - builder => '_init_ua', + lazy => 1, + builder => 'init_ua', ); has 'base_uri' => ( is => 'ro', isa => 'Str', - default => $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443'), + default => sub { + $_[0]->{local_test} + ? ( length($_[0]->{local_test})>1 ? $_[0]->{local_test} : 'https://127.0.0.1:4443' ) + : $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443')}, ); has 'name' => ( is => 'rw', @@ -82,6 +99,7 @@ has 'DATA_CREATED' => ( has 'KEEP_CREATED' =>( is => 'rw', isa => 'Bool', + default => 1, ); has 'URI_CUSTOM' =>( is => 'rw', @@ -130,24 +148,56 @@ sub get_cloned{ } return $state; } -sub _init_ua { +sub get_catalyst_config{ + my $self = shift; + my $catalyst_config; + my $panel_config; + if ($self->{local_test}) { + for my $path(qw#../ngcp_panel.conf ngcp_panel.conf#) { + if(-f $path) { + $panel_config = $path; + last; + } + } + $panel_config //= '../ngcp_panel.conf'; + $catalyst_config = Config::General->new($panel_config); + } else { + #taken 1:1 from /lib/NGCP/Panel.pm + for my $path(qw#/etc/ngcp-panel/ngcp_panel.conf etc/ngcp_panel.conf ngcp_panel.conf#) { + if(-f $path) { + $panel_config = $path; + last; + } + } + $panel_config //= 'ngcp_panel.conf'; + $catalyst_config = Config::General->new($panel_config); + } + my %config = $catalyst_config->getall(); + $self->{catalyst_config} = \%config; + $self->{panel_config} = $panel_config; + return $self->{catalyst_config}; +} +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, - #); + if($self->local_test){ + $ua->credentials( $self->base_uri, '', 'administrator', 'administrator' ); + $ua->ssl_opts( + verify_hostname => 0, + SSL_verify_mode => 0x00, + ); + }else{ + 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"; + $ua->ssl_opts( + SSL_cert_file => $valid_ssl_client_cert, + SSL_key_file => $valid_ssl_client_key, + SSL_ca_file => $ssl_ca_cert, + ); + } return $ua; } sub clear_data_created{ @@ -186,7 +236,12 @@ sub get_uri_collection{ } sub get_uri_get{ my($self,$query_string) = @_; - return $self->base_uri."/api/".$self->name.'/?'.$query_string; + return $self->base_uri."/api/".$self->name.($query_string ? '/?' : '/' ).$query_string; +} +sub get_uri{ + my($self,$add) = @_; + $add //= ''; + return $self->base_uri."/api/".$self->name.'/'.$add; } sub get_uri_firstitem{ my($self) = @_; @@ -228,9 +283,16 @@ sub encode_content{ sub request{ my($self,$req) = @_; #print $req->as_string; - $self->ua->request($req); + return $self->ua->request($req); } +sub request_process{ + my($self,$req) = @_; + #print $req->as_string; + my $res = $self->ua->request($req); + my $rescontent = $res->decoded_content ? JSON::from_json($res->decoded_content) : ''; + return ($res,$rescontent,$req); +} sub get_request_put{ my($self,$content,$uri) = @_; $uri ||= $self->get_uri_current; @@ -256,8 +318,6 @@ sub request_put{ $uri ||= $self->get_uri_current; my $req = $self->get_request_put( $content, $uri ); my $res = $self->request($req); - #print Dumper $res; - my $rescontent = $res->decoded_content ? JSON::from_json($res->decoded_content) : ''; return wantarray ? ($res,$rescontent,$req) : $res; } @@ -273,11 +333,15 @@ sub request_patch{ #print Dumper [$res,$rescontent,$req]; return wantarray ? ($res,$rescontent,$req) : $res; } - -sub request_post{ +sub process_data{ 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); + return $data; +} +sub request_post{ + my($self, $data_cb, $data_in, $data_cb_data) = @_; + my $data = $self->process_data($data_cb, $data_in, $data_cb_data); my $content = { $data->{json} ? ( json => JSON::to_json(delete $data->{json}) ) : (), %$data, @@ -289,7 +353,7 @@ sub request_post{ Content => $content; my $res = $self->request($req); my $rescontent = $res->decoded_content ? JSON::from_json($res->decoded_content) : ''; - return wantarray ? ($res,$rescontent,$req) : $res; + return wantarray ? ($res,$rescontent,$req,$content) : $res; }; sub request_options{ @@ -311,6 +375,14 @@ sub request_delete{ my $content = $res->decoded_content ? JSON::from_json($res->decoded_content) : ''; return($req,$res,$content); } +sub request_get{ + my($self,$uri) = @_; + $uri ||= $self->get_uri_current; + my $req = HTTP::Request->new('GET', $uri); + my $res = $self->request($req); + my $content = $res->decoded_content ? JSON::from_json($res->decoded_content) : ''; + return wantarray ? ($res, $content, $req) : $res; +} ############## end of test machine ############## start of test collection @@ -334,8 +406,8 @@ sub check_options_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 $opts = $res->decoded_content ? JSON::from_json($res->decoded_content) : undef; + $self->http_code_msg(200, "check $area options request", $res,$opts); 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}} ) { @@ -349,14 +421,14 @@ sub check_methods{ } } sub check_create_correct{ - my($self, $number, $uniquizer_cb, $keep_created) = @_; - if(!$keep_created && !$self->KEEP_CREATED){ + my($self, $number, $uniquizer_cb) = @_; + if(!$self->KEEP_CREATED){ $self->clear_data_created; } $self->DATA_CREATED->{ALL} //= {}; for(my $i = 1; $i <= $number; ++$i) { my ($res, $content, $req) = $self->request_post( $uniquizer_cb , undef, { i => $i} ); - is($res->code, 201, "create test item '".$self->name."' $i"); + $self->http_code_msg(201, "create test item '".$self->name."' $i",$res,$content); my $location = $res->header('Location'); if($location){ $self->DATA_CREATED->{ALL}->{$location} = { num => $i, content => $content, res => $res, req => $req, location => $location}; @@ -369,7 +441,7 @@ sub clear_test_data_all{ my @uris = $uri ? (('ARRAY' eq ref $uri) ? @$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"); + $self->http_code_msg(204, "check delete item $del_uri",$res,$content); } } sub clear_test_data_dependent{ @@ -377,15 +449,21 @@ sub clear_test_data_dependent{ my($req,$res,$content) = $self->request_delete($self->base_uri.$uri); return ('204' eq $res->code); } +sub check_embedded { + my($self, $embedded, $check_embedded_cb) = @_; + 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"); + } +} + 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 ($res,$list_collection) = $self->check_item_get($nexturi); my $selfuri = $self->base_uri . $list_collection->{_links}->{self}->{href}; is($selfuri, $nexturi, "check _links.self.href of collection"); my $colluri = URI->new($selfuri); @@ -418,18 +496,11 @@ sub check_list_collection{ 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}); + $self->check_embedded($list_collection->{_embedded}->{$hal_name}, $check_embedded_cb); push @href, $list_collection->{_links}->{$hal_name}->{href}; } else { foreach my $item_c(@{ $list_collection->{_links}->{$hal_name} }) { @@ -437,7 +508,7 @@ sub check_list_collection{ } 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); + $self->check_embedded($item_c, $check_embedded_cb); push @href, $item_c->{_links}->{self}->{href}; } } @@ -461,8 +532,8 @@ sub check_item_get{ $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 $content = $res->decoded_content ? JSON::from_json($res->decoded_content) : ''; + $self->http_code_msg(200, "fetch uri: $uri", $res, $content); return wantarray ? ($res, $content, $req) : $res; } @@ -473,8 +544,8 @@ sub check_put_content_type_empty{ $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"); + my($res,$content) = $self->request_process($req); + $self->http_code_msg(415, "check put missing content type", $res, $content); } sub check_put_content_type_wrong{ my($self) = @_; @@ -482,8 +553,8 @@ sub check_put_content_type_wrong{ 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"); + my($res,$content) = $self->request_process($req); + $self->http_code_msg(415, "check put invalid content type", $res, $content); } sub check_put_prefer_wrong{ my($self) = @_; @@ -491,8 +562,8 @@ sub check_put_prefer_wrong{ 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"); + my($res,$content) = $self->request_process($req); + $self->http_code_msg(400, "check put invalid prefer", $res, $content); } sub check_put_body_empty{ @@ -501,24 +572,62 @@ sub check_put_body_empty{ 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"); + my($res,$content) = $self->request_process($req); + $self->http_code_msg(400, "check put no body", $res, $content); } + sub check_get2put{ - my($self,$put_data_cb, $uri) = @_; + 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}; + my ($res_get, $result_item_get, $req_get) = $self->check_item_get($uri); + my $item_put_data = clone($result_item_get); + delete $item_put_data->{_links}; + delete $item_put_data->{_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"); + (defined $put_data_cb) and $put_data_cb->($item_put_data); + my ($res_put,$result_item_put,$req_put) = $self->request_put( $item_put_data, $uri ); + $self->http_code_msg(200, "check_get2put: check put successful",$res_put, $result_item_put); + is_deeply($result_item_get, $result_item_put, "check_get2put: check put if unmodified put returns the same"); + return ($res_put,$result_item_put,$req_put,$item_put_data); +} + +sub check_put2get{ + my($self, $put_data_in, $put_data_cb, $uri) = @_; + + my $item_put_data = $self->process_data($put_data_cb, $put_data_in); + $item_put_data = JSON::to_json($item_put_data); + my ($res_put,$result_item_put,$req_put) = $self->request_put( $item_put_data, $uri ); + $self->http_code_msg(200, "check_put2get: check put successful",$res_put, $result_item_put); + + my ($res_get, $result_item_get, $req_get) = $self->check_item_get($uri); + delete $result_item_get->{_links}; + delete $result_item_get->{_embedded}; + my $item_id = delete $result_item_get->{id}; + $item_put_data = JSON::from_json($item_put_data); + is_deeply($item_put_data, $result_item_get, "check_put2get: check PUTed item against POSTed item"); + $result_item_get->{id} = $item_id; + return ($res_put,$result_item_put,$req_put,$item_put_data,$res_get, $result_item_get, $req_get); } + +sub check_post2get{ + my($self, $post_data_in, $post_data_cb) = @_; + + my ($res_post, $result_item_post, $req_post, $item_post_data ) = $self->request_post( $post_data_cb, $post_data_in ); + $self->http_code_msg(201, "check_post2get: POST item '".$self->name."' for check_post2get", $res_post, $result_item_post); + my $location_post = $self->base_uri.($res_post->header('Location') // ''); + + my ($res_get, $result_item_get, $req_get) = $self->request_get( $location_post ); + $self->http_code_msg(200, "check_post2get: fetch POSTed test '".$self->name."'", $res_get, $result_item_get); + delete $result_item_get->{_links}; + my $item_id = delete $result_item_get->{id}; + $item_post_data = JSON::from_json($item_post_data); + is_deeply($item_post_data, $result_item_get, "check_post2get: check POSTed '".$self->name."' against fetched"); + $result_item_get->{id} = $item_id; + return ($res_post,$result_item_post,$req_post,$item_post_data, $location_post, $result_item_get); +} + sub check_put_bundle{ my($self) = @_; $self->check_put_content_type_empty; @@ -529,7 +638,7 @@ sub check_put_bundle{ sub check_patch_correct{ my($self,$content) = @_; my ($res,$rescontent,$req) = $self->request_patch( $content ); - is($res->code, 200, "check patched item"); + $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}->{collection}->{href}, '/api/'.$self->name.'/', "check patched collection link"); return ($res,$rescontent,$req); @@ -540,15 +649,15 @@ sub check_patch_prefer_wrong{ 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"); + my ($res,$content) = $self->request_process($req); + $self->http_code_msg(415, "check patch invalid prefer", $res, $content); } 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"); + my ($res,$content) = $self->request_process($req); + $self->http_code_msg(415, "check patch missing media type", $res, $content); } sub check_patch_content_type_wrong{ @@ -556,14 +665,14 @@ sub check_patch_content_type_wrong{ 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"); + my($res,$content) = $self->request_process($req); + $self->http_code_msg(415, "check patch invalid media type", $res, $content); } sub check_patch_body_empty{ my($self) = @_; my ($res,$content,$req) = $self->request_patch; - is($res->code, 400, "check patch missing body"); + $self->http_code_msg(400, "check patch missing body", $res, $content); like($content->{message}, qr/is missing a message body/, "check patch missing body response"); } @@ -572,7 +681,7 @@ sub check_patch_body_notarray{ my ($res,$content,$req) = $self->request_patch( { foo => 'bar' }, ); - is($res->code, 400, "check patch no array body"); + $self->http_code_msg(400, "check patch no array body", $res, $content); like($content->{message}, qr/must be an array/, "check patch missing body response"); } @@ -581,7 +690,7 @@ sub check_patch_op_missed{ my ($res,$content,$req) = $self->request_patch( [{ foo => 'bar' }], ); - is($res->code, 400, "check patch no op in body"); + $self->http_code_msg(400, "check patch no op in body", $res, $content); like($content->{message}, qr/must have an 'op' field/, "check patch no op in body response"); } @@ -590,7 +699,7 @@ sub check_patch_op_wrong{ my ($res,$content,$req) = $self->request_patch( [{ op => 'bar' }], ); - is($res->code, 400, "check patch invalid op in body"); + $self->http_code_msg(400, "check patch invalid op in body", $res, $content); like($content->{message}, qr/Invalid PATCH op /, "check patch no op in body response"); } @@ -599,7 +708,7 @@ sub check_patch_opreplace_paramsmiss{ my ($res,$content,$req) = $self->request_patch( [{ op => 'replace' }], ); - is($res->code, 400, "check patch missing fields for op"); + $self->http_code_msg(400, "check patch missing fields for op", $res, $content); like($content->{message}, qr/Missing PATCH keys /, "check patch missing fields for op response"); } @@ -608,7 +717,7 @@ sub check_patch_opreplace_paramsextra{ 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"); + $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"); } @@ -640,4 +749,19 @@ sub hash2params{ my($self,$hash) = @_; return join '&', map {$_.'='.uri_escape($hash->{$_})} keys %{ $hash }; } +sub http_code_msg{ + my($self,$code,$message,$res,$err) = @_; + $err //= $res->decoded_content ? JSON::from_json($res->decoded_content) : undef; + my $message_res; + if ( ($res->code < 300) || ( $code >= 300 ) ) { + $message_res = $message; + } else { + if (defined $err && defined $err->{message}) { + $message_res = $message . ' (' . $res->message . ': ' . $err->{message} . ')'; + } else { + $message_res = $message . ' (' . $res->message . ')'; + } + } + $code and is($res->code, $code, $message_res); +} 1; \ No newline at end of file diff --git a/t/lib/Test/FakeData.pm b/t/lib/Test/FakeData.pm index 09b352663e..837758227d 100644 --- a/t/lib/Test/FakeData.pm +++ b/t/lib/Test/FakeData.pm @@ -10,11 +10,12 @@ use File::Basename; use Data::Dumper; use Test::DeepHashUtils qw(reach nest deepvalue); use Clone qw/clone/; +use File::Slurp qw/read_file/; has 'test_machine' =>( is => 'rw', isa => 'Test::Collection', - default => sub { Test::Collection->new () }, + default => sub { Test::Collection->new ( 'KEEP_CREATED' => 0 ) }, ); has 'created' => ( is => 'rw', @@ -26,6 +27,16 @@ has 'loaded' => ( isa => 'HashRef', default => sub { {} }, ); +has 'searched' => ( + is => 'rw', + isa => 'HashRef', + default => sub { {} }, +); +has 'undeletable' => ( + is => 'rw', + isa => 'HashRef', + default => sub { {} }, +); has 'data_default' => ( is => 'rw', isa => 'HashRef', @@ -167,19 +178,19 @@ sub build_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',@_); }, + reseller_id => sub { return shift->get_id('resellers',@_); }, }, 'query' => ['email'], 'delete_potentially_dependent' => 1, }, 'contracts' => { 'data' => { - contact_id => sub { return shift->create('systemcontacts',@_); }, + contact_id => sub { return shift->get_id('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',@_); }, + billing_profile_id => sub { return shift->get_id('billingprofiles',@_); }, }, 'default' => 'contracts', 'query' => ['external_id'], @@ -187,7 +198,7 @@ sub build_data{ }, 'resellers' => { 'data' => { - contract_id => sub { return shift->create('contracts', @_ ); }, + contract_id => sub { return shift->get_id('contracts', @_ ); }, name => 'api_test test reseller', status => 'active', }, @@ -198,8 +209,8 @@ sub build_data{ 'customers' => { 'data' => { status => 'active', - contact_id => sub { return shift->create('customercontacts',@_); }, - billing_profile_id => sub { return shift->create('billingprofiles',@_); }, + contact_id => sub { return shift->get_id('customercontacts',@_); }, + billing_profile_id => sub { return shift->get_id('billingprofiles',@_); }, max_subscribers => undef, external_id => 'api_test customer', type => 'pbxaccount',#sipaccount @@ -211,7 +222,7 @@ sub build_data{ 'data' => { name => 'api_test test profile', handle => 'api_test_testprofile', - reseller_id => sub { return shift->create('resellers',@_); }, + reseller_id => sub { return shift->get_id('resellers',@_); }, }, 'default' => 'billing_profiles', 'query' => ['handle'], @@ -220,14 +231,14 @@ sub build_data{ 'subscribers' => { 'data' => { administrative => 0, - customer_id => sub { return shift->create('customers',@_); }, + customer_id => sub { return shift->get_id('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 => sub { return shift->get_id('domains',@_); },, #domain_id => email => undef, external_id => undef, @@ -236,7 +247,7 @@ sub build_data{ pbx_extension => '111', pbx_group_ids => [], pbx_groupmember_ids => [], - profile_id => sub { return shift->create('subscriberprofiles',@_); }, + profile_id => sub { return shift->get_id('subscriberprofiles',@_); }, status => 'active', pbx_hunt_policy => 'parallel', pbx_hunt_timeout => '15', @@ -246,14 +257,14 @@ sub build_data{ 'domains' => { 'data' => { domain => 'api_test_domain.api_test_domain', - reseller_id => sub { return shift->create('resellers',@_); }, + reseller_id => sub { return shift->get_id('resellers',@_); }, }, 'query' => ['domain'], }, 'subscriberprofilesets' => { 'data' => { name => 'api_test_subscriberprofileset', - reseller_id => sub { return shift->create('resellers',@_); }, + reseller_id => sub { return shift->get_id('resellers',@_); }, description => 'api_test_subscriberprofileset', }, 'query' => ['name'], @@ -261,89 +272,23 @@ sub build_data{ 'subscriberprofiles' => { 'data' => { name => 'api_test subscriberprofile', - profile_set_id => sub { return shift->create('subscriberprofilesets',@_); }, + profile_set_id => sub { return shift->get_id('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',@_); }, + device_id => sub { return shift->get_id('pbxdevicemodels',@_); }, version => 'api_test 1.1', content_type => 'text/plain', }, 'query' => ['version'], 'create_special'=> sub { - my ($self,$name) = @_; + my ($self,$collection_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->content_type->{POST} = $self->data->{$collection_name}->{data}->{content_type}; + $self->test_machine->QUERY_PARAMS($self->test_machine->hash2params($self->data->{$collection_name}->{data})); $self->test_machine->check_create_correct(1, sub {return 'test_api_empty_config';} ); $self->test_machine->set(%$prev_params); }, @@ -351,50 +296,33 @@ sub build_data{ }, 'pbxdeviceprofiles' => { 'data' => { - config_id => sub { return shift->create('pbxdeviceconfigs',@_); }, + config_id => sub { return shift->get_id('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 ){ + #$self->process_data($data); + return $data; +} +sub process_data{ + my($self,$data,$collections_slice) = @_; + $collections_slice //= [keys %$data]; + foreach my $collection_name( @$collections_slice ){ 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/]); + $self->clear_db($data); #incorrect place, leave it for the next timeframe to work on it $self->load_db($data); - return $data; } sub load_db{ - my($self,$data) = @_; + my($self,$data,$collections_slice) = @_; $data //= $self->data; - foreach my $collection_name( keys %$data ){ + $collections_slice //= [keys %$data]; + foreach my $collection_name( @$collections_slice ){ #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); @@ -412,30 +340,31 @@ sub load_db{ } return; } + sub clear_db{ - my($self,$data,$order_array) = @_; - $order_array //= []; + my($self,$data,$order_array,$collections_slice) = @_; + $order_array //= [qw/contracts systemcontacts customercontacts/]; my $order_hash = {}; - @$order_hash{(keys %$data)} = (0) x (keys %$data); + $collections_slice //= [keys %$data]; + @$order_hash{(keys %$data)} = (0) x @$collections_slice; @$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})){ + foreach my $collection_name (sort {$order_hash->{$a} <=> $order_hash->{$b}} @$collections_slice ){ + 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; + $values = ('HASH' eq ref $values) ? [$values] : $values; my @locations = map {$_->{href}} @$values; if($data->{$collection_name}->{no_delete_available}){ - push @undeletable_items, @locations; + @{$self->undeletable->{@locations}} = ($collection_name) x @locations; }else{ if($data->{$collection_name}->{delete_potentially_dependent}){ + #no checking of deletion success will be done for items which may depend on not deletable ones foreach( @locations ){ if(!$self->test_machine->clear_test_data_dependent($_)){ - push @undeletable_items, $_; + $self->undeletable->{$_} = $collection_name; } } }else{ @@ -444,12 +373,9 @@ sub clear_db{ } } } - 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; @@ -457,82 +383,159 @@ sub search_item{ if(!$item->{query}){ return; } + if($self->searched->{$collection_name}){ + return @{$self->searched->{$collection_name}}; + } $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); + my $search_value = deepvalue($item->{data},@deep_keys); + if('CODE' eq ref $search_value){ + $search_value = $search_value->($self); + } + $field_name.'='.$search_value; } @{$item->{query}} ); my($res, $content, $req) = $self->test_machine->check_item_get($self->test_machine->get_uri_get($query_string)); + #time for memoize? + $self->searched->{$collection_name} = [$res, $content, $req]; return ($res, $content, $req); } -sub create{ - my($self, $name, $parents_in, $field_path, $params) = @_; + +sub set_data_from_script{ + my($self, $data_in) = @_; + #$self->data->{$collection_name}->{data} = $data; + while (my($collection_name,$collection_data) = each %$data_in ){ + $self->data->{$collection_name} //= {}; + $self->data->{$collection_name} = { + %{$self->data->{$collection_name}}, + %$collection_data, + }; + } + #dirty hack, part 2 + if(grep {/^load_data_only$/} @ARGV){ + no strict "vars"; + $data_out = $data_in; + die; + } +} + +sub load_data_from_script{ + my($self, $collection_name) = @_; + my $collection_file = "./api-$collection_name.t"; + my $found = 0; + if(-e $collection_file){ + #dirty hack, part 1. To think about Safe + local @ARGV = qw/load_data_only/; + our $data_out; + do $collection_file; + if($data_out && $data_out->{$collection_name}){ + $self->data->{$collection_name} //= {}; + $self->data->{$collection_name} = $data_out->{$collection_name}; + $found = 1; + } + } + if(!$found){ + die("Missed data for the $collection_name\n"); + } +} + +sub process{ + my($self, $collection_name, $parents_in) = @_; + $self->load_collection_data($collection_name); $parents_in //= {}; - if($self->loaded->{$name} || $self->created->{$name}){ - return $self->get_id($name); + my $parents = {%{$parents_in}}; + $parents->{$collection_name} //= scalar values %$parents_in; + while (my @keys_and_value = reach($self->data->{$collection_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->{$collection_name}->{data}, @keys_and_value, $value ); + } } - 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}; + return $self->data->{$collection_name}->{data}; +} +sub load_collection_data{ + my($self, $collection_name) = @_; + if(!$self->data->{$collection_name}){ + $self->load_data_from_script($collection_name); + } + if(!$self->collection_id_exists($collection_name) ){ + $self->clear_db(undef,undef,[$collection_name]); + $self->load_db(undef,[$collection_name]); + } +} +sub get_id{ + my $self = shift; + #my( $collection_name, $parents_in, $field_path, $params) = @_; + my( $collection_name ) = @_; + $self->load_collection_data($collection_name); + if( $self->collection_id_exists($collection_name) ){ + return $self->get_existent_id($collection_name); + } + return $self->create(@_); +} + +sub get_existent_id{ + my($self, $collection_name) = @_; + my $id = $self->test_machine->get_id_from_created($self->created->{$collection_name}->[0]) + || $self->test_machine->get_id_from_created($self->loaded->{$collection_name}->[0]); + return $id +} + +sub create{ + my($self, $collection_name, $parents_in, $field_path, $params) = @_; + $parents_in //= {}; + if($parents_in->{$collection_name}){ + if($self->data->{$collection_name}->{default}){ + $self->data->{$collection_name}->{process_cycled} = {'parents'=>$parents_in,'field_path'=>$field_path}; + return $self->data_default->{$self->data->{$collection_name}->{default}}->{id}; }else{ - die('Data absence', Dumper([$name,$parents_in])); + die('Data absence', Dumper([$collection_name,$parents_in])); } } - $self->process($name, $parents_in); + $self->process($collection_name, $parents_in); #create itself - my $data = clone($self->data->{$name}->{data}); + my $data = clone($self->data->{$collection_name}->{data}); $self->test_machine->set( - name => $name, + name => $collection_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); + if(exists $self->data->{$collection_name}->{create_special} && 'CODE' eq ref $self->data->{$collection_name}->{create_special}){ + $self->data->{$collection_name}->{create_special}->($self,$collection_name); }else{ $self->test_machine->check_create_correct(1); } - $self->created->{$name} = [values %{$self->test_machine->DATA_CREATED->{ALL}}]; + $self->created->{$collection_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}}; + if($self->data->{$collection_name}->{process_cycled}){ + my %parents_cycled_ordered = reverse %{$self->data->{$collection_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}); + my $uri = $self->test_machine->get_uri_collection($parents_cycled_ordered{$last_parent}).$self->get_existent_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) } ], + path => join('/',('',@{$self->data->{$collection_name}->{process_cycled}->{field_path}})), + value => $self->get_existent_id($collection_name) } ], $uri ); - delete $self->data->{$name}->{process_cycled}; + delete $self->data->{$collection_name}->{process_cycled}; } - return $self->get_id($name); + return $self->get_existent_id($collection_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 collection_id_exists{ + my($self, $collection_name) = @_; + return exists $self->loaded->{$collection_name} || exists $self->created->{$collection_name} } sub DEMOLISH{ my($self) = @_; - ( 'ARRAY' eq ref$self->created ) and ( $self->test_machine->clear_test_data_all([ map {$_->{location}} @$self->created ]) ); + ( 'ARRAY' eq ref $self->created ) and ( $self->test_machine->clear_test_data_all([ map {$_->{location}} @$self->created ]) ); + if( keys %{$self->undeletable} ){ + print "We have test items, which can't delete through API:\n"; + print Dumper [ sort { $a cmp $b } keys %{$self->undeletable} ]; + } + } 1; __END__