diff --git a/lib/NGCP/Panel/Controller/API/FaxserverSettingsItem.pm b/lib/NGCP/Panel/Controller/API/FaxserverSettingsItem.pm index d814220ae0..ec387e135b 100644 --- a/lib/NGCP/Panel/Controller/API/FaxserverSettingsItem.pm +++ b/lib/NGCP/Panel/Controller/API/FaxserverSettingsItem.pm @@ -11,6 +11,7 @@ use NGCP::Panel::Utils::ValidateJSON qw(); use NGCP::Panel::Utils::DateTime; use Path::Tiny qw(path); use Safe::Isa qw($_isa); +use Clone qw/clone/; BEGIN { extends 'Catalyst::Controller::ActionRole'; } require Catalyst::ActionRole::ACL; require Catalyst::ActionRole::HTTPMethods; @@ -110,6 +111,7 @@ sub PATCH :Allow { my $item = $self->item_by_id($c, $id); last unless $self->resource_exists($c, faxserversettings => $item); my $old_resource = $self->hal_from_item($c, $item)->resource; + $old_resource = clone($old_resource); my $resource = $self->apply_patch($c, $old_resource, $json); last unless $resource; diff --git a/t/api-rest/api-billingfees.t b/t/api-rest/api-billingfees.t index 8e8adde3c5..6492d9486b 100644 --- a/t/api-rest/api-billingfees.t +++ b/t/api-rest/api-billingfees.t @@ -1,18 +1,11 @@ use strict; use warnings; -#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; $fake_data->set_data_from_script({ @@ -44,35 +37,35 @@ $test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPT $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_get2put(); $test_machine->check_bundle(); # specific tests # try to create fee without billing_profile_id { - my ($res, $err) = $test_machine->request_post(sub{delete $_[0]->{billing_profile_id};}); + my ($res, $err) = $test_machine->check_item_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 { - my ($res, $err) = $test_machine->request_post(sub{$_[0]->{billing_profile_id} = 99999;}); + my ($res, $err) = $test_machine->check_item_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 without billing_zone_id { - my ($res, $err) = $test_machine->request_post(sub{delete $_[0]->{billing_zone_id};}); + my ($res, $err) = $test_machine->check_item_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 { - my ($res, $err) = $test_machine->request_post(sub{$_[0]->{billing_zone_id} = 99999;}); + my ($res, $err) = $test_machine->check_item_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"); @@ -80,7 +73,7 @@ $test_machine->check_bundle(); # try to create fee with implicit zone which already exists { my $t = time; - my ($res, $err) = $test_machine->request_post(sub{ + my ($res, $err) = $test_machine->check_item_post(sub{ delete $_[0]->{billing_zone_id}; $_[0]->{billing_zone_zone} = 'apitestzone'; $_[0]->{billing_zone_detail} = 'api_test zone'; @@ -88,14 +81,13 @@ $test_machine->check_bundle(); }); is($res->code, 201, "create profile fee with existing implicit zone"); 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"); + ($res, $z_fee, $req) = $test_machine->check_item_get($test_machine->normalize_uri($res->header('Location')),"fetch profile fee with existing implicit zone"); 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 = 1 + time(); - my ($res, $err) = $test_machine->request_post(sub{ + my ($res, $err) = $test_machine->check_item_post(sub{ delete $_[0]->{billing_zone_id}; $_[0]->{billing_zone_zone} = 'apitestzone'.$t; $_[0]->{billing_zone_detail} = 'api_test zone'.$t; @@ -103,14 +95,13 @@ $test_machine->check_bundle(); }); is($res->code, 201, "create profile fee with new implicit zone"); 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"); + ($res, $z_fee, $req) = $test_machine->check_item_get($test_machine->normalize_uri($res->header('Location')),"fetch profile fee with new implicit zone"); 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,$res,$content) = $test_machine->request_delete($test_machine->base_uri.$z_fee->{_links}->{'ngcp:billingzones'}->{href}); + ($req,$res,$content) = $test_machine->request_delete($test_machine->normalize_uri($z_fee->{_links}->{'ngcp:billingzones'}->{href})); is($res->code, 204, "delete new implicit zone"); - ($res) = $test_machine->request_get($test_machine->base_uri.$z_fee->{_links}->{'self'}->{href}); + ($res) = $test_machine->request_get($test_machine->normalize_uri($z_fee->{_links}->{'self'}->{href})); is($res->code, 404, "check if fee is deleted when zone is deleted"); } @@ -150,7 +141,7 @@ $test_machine->check_bundle(); $test_machine->clear_test_data_all(); { - my $uri = $test_machine->base_uri.'/api/billingzones/'.$test_machine->DATA_ITEM->{billing_zone_id}; + my $uri = $test_machine->normalize_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"); ($res, $content, $req) = $test_machine->request_get($uri); diff --git a/t/api-rest/api-billingzones.t b/t/api-rest/api-billingzones.t index 954e9eb70d..0d1f41a0dc 100644 --- a/t/api-rest/api-billingzones.t +++ b/t/api-rest/api-billingzones.t @@ -30,7 +30,7 @@ $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_get2put(); $test_machine->check_bundle(); $test_machine->clear_test_data_all(); done_testing; diff --git a/t/api-rest/api-calllists.t b/t/api-rest/api-calllists.t index 1ea82b571e..c870b0eb20 100644 --- a/t/api-rest/api-calllists.t +++ b/t/api-rest/api-calllists.t @@ -18,8 +18,7 @@ SKIP: if($ENV{API_FORCE_SUBSCRIBER_ID}) { $sub1_id = $ENV{API_FORCE_SUBSCRIBER_ID}; } else { - ($res, $sub1) = $test_machine->request_get('/api/subscribers/?page=1&rows=1'); - is($res->code, 200, "fetch a subscriber for testing"); + ($res, $sub1) = $test_machine->check_item_get('/api/subscribers/?page=1&rows=1',"fetch a subscriber for testing"); if ($sub1->{total_count} < 1) { skip("Precondition not met: need a subscriber",1); } @@ -27,14 +26,11 @@ SKIP: } cmp_ok ($sub1_id, '>', 0, "should be positive integer"); - ($res, $cl_collection) = $test_machine->request_get('/api/calllists/?page=1&rows=10&subscriber_id='.$sub1_id); - is($res->code, 200, "fetch calllists collection of subscriber ($sub1_id)"); + ($res, $cl_collection) = $test_machine->check_item_get('/api/calllists/?page=1&rows=10&subscriber_id='.$sub1_id,"fetch calllists collection of subscriber ($sub1_id)"); - ($res, $cl_collection_in) = $test_machine->request_get('/api/calllists/?page=1&rows=10&direction=in&subscriber_id='.$sub1_id); - is($res->code, 200, "fetch calllists collection of subscriber ($sub1_id) with direction filter in"); + ($res, $cl_collection_in) = $test_machine->check_item_get('/api/calllists/?page=1&rows=10&direction=in&subscriber_id='.$sub1_id,"fetch calllists collection of subscriber ($sub1_id) with direction filter in"); - ($res, $cl_collection_out) = $test_machine->request_get('/api/calllists/?page=1&rows=10&direction=out&subscriber_id='.$sub1_id); - is($res->code, 200, "fetch calllists collection of subscriber ($sub1_id) with direction filter out"); + ($res, $cl_collection_out) = $test_machine->check_item_get('/api/calllists/?page=1&rows=10&direction=out&subscriber_id='.$sub1_id, "fetch calllists collection of subscriber ($sub1_id) with direction filter out"); is($cl_collection_in->{total_count}+$cl_collection_out->{total_count}, $cl_collection->{total_count}, "Incoming and outgoing calls should add up to total number of calls"); @@ -50,8 +46,7 @@ SKIP: if($ENV{API_FORCE_CUSTOMER_ID}) { $cust1_id = $ENV{API_FORCE_CUSTOMER_ID}; } else { - ($res, $cust1) = $test_machine->request_get('/api/customers/?page=1&rows=1'); - is($res->code, 200, "fetch a customer for testing"); + ($res, $cust1) = $test_machine->check_item_get('/api/customers/?page=1&rows=1',"fetch a customer for testing"); if ($cust1->{total_count} < 1) { skip("Precondition not met: need a customer",1); } @@ -59,14 +54,11 @@ SKIP: } cmp_ok ($cust1_id, '>', 0, "should be positive integer"); - ($res, $cl_collection) = $test_machine->request_get('/api/calllists/?page=1&rows=10&customer_id='.$cust1_id); - is($res->code, 200, "fetch calllists collection of customer ($cust1_id)"); + ($res, $cl_collection) = $test_machine->check_item_get('/api/calllists/?page=1&rows=10&customer_id='.$cust1_id,"fetch calllists collection of customer ($cust1_id)"); - ($res, $cl_collection_in) = $test_machine->request_get('/api/calllists/?page=1&rows=10&direction=in&customer_id='.$cust1_id); - is($res->code, 200, "fetch calllists collection of customer ($cust1_id) with direction filter in"); + ($res, $cl_collection_in) = $test_machine->check_item_get('/api/calllists/?page=1&rows=10&direction=in&customer_id='.$cust1_id,"fetch calllists collection of customer ($cust1_id) with direction filter in"); - ($res, $cl_collection_out) = $test_machine->request_get('/api/calllists/?page=1&rows=10&direction=out&customer_id='.$cust1_id); - is($res->code, 200, "fetch calllists collection of customer ($cust1_id) with direction filter out"); + ($res, $cl_collection_out) = $test_machine->check_item_get('/api/calllists/?page=1&rows=10&direction=out&customer_id='.$cust1_id,"fetch calllists collection of customer ($cust1_id) with direction filter out"); is($cl_collection_in->{total_count}+$cl_collection_out->{total_count}, $cl_collection->{total_count}, "Incoming and outgoing calls should add up to total number of calls"); diff --git a/t/api-rest/api-cfdestinationsets.t b/t/api-rest/api-cfdestinationsets.t index ed277c2fca..8b533b9a7b 100644 --- a/t/api-rest/api-cfdestinationsets.t +++ b/t/api-rest/api-cfdestinationsets.t @@ -9,15 +9,12 @@ my $test_machine = Test::Collection->new( ); diag('Note that the next tests require at least one subscriber to be present ' . - 'and accessible to the current API user.'); + 'and accessible to the current API user.'); # fetch a cfdestinationset for testing that { - my ($res, $content) = $test_machine->request_get('/api/cfdestinationsets/?page=1&rows=10'); - is($res->code, 200, "fetch cfdestinationsets collection"); - - ($res, $content) = $test_machine->request_get('/api/cftimesets/?page=1&rows=10'); - is($res->code, 200, "fetch cftimesets collection"); + my ($res, $content) = $test_machine->check_item_get('/api/cfdestinationsets/?page=1&rows=10', "fetch cfdestinationsets collection"); + ($res, $content) = $test_machine->check_item_get('/api/cftimesets/?page=1&rows=10', "fetch cftimesets collection"); } # fetch a cfdestinationset being a reseller @@ -33,11 +30,9 @@ SKIP: } is($res->code, 200, "fetch cfdestinationsets collection as reseller"); - ($res, $cft_collection1) = $test_machine->request_get('/api/cftimesets/?page=1&rows=10'); - is($res->code, 200, "fetch cftimesets collection as reseller"); + ($res, $cft_collection1) = $test_machine->check_item_get('/api/cftimesets/?page=1&rows=10', "fetch cftimesets collection as reseller"); - ($res, $sub1) = $test_machine->request_get('/api/subscribers/?page=1&rows=1'); - is($res->code, 200, "fetch a subscriber of our reseller for testing"); + ($res, $sub1) = $test_machine->check_item_get('/api/subscribers/?page=1&rows=1',"fetch a subscriber of our reseller for testing"); if ($sub1->{total_count} < 1) { skip("Precondition not met: need a subscriber",1); } @@ -45,16 +40,14 @@ SKIP: cmp_ok ($sub1_id, '>', 0, "should be positive integer"); - ($res, $cf_collection2) = $test_machine->request_get('/api/cfdestinationsets/?page=1&rows=10&subscriber_id='.$sub1_id); - is($res->code, 200, "fetch cfdestinationsets collection as reseller with subscriber filter"); + ($res, $cf_collection2) = $test_machine->check_item_get('/api/cfdestinationsets/?page=1&rows=10&subscriber_id='.$sub1_id, "fetch cfdestinationsets collection as reseller with subscriber filter"); cmp_ok($cf_collection1->{total_count}, '>=', $cf_collection2->{total_count}, "filtered collection (cfdestinationsets) should be smaller or equal"); # -------- - ($res, $cft_collection2) = $test_machine->request_get('/api/cftimesets/?page=1&rows=10&subscriber_id='.$sub1_id); - is($res->code, 200, "fetch cftimesets collection as reseller with subscriber filter"); + ($res, $cft_collection2) = $test_machine->check_item_get('/api/cftimesets/?page=1&rows=10&subscriber_id='.$sub1_id, "fetch cftimesets collection as reseller with subscriber filter"); cmp_ok($cft_collection1->{total_count}, '>=', $cft_collection2->{total_count}, "filtered collection (cftimesets) should be smaller or equal"); diff --git a/t/api-rest/api-faxes.t b/t/api-rest/api-faxes.t index fee266c45f..cdd39b9161 100644 --- a/t/api-rest/api-faxes.t +++ b/t/api-rest/api-faxes.t @@ -1,18 +1,13 @@ -#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; + use File::Basename; + #init test_machine my $fake_data = Test::FakeData->new; $fake_data->set_data_from_script({ @@ -25,11 +20,11 @@ $fake_data->set_data_from_script({ faxfile => [ dirname($0).'/resources/empty.txt' ], }, '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); + my ($self,$name,$test_machine) = @_; + my $prev_params = $test_machine->get_cloned('content_type'); + @{$test_machine->content_type}{qw/POST PUT/} = (('multipart/form-data') x 2); + $test_machine->check_create_correct(1); + $test_machine->set(%$prev_params); }, 'no_delete_available' => 1, }, @@ -38,14 +33,23 @@ my $test_machine = Test::Collection->new( name => 'faxes', embedded => [qw/subscribers/] ); + $test_machine->DATA_ITEM_STORE($fake_data->process('faxes')); +{ + my $uri = $test_machine->normalize_uri('/api/faxserversettings/'.$test_machine->DATA_ITEM->{json}->{subscriber_id}); + my($res,$faxserversettings,$req) = $test_machine->check_item_get($uri); + $faxserversettings->{active} = 1; + $faxserversettings->{password} = 'aaa111'; + $test_machine->request_put($faxserversettings,$uri); +} @{$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)}; -$test_machine->form_data_item( ); -$test_machine->check_create_correct( 1 ); +$test_machine->form_data_item(); + +$test_machine->check_create_correct( 1, sub{ my $cmd = "echo 'aaa' > $_[0]->{faxfile}->[0]";`$cmd`; } ); #$test_machine->check_bundle(); #$test_machine->check_get2put( sub { $_[0] = { json => JSON::to_json($_[0]), 'faxfile' => $test_machine->DATA_ITEM_STORE->{faxfile} }; } ); diff --git a/t/api-rest/api-pbxdevicemodels.t b/t/api-rest/api-pbxdevicemodels.t index 83344e6780..49033e4bbf 100644 --- a/t/api-rest/api-pbxdevicemodels.t +++ b/t/api-rest/api-pbxdevicemodels.t @@ -69,11 +69,11 @@ $fake_data->set_data_from_script({ }, '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); + my ($self,$collection_name,$test_machine) = @_; + my $prev_params = $test_machine->get_cloned('content_type'); + @{$test_machine->content_type}{qw/POST PUT/} = (('multipart/form-data') x 2); + $test_machine->check_create_correct(1); + $test_machine->set(%$prev_params); }, 'no_delete_available' => 1, }, @@ -111,27 +111,34 @@ foreach my $type(qw/extension phone/){ $_[0]->{json}->{model} .= $type."TEST_".($_[1]->{i} + 3).'_'.$time; $_[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_get2put( { + 'data_cb' => 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};}); + my ($res, $err) = $test_machine->check_item_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;}); + my ($res, $err) = $test_machine->check_item_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;}); + my ($res, $err) = $test_machine->check_item_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"); diff --git a/t/api-rest/api-pbxdevices.t b/t/api-rest/api-pbxdevices.t index 9ce61875bd..3ab0fde687 100644 --- a/t/api-rest/api-pbxdevices.t +++ b/t/api-rest/api-pbxdevices.t @@ -24,7 +24,7 @@ $fake_data->set_data_from_script({ profile_id => sub { return shift->get_id('pbxdeviceprofiles',@_); }, customer_id => sub { return shift->get_id('customers',@_); }, identifier => 'aaaabbbbcccc', - station_name => 'api_test_vun', + station_name => 'api_test_run', lines=>[{ linerange => 'Phone Ports api_test', type => 'private', @@ -51,7 +51,7 @@ $test_machine->DATA_ITEM_STORE($fake_data->process('pbxdevices')); $test_machine->form_data_item( ); # create 3 new field pbx devices from DATA_ITEM $test_machine->check_create_correct( 3, sub{ $_[0]->{identifier} = sprintf('%x', (hex('0x'.$_[0]->{identifier}) + $_[1]->{i}) ); } ); -$test_machine->check_get2put( ); +$test_machine->check_get2put(); $test_machine->check_bundle(); $test_machine->clear_test_data_all(); diff --git a/t/api-rest/api-subscriberregistrations.t b/t/api-rest/api-subscriberregistrations.t index 0c86be4b7f..786e139005 100644 --- a/t/api-rest/api-subscriberregistrations.t +++ b/t/api-rest/api-subscriberregistrations.t @@ -1,8 +1,6 @@ -#use Sipwise::Base; use strict; #use Moose; -use Sipwise::Base; use Test::Collection; use Test::FakeData; use Test::More; @@ -53,34 +51,31 @@ $test_machine->clear_data_created(); $test_machine->check_create_correct( 1, sub{ $_[0]->{contact} .= time().'_'.$_[1]->{i} ; } ); { - my($res, $content) = $test_machine->request_post(sub{$_[0]->{q} = 2;$_[0]->{contact} .= time().'_MT14779_1' ;}); + my($res, $content) = $test_machine->check_item_post(sub{$_[0]->{q} = 2;$_[0]->{contact} .= time().'_MT14779_1' ;}); $test_machine->http_code_msg(422, "check creation of the subscriber registration with q > 1. MT#14779",$res,$content); } { - my($res, $content) = $test_machine->request_post(sub{$_[0]->{q} = -2;$_[0]->{contact} .= time().'_MT14779_2' ;}); + my($res, $content) = $test_machine->check_item_post(sub{$_[0]->{q} = -2;$_[0]->{contact} .= time().'_MT14779_2' ;}); $test_machine->http_code_msg(422, "check creation of the subscriber registration with q < -1. MT#14779",$res,$content); } { # Default value should be used. - my($res, $content) = $test_machine->request_post(sub{delete $_[0]->{q};$_[0]->{contact} .= time().'_MT14779_3';}); + my($res, $content) = $test_machine->check_item_post(sub{delete $_[0]->{q};$_[0]->{contact} .= time().'_MT14779_3';}); $test_machine->http_code_msg(201, "check creation of the subscriber registration without q. MT#14779.",$res,$content); } { - my($res, $content) = $test_machine->request_post(sub{delete $_[0]->{expires};$_[0]->{contact} .= time().'_MT14891_1';}); + my($res, $content) = $test_machine->check_item_post(sub{delete $_[0]->{expires};$_[0]->{contact} .= time().'_MT14891_1';}); $test_machine->http_code_msg(422, "check creation of the subscriber registration without required expires. MT#14891.",$res,$content); } #api doesn't deny extra fields #{ -# my($res, $content) = $test_machine->request_post(sub{$_[0]->{user_agent} = 'Test User Agent';$_[0]->{contact} .= time().'_MT14789' ;}); -# $test_machine->http_code_msg(422, "check creation of the subscriber registration without required expires. MT#14789.",$res,$content); +# my($res, $content) = $test_machine->check_item_post(sub{$_[0]->{user_agent} = 'Test User Agent';$_[0]->{contact} .= time().'_MT14789' ;}); +# $test_machine->http_code_msg(422, "check creation of the subscriber registration with already removed "user_agent". MT#14789.",$res,$content); #} - - $test_machine->clear_test_data_all(); - done_testing; # vim: set tabstop=4 expandtab: diff --git a/t/api-rest/api-subscribers.t b/t/api-rest/api-subscribers.t new file mode 100644 index 0000000000..7aaca24ebf --- /dev/null +++ b/t/api-rest/api-subscribers.t @@ -0,0 +1,124 @@ +use strict; + +use Test::Collection; +use Test::FakeData; +use Test::More; +use Data::Dumper; + +#use NGCP::Panel::Utils::Subscriber; + +my $test_machine = Test::Collection->new( + name => 'subscribers', +); +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({ + 'subscribers' => { + 'data' => { + administrative => 0, + 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->get_id('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->get_id('subscriberprofiles',@_); }, + status => 'active', + pbx_hunt_policy => 'parallel', + pbx_hunt_timeout => '15', + }, + 'query' => ['username'], + }, +}); + +$test_machine->DATA_ITEM_STORE($fake_data->process('subscribers')); +$test_machine->form_data_item( ); + +# create new subscribers from DATA_ITEM. Item is not created in the fake_data->process. + +$test_machine->check_create_correct( 1, sub{ $_[0]->{username} .= time().'_'.$_[1]->{i} ; } ); +##$test_machine->check_bundle(); +##$test_machine->check_get2put(); + +#------- MT#15441 +{ + my $intentional_cli = '111'.time(); + my $intentional_primary_number = { + 'cc' => '111', + 'ac' => '222', + 'sn' => '123'.time(), + }; + + #prepare preconditions: cli should differ from primary_nuber + my $subscriber = ($test_machine->get_created_first() || $fake_data->get_existent_item($test_machine->name) || $test_machine->get_item_hal()); + $subscriber->{uri} = $subscriber->{location}; + my ($preferences, $preferences_put) = ({'uri' => '/api/subscriberpreferences/'.$test_machine->get_id_from_created($subscriber)}) x 2; + (undef, $preferences->{content}) = $test_machine->check_item_get($preferences->{uri}); + $preferences->{content}->{cli} = $intentional_cli; + (undef, $preferences_put->{content}) = $test_machine->request_put($preferences->{content},$preferences->{uri}); + + is($preferences_put->{content}->{cli}, $intentional_cli, "check that cli was updated on subscriberpreferences put: $preferences_put->{content}->{cli} == $intentional_cli"); + + my ($subscriber_put, $subscriber_get, $preferences_get); + +#1 + $subscriber->{content}->{primary_number} = $intentional_primary_number; + ($subscriber_put,$subscriber_get,$preferences_get) = $test_machine->put_and_get($subscriber, $preferences_put); + is($preferences_get->{content}->{cli}, $intentional_cli, "check that cli was preserved on subscriber phones update: $preferences_get->{content}->{cli} == $intentional_cli"); +#/1 +#2 + delete $subscriber->{content}->{primary_number}; + ($subscriber_put,$subscriber_get,$preferences_get) = $test_machine->put_and_get($subscriber, $preferences_put); + is($preferences_get->{content}->{cli}, $intentional_cli, "check that cli was preserved on subscriber phones update: $preferences_get->{content}->{cli} == $intentional_cli"); +#/2 + #now prepare preferences for zero situation, when synchronization will be restarted again + delete $preferences->{content}->{cli}; + (undef, $preferences_put->{content}) = $test_machine->request_put($preferences->{content},$preferences->{uri}); + is($preferences_put->{content}->{cli}, undef, "check that cli was deleted on subscriberpreferences put with empty cli"); +#3 + $subscriber->{content}->{primary_number} = $intentional_primary_number; + ($subscriber_put,$subscriber_get,$preferences_get) = $test_machine->put_and_get($subscriber, $preferences_put); + is($preferences_get->{content}->{cli}, number_as_string($intentional_primary_number), "check that cli was created on subscriber phones update: $preferences_get->{content}->{cli} == ".number_as_string($intentional_primary_number) ); +#/3 + $intentional_primary_number = { + 'cc' => '222', + 'ac' => '333', + 'sn' => '444'.time(), + }; +#4 + $subscriber->{content}->{primary_number} = $intentional_primary_number; + ($subscriber_put,$subscriber_get,$preferences_get) = $test_machine->put_and_get($subscriber, $preferences_put); + is($preferences_get->{content}->{cli}, number_as_string($intentional_primary_number), "check that cli was updated on subscriber phones update: $preferences_get->{content}->{cli} == ".number_as_string($intentional_primary_number) ); +#/4 +#5 + delete $subscriber->{content}->{primary_number}; + ($subscriber_put,$subscriber_get,$preferences_get) = $test_machine->put_and_get($subscriber, $preferences_put); + is($preferences_get->{content}->{cli}, undef, "check that cli was deleted on subscriber phones update"); +#/5 +} + +$test_machine->clear_test_data_all(); +done_testing; + + +sub number_as_string{ + my ($number_row, %params) = @_; + return 'HASH' eq ref $number_row + ? $number_row->{cc} . ($number_row->{ac} // '') . $number_row->{sn} + : $number_row->cc . ($number_row->ac // '') . $number_row->sn; +} + +# vim: set tabstop=4 expandtab: diff --git a/t/api-rest/api-trustedsources.t b/t/api-rest/api-trustedsources.t index 7215ce005d..2d5a8e7bce 100644 --- a/t/api-rest/api-trustedsources.t +++ b/t/api-rest/api-trustedsources.t @@ -1,17 +1,10 @@ use strict; use warnings; -use Net::Domain qw(hostfqdn); -use LWP::UserAgent; -use HTTP::Request::Common; -use JSON; -use Test::More; -use Data::Dumper; -use File::Basename; -use bignum qw/hex/; - use Test::Collection; use Test::FakeData; +use Test::More; +use Data::Dumper; #init test_machine my $test_machine = Test::Collection->new( @@ -37,15 +30,13 @@ $fake_data->set_data_from_script({ #for item creation test purposes /post request data/ $test_machine->DATA_ITEM_STORE($fake_data->process('trustedsources')); - $test_machine->form_data_item( ); # create 3 new field pbx devices from DATA_ITEM $test_machine->check_create_correct( 3, sub{ $_[0]->{src_ip} = sprintf('1.2.3.%d', $_[1]->{i}); } ); -$test_machine->check_get2put( ); +$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-rest/api-vouchers.t b/t/api-rest/api-vouchers.t index 7a6d37bd84..c784250f87 100644 --- a/t/api-rest/api-vouchers.t +++ b/t/api-rest/api-vouchers.t @@ -44,20 +44,20 @@ 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(); - $voucher_uri = $location_post; - my($res_put,$result_item_put,$req_put,$item_put_data,$get_res,$result_item_get,$get_req) = $test_machine->check_put2get(undef, undef, $voucher_uri); + my($post,$get_post) = $test_machine->check_post2get(); + $voucher_uri = $post->{location}; + my($put,$get_put) = $test_machine->check_put2get( {}, {'uri' => $voucher_uri} ); - $voucher = $result_item_get; + $voucher = $get_put->{content}; - my($res_patch,$patch_voucher) = $test_machine->request_patch( [ { op => 'replace', path => '/code', value => $result_item_put->{code} } ], $voucher_uri ); - delete $result_item_put->{_links}; - delete $result_item_put->{id}; + my($res_patch,$patch_voucher) = $test_machine->request_patch( [ { op => 'replace', path => '/code', value => $put->{content}->{code} } ], $voucher_uri ); + delete $put->{content}->{_links}; + delete $put->{content}->{id}; delete $patch_voucher->{_links}; delete $patch_voucher->{id}; - is_deeply($result_item_put, $patch_voucher, "check PATCHed voucher against POSTed voucher"); + is_deeply($put->{content}, $patch_voucher, "check PATCHed voucher against POSTed voucher"); - my($res,$result_item,$req) = $test_machine->request_post(undef,$voucher); + my($res,$result_item,$req) = $test_machine->request_post($voucher); $test_machine->http_code_msg(422, "POST same voucher code again", $res, $result_item); } { diff --git a/t/lib/Test/Collection.pm b/t/lib/Test/Collection.pm index d4dbce72a0..559b6071f9 100644 --- a/t/lib/Test/Collection.pm +++ b/t/lib/Test/Collection.pm @@ -12,7 +12,7 @@ use Net::Domain qw(hostfqdn); use URI; use URI::Escape; use Clone qw/clone/; - +use Test::HTTPRequestAsCurl; use Data::Dumper; @@ -21,6 +21,12 @@ has 'local_test' => ( isa => 'Str', default => $ENV{LOCAL_TEST} // '', ); + +has 'DEBUG' => ( + is => 'rw', + isa => 'Bool', + default => 0, +); has 'catalyst_config' => ( is => 'rw', isa => 'HashRef', @@ -107,6 +113,11 @@ has 'KEEP_CREATED' =>( isa => 'Bool', default => 1, ); +has 'DATA_LOADED' => ( + is => 'rw', + isa => 'HashRef', + default => sub{{}}, +); has 'URI_CUSTOM' =>( is => 'rw', isa => 'Str', @@ -188,8 +199,8 @@ sub init_ua { my $ua = LWP::UserAgent->new; my $uri = $self->base_uri; $uri =~ s/^https?:\/\///; - my($user,$pass) = $self->get_role_credentials(); - $ua->credentials( $uri, 'api_admin_http', $user, $pass); + my($user,$pass,$role,$realm) = $self->get_role_credentials(); + $ua->credentials( $uri, $realm, $user, $pass); $ua->ssl_opts( verify_hostname => 0, SSL_verify_mode => 0, @@ -201,9 +212,9 @@ sub runas { my($role_in,$uri) = @_; $uri //= $self->base_uri; $uri =~ s/^https?:\/\///; - my($user,$pass,$role) = $self->get_role_credentials($role_in); + my($user,$pass,$role,$realm) = $self->get_role_credentials($role_in); $self->runas_role($role); - $self->ua->credentials( $uri, 'api_admin_http', $user, $pass); + $self->ua->credentials( $uri, $realm, $user, $pass); } sub get_role_credentials{ my $self = shift; @@ -217,7 +228,8 @@ sub get_role_credentials{ $user //= $ENV{API_USER_RESELLER} // 'api_test'; $pass //= $ENV{API_PASS_RESELLER} // 'api_test'; } - return($user,$pass,$role); + my $realm = 'api_admin_http'; + return($user,$pass,$role,$realm); } sub clear_data_created{ my($self) = @_; @@ -252,39 +264,63 @@ sub restore_uri_custom{ sub get_uri_collection{ my($self,$name) = @_; $name //= $self->name; - return $self->base_uri."/api/".$name.($name ? "/" : "").($self->QUERY_PARAMS ? "?".$self->QUERY_PARAMS : ""); + return $self->normalize_uri("/api/".$name.($name ? "/" : "").($self->QUERY_PARAMS ? "?".$self->QUERY_PARAMS : "")); } sub get_uri_get{ my($self,$query_string, $name) = @_; $name //= $self->name; - return $self->base_uri."/api/".$name.($query_string ? '/?' : '/' ).$query_string; + return $self->normalize_uri("/api/".$name.($query_string ? '/?' : '/' ).$query_string); } sub get_uri{ my($self,$add,$name) = @_; $add //= ''; $name //= $self->name; - return $self->base_uri."/api/".$name.'/'.$add; + return $self->normalize_uri("/api/".$name.'/'.$add); +} +sub get_uri_item{ + my($self,$name,$item) = @_; + my $resuri; + $item ||= $self->get_item_hal($name); + $resuri = $self->normalize_uri('/'.$item->{location}); + return $resuri; } -sub get_uri_firstitem{ +sub get_item_hal{ my($self,$name) = @_; - if(!$self->DATA_CREATED->{FIRST}){ - my($res,$list_collection,$req) = $self->check_item_get($self->get_uri_collection."?page=1&rows=1"); + $name ||= $self->name; + my $resitem ; + if(( $name eq $self->name ) && $self->DATA_CREATED->{FIRST}){ + $resitem = $self->get_created_first; + } + if($self->DATA_LOADED->{$name} && @{$self->DATA_LOADED->{$name}}){ + $resitem = $self->DATA_LOADED->{$name}->[0]; + } + if(!$resitem){ + my ($reshal, $location); + my($res,$list_collection,$req) = $self->check_item_get($self->get_uri_collection($name)."?page=1&rows=1"); my $hal_name = $self->get_hal_name($name); if(ref $list_collection->{_links}->{$hal_name} eq "HASH") { - $self->DATA_CREATED->{FIRST} = $list_collection->{_links}->{$hal_name}->{href}; + $reshal = $list_collection; + $location = $reshal->{_links}->{$hal_name}->{href}; } else { - $self->DATA_CREATED->{FIRST} = $list_collection->{_embedded}->{$hal_name}->[0]->{_links}->{self}->{href}; + $reshal = $list_collection->{_embedded}->{$hal_name}->[0]; + $location = $reshal->{_links}->{self}->{href}; } + $resitem = { num => 1, content => $reshal, res => $res, req => $req, location => $location }; + $self->DATA_LOADED->{$name} ||= []; + push @{$self->DATA_LOADED->{$name}}, $resitem; } - $self->DATA_CREATED->{FIRST} //= ''; - return $self->base_uri.'/'.$self->DATA_CREATED->{FIRST}; + return $resitem; +} +sub get_created_first{ + my($self) = @_; + return $self->DATA_CREATED->{ALL}->{$self->DATA_CREATED->{FIRST}}; } - sub get_uri_current{ my($self) = @_; $self->URI_CUSTOM and return $self->URI_CUSTOM; - return $self->get_uri_firstitem; + return $self->get_uri_item; } + sub encode_content{ my($self,$content, $type) = @_; $type //= $self->ENCODE_CONTENT; @@ -304,15 +340,25 @@ sub encode_content{ } sub request{ my($self,$req) = @_; - #print $req->as_string; + + + my $credentials = {}; + (@$credentials{qw/user password/},undef,undef) = $self->get_role_credentials(); + my $curl = Test::HTTPRequestAsCurl::as_curl($req, credentials => $credentials ); + if($self->DEBUG){ + print $req->as_string; + print "$curl\n\n"; + } my $res = $self->ua->request($req); #draft of the debug mode - #if($res->code >= 400){ - # print Dumper $req; - # print Dumper $res; - # print Dumper $self->get_response_content($res); - # die; - #} + if($self->DEBUG){ + if($res->code >= 400){ + print Dumper $req; + print Dumper $res; + print Dumper $self->get_response_content($res); + #die; + } + } return $res; } @@ -326,6 +372,7 @@ sub request_process{ sub get_request_put{ my($self,$content,$uri) = @_; $uri ||= $self->get_uri_current; + $uri = $self->normalize_uri($uri); #This is for multipart/form-data cases $content = $self->encode_content($content, $self->content_type->{PUT}); my $req = POST $uri, @@ -338,6 +385,7 @@ sub get_request_put{ sub get_request_patch{ my($self,$uri) = @_; $uri ||= $self->get_uri_current; + $uri = $self->normalize_uri($uri); my $req = HTTP::Request->new('PATCH', $uri); $req->header('Prefer' => 'return=representation'); $req->header('Content-Type' => $self->content_type->{PATCH} ); @@ -346,7 +394,7 @@ sub get_request_patch{ sub request_put{ my($self,$content,$uri) = @_; $uri ||= $self->get_uri_current; - my $req = $self->get_request_put( $content, $uri ); + my $req = $self->get_request_put( $content, $self->normalize_uri($uri) ); my $res = $self->request($req); my $rescontent = $self->get_response_content($res); return wantarray ? ($res,$rescontent,$req) : $res; @@ -363,36 +411,28 @@ sub request_patch{ #print Dumper [$res,$rescontent,$req]; return wantarray ? ($res,$rescontent,$req) : $res; } -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, - }; + my($self, $content, $uri, $req) = @_; + $uri ||= $self->get_uri_collection; + $uri = $self->normalize_uri($uri); $content = $self->encode_content($content, $self->content_type->{POST} ); #form-data is set automatically, despite on $self->content_type->{POST} - my $req = POST $self->get_uri_collection, + $req ||= POST $uri, Content_Type => $self->content_type->{POST}, Content => $content; + $req->header('Prefer' => 'return=representation'); my $res = $self->request($req); my $rescontent = $self->get_response_content($res); return wantarray ? ($res,$rescontent,$req,$content) : $res; }; - sub request_options{ my ($self,$uri) = @_; # OPTIONS tests my $req = HTTP::Request->new('OPTIONS', $self->normalize_uri($uri)); my $res = $self->request($req); my $content = $self->get_response_content($res); - return($req,$res,$content); + return($res,$content,$req); } sub request_delete{ @@ -417,6 +457,9 @@ sub get_response_content{ if($res->decoded_content){ eval { $content = JSON::from_json($res->decoded_content); }; } + #print "get_response_content;\n"; + #print Dumper [caller]; + #print Dumper $content; return $content; } sub normalize_uri{ @@ -430,6 +473,8 @@ sub normalize_uri{ ############## end of test machine ############## start of test collection +#---------------- options, methods, hal format + sub check_options_collection{ my ($self, $uri) = @_; # OPTIONS tests @@ -463,43 +508,6 @@ sub check_methods{ } } } -sub check_create_correct{ - 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} ); - $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}; - $self->DATA_CREATED->{FIRST} = $location unless $self->DATA_CREATED->{FIRST}; - } - } -} -sub clear_test_data_all{ - my($self,$uri) = @_; - 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); - $self->http_code_msg(204, "check delete item $del_uri",$res,$content); - } - $self->clear_data_created(); -} -sub clear_test_data_dependent{ - my($self,$uri) = @_; - 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) = @_; @@ -508,7 +516,7 @@ sub check_list_collection{ do { #print "nexturi=$nexturi;\n"; my ($res,$list_collection) = $self->check_item_get($nexturi); - my $selfuri = $self->base_uri . $list_collection->{_links}->{self}->{href}; + my $selfuri = $self->normalize_uri($list_collection->{_links}->{self}->{href}); is($selfuri, $nexturi, "check _links.self.href of collection"); my $colluri = URI->new($selfuri); @@ -531,7 +539,7 @@ sub check_list_collection{ } if($list_collection->{_links}->{next}->{href}) { - $nexturi = $self->base_uri . $list_collection->{_links}->{next}->{href}; + $nexturi = $self->normalize_uri($list_collection->{_links}->{next}->{href}); } else { $nexturi = undef; } @@ -575,17 +583,18 @@ sub check_created_listed{ } } -sub check_item_get{ - my($self,$uri) = @_; - $uri ||= $self->get_uri_current; - my $req = HTTP::Request->new('GET', $uri); - my $res = $self->request($req); - #print Dumper $res; - $self->http_code_msg(200, "fetch uri: $uri", $res); - my $content = $self->get_response_content($res); - return wantarray ? ($res, $content, $req) : $res; +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"); + } } +#------------------- put bundle ----------- + +#------------------- put bundle ----------- + sub check_put_content_type_empty{ my($self) = @_; # check if it fails without content type @@ -625,58 +634,6 @@ sub check_put_body_empty{ $self->http_code_msg(400, "check put no body", $res, $content); } -sub check_get2put{ - my($self, $put_data_cb, $uri) = @_; - #$req->remove_header('Prefer'); - #$req->header('Prefer' => "return=representation"); - # PUT same result again - 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_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; @@ -684,6 +641,8 @@ sub check_put_bundle{ $self->check_put_prefer_wrong; $self->check_put_body_empty; } + +#-------------------- patch bundle ------- sub check_patch_correct{ my($self,$content) = @_; my ($res,$rescontent,$req) = $self->request_patch( $content ); @@ -799,7 +758,161 @@ sub check_bundle{ } } } -#utils + +sub check_item_get{ + my($self, $uri, $msg) = @_; + $msg //= ''; + $uri ||= $self->get_uri_current; + $uri = $self->normalize_uri($uri); + my ($res, $content, $req) = $self->request_get($uri); + $self->http_code_msg(200, $msg.($msg?": ":"")."fetch uri: $uri", $res); + return wantarray ? ($res, $content, $req) : $res; +} +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 get_item_post_content{ + my($self, $data_cb, $data_in, $data_cb_data) = @_; + my $data = $self->process_data($data_cb, $data_in, $data_cb_data); + #print Dumper $data; + my $content = { + $data->{json} ? ( json => JSON::to_json(delete $data->{json}) ) : (), + %$data, + }; + return $content; +} +sub check_item_post{ + my($self, $data_cb, $data_in, $data_cb_data) = @_; + my $content = $self->get_item_post_content($data_cb, $data_in, $data_cb_data); + my ($res,$rescontent,$req) = $self->request_post($content);#,$uri,$req + return wantarray ? ($res,$rescontent,$req,$content) : $res; +}; +sub check_create_correct{ + 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->check_item_post( $uniquizer_cb , undef, { i => $i } ); + $self->http_code_msg(201, "create test item '".$self->name."' $i",$res,$content); + my $location = $res->header('Location'); + if($location){ + #some interfaces (e.g. subscribers) don't provide hal after creation - is it correct, by the way? + if(!$content){ + my($res_get,$content_get,$content_req) = $self->check_item_get($location,"no object returned after POST"); + if($content_get){ + $content = $content_get; + } + } + $self->DATA_CREATED->{ALL}->{$location} = { num => $i, content => $content, res => $res, req => $req, location => $location}; + $self->DATA_CREATED->{FIRST} = $location unless $self->DATA_CREATED->{FIRST}; + } + } +} + +sub clear_test_data_all{ + my($self,$uri) = @_; + my @uris = $uri ? (('ARRAY' eq ref $uri) ? @$uri : ($uri)) : keys %{ $self->DATA_CREATED->{ALL} }; + foreach my $del_uri(@uris){ + $del_uri = $self->normalize_uri($del_uri); + my($req,$res,$content) = $self->request_delete($del_uri); + $self->http_code_msg(204, "check delete item $del_uri",$res,$content); + } + $self->clear_data_created(); +} +sub clear_test_data_dependent{ + my($self,$uri) = @_; + my($req,$res,$content) = $self->request_delete($self->normalize_uri($uri)); + return ('204' eq $res->code); +} + +sub check_get2put{ + my($self, $put_in, $get_in) = @_; + + my($put_out,$get_out); + + $get_in //= {}; + $get_in->{uri} //= $put_in->{uri}; + $put_in->{uri} //= $get_in->{uri}; + + @$get_out{qw/response content request/} = $self->check_item_get($get_in->{uri}); + $put_out->{content_in} = clone($get_out->{content}); + delete $put_out->{content_in}->{_links}; + delete $put_out->{content_in}->{_embedded}; + # check if put is ok + (defined $put_in->{data_cb}) and $put_in->{data_cb}->($put_out->{content_in}); + @{$put_out}{qw/response content request/} = $self->request_put( $put_out->{content_in}, $put_in->{uri} ); + $self->http_code_msg(200, "check_get2put: check put successful", $put_out->{response}, $put_out->{content} ); + is_deeply($get_out->{content}, $put_out->{content}, "check_get2put: check put if unmodified put returns the same"); + return ($put_out,$get_out); +} + +sub check_put2get{ + my($self, $put_in, $get_in) = @_; + + my($put_out,$get_out); + + $get_in //= {}; + $get_in->{uri} //= $put_in->{uri}; + $put_in->{uri} //= $get_in->{uri}; + $get_out->{uri} = $get_in->{uri}; + + $put_out->{content_in} = $self->process_data($put_in->{data_cb}, $put_in->{data_in}); + $put_out->{content_in} = JSON::to_json($put_out->{content_in}); + @{$put_out}{qw/response content request/} = $self->request_put( $put_out->{content_in}, $put_in->{uri} ); + $self->http_code_msg(200, "check_put2get: check put successful",$put_out->{response}, $put_out->{content}); + + @{$get_out}{qw/response content request/} = $self->check_item_get($get_out->{uri}); + delete $get_out->{content}->{_links}; + delete $get_out->{content}->{_embedded}; + my $item_id = delete $get_out->{content}->{id}; + $put_out->{content_in} = JSON::from_json($put_out->{content_in}); + is_deeply($put_out->{content_in}, $get_out->{content}, "check_put2get: check PUTed item against POSTed item"); + $get_out->{content}->{id} = $item_id; + return ($put_out,$get_out); +} + +sub check_post2get{ + my($self, $post_in, $get_in) = @_; + $get_in //= {}; + #$post = {data_in=>,data_cb=>}; + #$get = {uri=>} + #return + #$post={response,content,request,data,location} + #$get=={response,content,request,uri} + my($post_out,$get_out); + @{$post_out}{qw/response content request data/} = $self->check_item_post( $post_in->{data_cb}, $post_in->{data_in} ); + $self->http_code_msg(201, "check_post2get: POST item '".$self->name."' for check_post2get", @{$post_out}{qw/response content/}); + $post_out->{location} = $self->normalize_uri(($post_out->{response}->header('Location') // '')); + + $get_out->{uri} = $get_in->{uri} // $post_out->{location}; + @{$get_out}{qw/response content request/} = $self->check_item_get( $get_out->{uri}, "check_post2get: fetch POSTed test '".$self->name."'" ); + + delete $get_out->{content}->{_links}; + my $item_id = delete $get_out->{content}->{id}; + is_deeply($post_out->{data}, $get_out->{content}, "check_post2get: check POSTed '".$self->name."' against fetched"); + $get_out->{content}->{id} = $item_id; + + return ($post_out, $get_out); +} +sub put_and_get{ + my($self, $put_in, $get_in) = @_; + my($put_out,$put_get_out,$get_out); + @{$put_out}{qw/response content request/} = $self->request_put($put_in->{content},$put_in->{uri}); + @{$put_get_out}{qw/response content request/} = $self->check_item_get($put_in->{uri}); + @{$get_out}{qw/response content request/} = $self->check_item_get($get_in->{uri}); + delete $put_get_out->{content_in}->{_links}; + delete $put_get_out->{content_in}->{_embedded}; + is_deeply($put_in->{content}, $put_get_out->{content}, "check that '$put_in->{uri}' was updated on put"); + return ($put_out,$put_get_out,$get_out); +} + +####--------------------------utils + sub hash2params{ my($self,$hash) = @_; return join '&', map {$_.'='.uri_escape($hash->{$_})} keys %{ $hash }; diff --git a/t/lib/Test/FakeData.pm b/t/lib/Test/FakeData.pm index dff1dd7c99..0fb435abd5 100644 --- a/t/lib/Test/FakeData.pm +++ b/t/lib/Test/FakeData.pm @@ -229,32 +229,6 @@ sub build_data{ 'no_delete_available' => 1, 'dependency_requires_recreation' => ['resellers'], }, - 'subscribers' => { - 'data' => { - administrative => 0, - 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->get_id('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->get_id('subscriberprofiles',@_); }, - status => 'active', - pbx_hunt_policy => 'parallel', - pbx_hunt_timeout => '15', - }, - 'query' => ['username'], - }, 'domains' => { 'data' => { domain => 'api_test_domain.api_test_domain', @@ -286,12 +260,12 @@ sub build_data{ }, 'query' => ['version'], 'create_special'=> sub { - my ($self,$collection_name) = @_; - my $prev_params = $self->test_machine->get_cloned('content_type','QUERY_PARAMS'); - $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); + my ($self,$collection_name,$test_machine) = @_; + my $prev_params = $test_machine->get_cloned('content_type','QUERY_PARAMS'); + $test_machine->content_type->{POST} = $self->data->{$collection_name}->{data}->{content_type}; + $test_machine->QUERY_PARAMS($test_machine->hash2params($self->data->{$collection_name}->{data})); + $test_machine->check_create_correct(1, sub {return 'test_api_empty_config';} ); + $test_machine->set(%$prev_params); }, 'no_delete_available' => 1, }, @@ -345,11 +319,15 @@ sub load_db{ sub clear_db{ my($self,$data,$order_array,$collections_slice) = @_; + $data ||= $self->data; $order_array //= [qw/contracts systemcontacts customercontacts/]; my $order_hash = {}; $collections_slice //= [keys %$data]; @$order_hash{(keys %$data)} = (0) x @$collections_slice; @$order_hash{@$order_array} = (1..$#$order_array+1); + #print "clear_db;\n"; + #print Dumper $collections_slice; + #print Dumper [caller]; foreach my $collection_name (sort {$order_hash->{$a} <=> $order_hash->{$b}} @$collections_slice ){ if(!$data->{$collection_name}->{query}){ next; @@ -360,7 +338,7 @@ sub clear_db{ $values = ('HASH' eq ref $values) ? [$values] : $values; my @locations = map {$_->{href}} @$values; if($data->{$collection_name}->{no_delete_available}){ - @{$self->undeletable->{@locations}} = ($collection_name) x @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 @@ -372,6 +350,7 @@ sub clear_db{ }else{ $self->test_machine->clear_test_data_all([ @locations ]); } + $self->clear_cached_data($collection_name); } } } @@ -399,15 +378,19 @@ sub search_item{ $field_name.'='.uri_escape($search_value); } @{$item->{query}} ); - my $name_prev = $self->test_machine->{name}; - $self->test_machine->name($collection_name); - my($res, $content, $req) = $self->test_machine->check_item_get($self->test_machine->get_uri_get($query_string)); - $name_prev and $self->test_machine->name($name_prev); + my($res, $content, $req) = $self->test_machine->check_item_get($self->test_machine->get_uri_get($query_string,$collection_name)); #time for memoize? $self->searched->{$collection_name} = [$res, $content, $req]; return ($res, $content, $req); } +sub clear_cached_data{ + my($self, @collections) = @_; + #print Dumper [@collections]; + delete @{$self->loaded}{@collections}; + delete @{$self->created}{@collections}; + delete @{$self->searched}{@collections}; +} sub set_data_from_script{ my($self, $data_in) = @_; while (my($collection_name,$collection_data) = each %$data_in ){ @@ -445,30 +428,16 @@ sub load_data_from_script{ } } -sub process{ - my($self, $collection_name, $parents_in) = @_; - $self->load_collection_data($collection_name); - $parents_in //= {}; - my $parents = {%{$parents_in}};#copy - $parents->{$collection_name}->[0] //= 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 ){ - $parents->{$collection_name}->[1] = [@keys_and_value]; - my $value = $field_value->($self,$parents,[@keys_and_value]); - #here we don't use get/set _collection_data_fields - we should refer directly to the {json}, if we have {json} - nest( $self->data->{$collection_name}->{data}, @keys_and_value, $value ); - } - } - return $self->data->{$collection_name}->{data}; -} sub load_collection_data{ my($self, $collection_name) = @_; + #print "load_collection_data: $collection_name;\n"; + #print Dumper [caller]; if(!$self->data->{$collection_name}){ $self->load_data_from_script($collection_name); } - if(!$self->collection_id_exists($collection_name) ){ + if(! ( $self->collection_id_exists($collection_name) ) ){ $self->clear_db(undef,undef,[$collection_name]); + #print "load_collection_data: $collection_name: collection_id_exists=".$self->collection_id_exists($collection_name)."; loaded:".($self->loaded->{$collection_name}?$self->loaded->{$collection_name}:"undef")."; created: ".($self->created->{$collection_name}?$self->created->{$collection_name}:"undef")." searched: ".($self->searched->{$collection_name}?$self->searched->{$collection_name}:"undef")." ;\n"; $self->load_db(undef,[$collection_name]); } } @@ -476,19 +445,67 @@ sub get_id{ my $self = shift; #my( $collection_name, $parents_in, $params) = @_; my( $collection_name ) = @_; + #print "get_id: $collection_name;\n"; + #print Dumper [caller]; $self->load_collection_data($collection_name); + my $res_id; if( $self->collection_id_exists($collection_name) ){ - return $self->get_existent_id($collection_name); + $res_id = $self->get_existent_id($collection_name); + #print "get_id: $collection_name: id exists: $res_id ;\n"; + }else{ + $res_id = $self->create(@_); + #print "get_id: $collection_name: create: $res_id ;\n"; } - return $self->create(@_); + #print "get_id end: $collection_name:$res_id ;\n"; + #print Dumper $collection_name; + #print Dumper [caller]; + return $res_id; +} +sub get_existent_item{ + my($self, $collection_name) = @_; + my $item = $self->created->{$collection_name}->[0] + || $self->loaded->{$collection_name}->[0]; + return $item } - 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 collection_id_exists{ + my($self, $collection_name) = @_; + return (exists $self->loaded->{$collection_name}) || ( exists $self->created->{$collection_name}); +} +sub set_collection_data_fields{ + my($self, $collection_name, $fields) = @_; + @{ $self->data->{$collection_name}->{data}->{json} || $self->data->{$collection_name}->{data} }{keys %$fields} = values %$fields; +} +sub get_collection_data_fields{ + my($self, $collection_name, @fields ) = @_; + my $data = $self->data->{$collection_name}->{data}->{json} || $self->data->{$collection_name}->{data}; + my %res = map { $_ => $data->{$_} } @fields; + return wantarray ? %res : ( values %res )[0]; +} +sub process{ + my($self, $collection_name, $parents_in) = @_; + #print "process: $collection_name;\n"; + #print Dumper [caller]; + $self->load_collection_data($collection_name); + $parents_in //= {}; + my $parents = {%{$parents_in}};#copy + $parents->{$collection_name}->[0] //= 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 ){ + $parents->{$collection_name}->[1] = [@keys_and_value]; + my $value = $field_value->($self,$parents,[@keys_and_value]); + #here we don't use get/set _collection_data_fields - we should refer directly to the {json}, if we have {json} + nest( $self->data->{$collection_name}->{data}, @keys_and_value, $value ); + } + } + return $self->data->{$collection_name}->{data}; +} sub create{ my($self, $collection_name, $parents_in, $params) = @_; @@ -505,18 +522,24 @@ sub create{ $self->process($collection_name, $parents_in); #create itself my $data = clone($self->data->{$collection_name}->{data}); - $self->test_machine->set( + my $test_machine = clone $self->test_machine; + $test_machine->set( name => $collection_name, DATA_ITEM => $data, ); 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); + $self->data->{$collection_name}->{create_special}->($self,$collection_name,$test_machine); }else{ - $self->test_machine->check_create_correct(1); + $test_machine->check_create_correct(1); } - $self->created->{$collection_name} = [values %{$self->test_machine->DATA_CREATED->{ALL}}]; + $self->created->{$collection_name} = [values %{$test_machine->DATA_CREATED->{ALL}}]; if($self->data->{$collection_name}->{process_cycled}){ + undef $test_machine; + #parents is a flat description of the dependency hierarchy + #parent is just a collection which requires id of the current collection in its data + #parents = { $parent_collection_name => [ $number_of_parents_levels_before, [ @nested_keys in collection to set this collection value]] } + #so, last_parent is just a collection, which directly requires current collection item id my $parents_cycled = $self->data->{$collection_name}->{process_cycled}->{parents}; my $last_parent = ( sort { $parents_cycled->{$b}->[0] <=> $parents_cycled->{$a}->[0] } keys %{$parents_cycled} )[0]; if(grep {$collection_name} @{$self->data->{$last_parent}->{dependency_requires_recreation}} ){ @@ -528,11 +551,12 @@ sub create{ #so all we need - update "created" field for further get_existent_id, which will be aclled on exit from this "create" function $self->create($last_parent,{%parents_temp} ); }else{ - my $uri = $self->test_machine->get_uri_collection($last_parent).$self->get_existent_id($last_parent); - $self->test_machine->request_patch([ { - op => 'replace', - path => join('/',('',@{$parents_cycled->{$last_parent}->[1]})), - value => $self->get_existent_id($collection_name) } ], + my $uri = $test_machine->get_uri_collection($last_parent).$self->get_existent_id($last_parent); + $test_machine->request_patch([ { + op => 'replace', + path => join('/',('',@{$parents_cycled->{$last_parent}->[1]})), + value => $self->get_existent_id($collection_name) } + ], $uri ); } @@ -540,20 +564,6 @@ sub create{ } return $self->get_existent_id($collection_name); } -sub set_collection_data_fields{ - my($self, $collection_name, $fields) = @_; - @{ $self->data->{$collection_name}->{data}->{json} || $self->data->{$collection_name}->{data} }{keys %$fields} = values %$fields; -} -sub get_collection_data_fields{ - my($self, $collection_name, @fields ) = @_; - my $data = $self->data->{$collection_name}->{data}->{json} || $self->data->{$collection_name}->{data}; - my %res = map { $_ => $data->{$_} } @fields; - return wantarray ? %res : ( values %res )[0]; -} -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 ]) ); @@ -561,7 +571,6 @@ sub DEMOLISH{ print "We have test items, which can't delete through API:\n"; print Dumper [ sort { $a cmp $b } keys %{$self->undeletable} ]; } - } 1; __END__ diff --git a/t/lib/Test/HTTPRequestAsCurl.pm b/t/lib/Test/HTTPRequestAsCurl.pm new file mode 100644 index 0000000000..7b488c21a6 --- /dev/null +++ b/t/lib/Test/HTTPRequestAsCurl.pm @@ -0,0 +1,138 @@ +package Test::HTTPRequestAsCurl; +use 5.008005; +use strict; +use warnings; + +our $VERSION = "0.03"; + +use Carp; +use String::ShellQuote qw/ shell_quote /; +#use Win32::ShellQuote qw/ cmd_escape /; +use Exporter::Shiny qw/ as_curl /; + +sub as_curl { + my ($request, %params) = @_; + + my $content = $request->content; + my @data = split '&', $content; + my $method = $request->method; + my $uri = $request->uri; + my $headers = $request->headers; + my $user = $headers->authorization_basic || ( $params{credentials} ? join(':', @{$params{credentials}}{qw/user password/}) : ''); + #my @h = grep { $_ !~ /(authorization|content-length|content-type)/i } + my @h = grep { $_ !~ /(authorization|content-length)/i } + $headers->header_field_names; + + my @cmd = (["curl"]); + push(@cmd, ["--request", $method, $uri]); + push(@cmd, ["--dump-header", "-"]); + push(@cmd, ["--insecure"]) if $user; + push(@cmd, ["--user", $user]) if $user; + push(@cmd, ["--header", "$_: " . $headers->header($_)]) for sort @h; + push(@cmd, ["--data", $_]) for sort @data; + + #return map { @$_ } @cmd unless keys %params; + + return _make_it_pretty(\@cmd, %params); +} + +sub _make_it_pretty { + my ($cmd, %params) = @_; + + $params{shell} = $params{shell} || _default_shell_escape(); + #$params{newline} = $params{newline} || "\\ \n"; + $params{newline} = $params{newline} || ""; + + my $string; + for my $part (@$cmd) { + #if ($params{shell} eq 'win32') { + # $string .= cmd_escape join " ", @$part; + # $string .= ' ^' . $params{newline}; + #} + #els + if ($params{shell} eq 'bourne') { + $string .= shell_quote @$part; + $string .= ' ' . $params{newline}; + } + else { + croak "this shell is not currently supported: $params{shell}"; + } + + } + + return $string; +} + +sub _default_shell_escape { $^O eq 'MSWin32' ? 'win32' : 'bourne' } + + +1; +__END__ + +=encoding utf-8 + +=head1 NAME + +HTTP::Request::AsCurl - Generate a curl command from an HTTP::Request object. + + +=head1 SYNOPSIS + + use HTTP::Request::Common; + use HTTP::Request::AsCurl qw/as_curl/; + + my $request = POST('api.earth.defense/weapon1', { + target => 'mothership', + when => 'now' + }); + + system as_curl($request); + + print as_curl($request, pretty => 1, newline => "\n", shell => 'bourne'); + # curl \ + # --request POST api.earth.defense/weapon1 \ + # --dump-header - \ + # --data target=mothership \ + # --data when=now + + +=head1 DESCRIPTION + +This module converts an HTTP::Request object to a curl command. It can be used +for debugging REST APIs. + +It handles headers and basic authentication. + + +=head1 METHODS + +=head2 as_curl($request, %params) + +Accepts an HTTP::Request object and converts it to a curl command. If there +are no C<%params>, C returns the cmd as an array suitable for being +passed to system(). + +If there are C<%params>, C returns a formatted string. The string's +format defaults to using "\n" for newlines and escaping the curl command using +bourne shell rules unless you are on a win32 system in which case it defaults +to using win32 cmd.exe escaping rules. + +Available params are as follows + + newline: defaults to "\n" + shell: currently available options are 'bourne' and 'win32' + + +=head1 LICENSE + +Copyright (C) Eric Johnson. + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + + +=head1 AUTHOR + +Eric Johnson Eeric.git@iijo.orgE + +=cut