diff --git a/Changes b/Changes index fa73c4772e..de7c9b5b69 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,6 @@ +0.39 + - API updates for any service up to the date of release + 0.38 19 Jun 2018 - Fix test suite so that credentials_process tests don't need extra modules (#251, #248) - API updates for any service up to the date of release diff --git a/Makefile b/Makefile index ec05321e8f..af89c91331 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ test: - carton exec -- prove -v -I lib -I auto-lib t/ + carton exec -- prove -r -v -I lib -I auto-lib t/ pod-test: for i in `find auto-lib/Paws/ -name \*.pm`; do podchecker $$i; done; diff --git a/auto-lib/Paws.pm b/auto-lib/Paws.pm index 84e02d2a67..27adfba6ff 100644 --- a/auto-lib/Paws.pm +++ b/auto-lib/Paws.pm @@ -57,7 +57,7 @@ __PACKAGE__->meta->make_immutable; package Paws; -our $VERSION = '0.38'; +our $VERSION = '0.39'; use Moose; use MooseX::ClassAttribute; diff --git a/auto-lib/Paws/Route53/ChangeTagsForResource.pm b/auto-lib/Paws/Route53/ChangeTagsForResource.pm index 4cb86d3e39..87378539a3 100644 --- a/auto-lib/Paws/Route53/ChangeTagsForResource.pm +++ b/auto-lib/Paws/Route53/ChangeTagsForResource.pm @@ -13,7 +13,7 @@ package Paws::Route53::ChangeTagsForResource; class_has _api_method => (isa => 'Str', is => 'ro', default => 'POST'); class_has _returns => (isa => 'Str', is => 'ro', default => 'Paws::Route53::ChangeTagsForResourceResponse'); class_has _result_key => (isa => 'Str', is => 'ro'); - + 1; ### main pod documentation begin ### @@ -110,4 +110,3 @@ The source code is located here: L Please report bugs to: L =cut - diff --git a/botocore b/botocore index 8eb2d13174..e7a50f64bb 160000 --- a/botocore +++ b/botocore @@ -1 +1 @@ -Subproject commit 8eb2d131748e6f8e240fb8da2ee6fb2c71143191 +Subproject commit e7a50f64bbedd458dd5e8d9ba4a19da39bdbf8fe diff --git a/builder-lib/Paws/API/Builder/Paws.pm b/builder-lib/Paws/API/Builder/Paws.pm index 9bace7e53a..d78e02a620 100644 --- a/builder-lib/Paws/API/Builder/Paws.pm +++ b/builder-lib/Paws/API/Builder/Paws.pm @@ -9,7 +9,7 @@ package Paws::API::Builder::Paws { use Moose; sub version { - '0.38'; + '0.39'; } sub services { diff --git a/cpanfile b/cpanfile index 34cc1bb34e..297f09661f 100644 --- a/cpanfile +++ b/cpanfile @@ -14,7 +14,7 @@ requires 'DateTime'; requires 'DateTime::Format::ISO8601'; requires 'URL::Encode'; requires 'URL::Encode::XS'; -requires 'URI::Template'; +requires 'URI::Template' => '0.23'; requires 'Config::INI'; requires 'Digest::SHA'; # For the paws CLI diff --git a/examples/s3-common-methods.pl b/examples/s3-common-methods.pl index f8998adb96..5fd0f9096c 100755 --- a/examples/s3-common-methods.pl +++ b/examples/s3-common-methods.pl @@ -14,8 +14,9 @@ use Paws; my $service = 'S3'; -my $region = 'us-east-1'; -my $bucketname = 'paws-test-bucket'; +# my $region = 'us-east-1'; +my $region = 'us-west-2'; +my $bucketname = 'shadowcatjesstest'; my $test_dir = 'uploads/test_'; @@ -46,7 +47,7 @@ $response = $s3->CreateBucket( Bucket => $bucketname, CreateBucketConfiguration => { - LocationConstraint => 'eu-west-1', + LocationConstraint => 'us-west-2', }, ); p $response; diff --git a/lib/Paws/Net/Caller.pm b/lib/Paws/Net/Caller.pm index 442181a486..8e37d1d6be 100755 --- a/lib/Paws/Net/Caller.pm +++ b/lib/Paws/Net/Caller.pm @@ -22,6 +22,7 @@ package Paws::Net::Caller; # HTTP::Tiny derives the Host header from the URL. It's an error to set it. delete $headers->{Host}; +# print STDERR Data::Dumper::Dumper($requestObj); my $response = $self->ua->request( $requestObj->method, $requestObj->url, @@ -30,6 +31,7 @@ package Paws::Net::Caller; (defined $requestObj->content)?(content => $requestObj->content):(), } ); + print STDERR Data::Dumper::Dumper($response); return Paws::Net::APIResponse->new( status => $response->{status}, content => $response->{content}, diff --git a/lib/Paws/Net/FileMockCaller.pm b/lib/Paws/Net/FileMockCaller.pm index 16123ca5ee..0f9b8714af 100644 --- a/lib/Paws/Net/FileMockCaller.pm +++ b/lib/Paws/Net/FileMockCaller.pm @@ -63,11 +63,19 @@ package Paws::Net::FileMockCaller; default => sub { shift->file_contents->{ request }->{ params } } ); + has actual_request => ( + is => 'rw', + isa => 'Object', + lazy => 1, + default => sub { '' }, + ); + has _encoder => (is => 'ro', default => sub { JSON::MaybeXS->new(canonical => 1) }); sub send_request { my ($self, $service, $call_object) = @_; + $self->actual_request($service->prepare_request_for_call($call_object)); my $actual_call = $self->_encoder->encode($service->to_hash($call_object)); my $recorded_call = $self->_encoder->encode($self->params); @@ -92,7 +100,7 @@ package Paws::Net::FileMockCaller; sub caller_to_response { my ($self, $service, $call_object, $response) = @_; - + return $self->real_caller->caller_to_response($service, $call_object, $response); }; diff --git a/lib/Paws/Net/MockCaller.pm b/lib/Paws/Net/MockCaller.pm index 0707899e8d..11d91d4303 100644 --- a/lib/Paws/Net/MockCaller.pm +++ b/lib/Paws/Net/MockCaller.pm @@ -8,6 +8,7 @@ package Paws::Net::MockCaller; use Moose::Util::TypeConstraints; use Path::Tiny; use Paws::Net::FileMockCaller; + use Paws::Net::NoResponseMockCaller; has real_caller => ( is => 'ro', @@ -18,17 +19,25 @@ package Paws::Net::MockCaller; } ); + has mock_type => ( + is => 'ro', + isa => 'Str', + default => sub { 'FileMockCaller' }, + ); + has mock_mode => ( is => 'ro', + lazy => 1, isa => enum([ 'REPLAY', 'RECORD' ]), - required => 1, + required => 0, default => sub { $ENV{PAWS_MOCK_MODE} } ); has mock_dir => ( is => 'ro', isa => 'Str', - required => 1, + lazy => 1, + required => 0, default => sub { $ENV{PAWS_MOCK_DIR} } ); @@ -49,7 +58,8 @@ package Paws::Net::MockCaller; lazy => 1, default => sub { my $self = shift; - Paws::Net::FileMockCaller->new( + my $mock_class = 'Paws::Net::' . $self->mock_type; + $mock_class->new( real_caller => $self->real_caller, ); } @@ -60,60 +70,70 @@ package Paws::Net::MockCaller; isa => 'CodeRef', ); + sub actual_request { + return $_[0]->caller->actual_request; + } + sub send_request { my ($self, $service, $call_object) = @_; - $self->_test_file(sprintf("%s/%04d.response", $self->mock_dir, $self->_request_num)); - $self->_next_request; - - if ($self->mock_mode eq 'REPLAY') { - $self->caller->file($self->_test_file); - return $self->caller->send_request($service, $call_object); - } elsif ($self->mock_mode eq 'RECORD') { - return $self->real_caller->send_request($service, $call_object); + if ($self->mock_type eq 'FileMockCaller') { + $self->_test_file(sprintf("%s/%04d.response", $self->mock_dir, $self->_request_num)); + $self->_next_request; + + if ($self->mock_mode eq 'REPLAY') { + $self->caller->file($self->_test_file); + return $self->caller->send_request($service, $call_object); + } elsif ($self->mock_mode eq 'RECORD') { + return $self->real_caller->send_request($service, $call_object); + } else { + die "Unsupported record mode " . $self->mock_mode; + } } else { - die "Unsupported record mode " . $self->mock_mode; + return $self->caller->send_request($service, $call_object); } }; sub caller_to_response { my ($self, $service, $call_object, $response) = @_; - my $content = $response->content; - my $headers = $response->headers; - - $content =~ s/<(RequestId)>.*<\/(RequestId)>/<$1>000000000000000000000000000000000000<\/$2>/ if (defined $content); - $content =~ s/<(RequestID)>.*<\/(RequestID)>/<$1>000000000000000000000000000000000000<\/$2>/ if (defined $content); - - if ($headers->{ "x-amzn-requestid" }) { - $headers->{ "x-amzn-requestid" } = '000000000000000000000000000000000000' - } - - if ($headers->{ "x-amz-request-id" }) { - $headers->{ "x-amz-request-id" } = '000000000000000000000000000000000000' - } - if ($self->mock_mode eq 'RECORD') { - my $file = path $self->_test_file; - $file->parent->mkpath; - - write_text($self->_test_file, encode_json({ - request => { - params => $service->to_hash($call_object), - service => $service->service, - call => $call_object->_api_call, - }, - response => { - content => $response->content, - headers => $response->headers, - status => $response->status, - } - })); - - my $result = $self->real_caller->caller_to_response($service, $call_object, $response); + if ($self->mock_type eq 'FileMockCaller') { + my $content = $response->content; + my $headers = $response->headers; + + $content =~ s/<(RequestId)>.*<\/(RequestId)>/<$1>000000000000000000000000000000000000<\/$2>/ if (defined $content); + $content =~ s/<(RequestID)>.*<\/(RequestID)>/<$1>000000000000000000000000000000000000<\/$2>/ if (defined $content); + + if ($headers->{ "x-amzn-requestid" }) { + $headers->{ "x-amzn-requestid" } = '000000000000000000000000000000000000' + } + + if ($headers->{ "x-amz-request-id" }) { + $headers->{ "x-amz-request-id" } = '000000000000000000000000000000000000' + } + + if ($self->mock_mode eq 'RECORD') { + my $file = path $self->_test_file; + $file->parent->mkpath; + + write_text($self->_test_file, encode_json({ + request => { + params => $service->to_hash($call_object), + service => $service->service, + call => $call_object->_api_call, + }, + response => { + content => $response->content, + headers => $response->headers, + status => $response->status, + } + })); + } + my $result = $self->caller->caller_to_response($service, $call_object, $response); $self->result_hook->($self, $result) if (defined $self->result_hook); return $result; } else { - return $self->real_caller->caller_to_response($service, $call_object, $response); + return $self->caller->caller_to_response($service, $call_object, $response); } }; diff --git a/lib/Paws/Net/NoResponseMockCaller.pm b/lib/Paws/Net/NoResponseMockCaller.pm new file mode 100644 index 0000000000..9f7cd16048 --- /dev/null +++ b/lib/Paws/Net/NoResponseMockCaller.pm @@ -0,0 +1,48 @@ +package Paws::Net::NoResponseMockCaller; + use Moose; + + with 'Paws::Net::RetryCallerRole', 'Paws::Net::CallerRole'; + + use Paws::Net::APIResponse; + use File::Slurper qw(read_text write_text); + use JSON::MaybeXS; + use Moose::Util::TypeConstraints; + use Path::Tiny; + + has file => (is => 'rw', isa => 'Str', trigger => \&_set_file); + + has real_caller => ( + is => 'ro', + does => 'Paws::Net::CallerRole', + default => sub { + require Paws::Net::Caller; + Paws::Net::Caller->new; + } + ); + + has actual_request => ( + is => 'rw', + isa => 'Object', + lazy => 1, + default => sub { '' }, + ); + + has _encoder => (is => 'ro', default => sub { JSON::MaybeXS->new(canonical => 1) }); + + sub send_request { + my ($self, $service, $call_object) = @_; + + $self->actual_request($service->prepare_request_for_call($call_object)); + my $actual_call = $self->_encoder->encode($service->to_hash($call_object)); + + # we don't care about the response + return {}; + }; + + sub caller_to_response { + my ($self, $service, $call_object, $response) = @_; + + return $response; + }; + +1; diff --git a/lib/Paws/Net/RestXmlCaller.pm b/lib/Paws/Net/RestXmlCaller.pm index 449c6baaa5..9d1f16ec9d 100755 --- a/lib/Paws/Net/RestXmlCaller.pm +++ b/lib/Paws/Net/RestXmlCaller.pm @@ -28,7 +28,7 @@ package Paws::Net::RestXmlCaller; my %p; foreach my $att (grep { $_ !~ m/^_/ } $params->meta->get_attribute_list) { - + # e.g. S3 metadata objects, which are passed in the header next if $params->meta->get_attribute($att)->does('Paws::API::Attribute::Trait::ParamInHeaders'); @@ -79,6 +79,14 @@ package Paws::Net::RestXmlCaller; { if ($attribute->does('Paws::API::Attribute::Trait::ParamInURI')) { my $att_name = $attribute->name; + my $non_print = join('', map { chr($_) } (128..255) ); + if ($call->$att_name =~ /[{^}`\[\]><#%'"~|\\$non_print]/) { + return Paws::Exception->throw( + message => "Found unacceptable content in $att_name parameter", + code => 'InvalidInput', + request_id => '', + ); + } if ($uri_attrib_is_greedy{$att_name}) { $vars->{ $attribute->uri_name } = uri_escape_utf8($call->$att_name, q[^A-Za-z0-9\-\._~/]); $uri_template =~ s{$att_name\+}{\+$att_name}g; @@ -120,7 +128,7 @@ package Paws::Net::RestXmlCaller; elsif ($attribute->does('Paws::API::Attribute::Trait::ParamInHeaders')) { my $map = $attribute->get_value($call)->Map; my $prefix = $attribute->header_prefix; - for my $header (keys %{$map}) { + for my $header (keys %{$map}) { my $header_name = $prefix . $header; $request->headers->header( $header_name => $map->{$header} ); } @@ -179,11 +187,11 @@ package Paws::Net::RestXmlCaller; my $xml = ''; foreach my $attribute ($call->meta->get_all_attributes) { - if ($attribute->has_value($call) and + if ($attribute->has_value($call) and not $attribute->does('Paws::API::Attribute::Trait::ParamInHeader') and not $attribute->does('Paws::API::Attribute::Trait::ParamInQuery') and not $attribute->does('Paws::API::Attribute::Trait::ParamInURI') and - not $attribute->does('Paws::API::Attribute::Trait::ParamInBody') and + not $attribute->does('Paws::API::Attribute::Trait::ParamInBody') and not $attribute->type_constraint eq 'Paws::S3::Metadata' ) { my $attribute_value = $attribute->get_value($call); diff --git a/t/glacier/content_headers.t b/t/glacier/content_headers.t new file mode 100644 index 0000000000..c8dfd94598 --- /dev/null +++ b/t/glacier/content_headers.t @@ -0,0 +1,235 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; +use if (!-e 'META.json'), lib => './auto-lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use URI::Escape; + +use Paws; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $idname = 'testid'; +my $vaultname = 'testvault'; +my $glacier = Paws->service('Glacier', region => 'us-west-2'); + +my %md5_methods = ( + 'AbortMultipartUpload' => { + AccountId => $idname, + UploadId => +'19gaRezEXAMPLES6Ry5YYdqthHOC_kGRCT03L9yetr220UmPtBYKk-OssZtLqyFu7sY1_lR7vgFuJV6NtcV5zpsJ', + VaultName => $vaultname, + }, + 'AbortVaultLock' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'AddTagsToVault' => { + Tags => { + Examplekey1 => 'examplevalue1', + Examplekey2 => 'examplevalue2' + }, + AccountId => $idname, + VaultName => $vaultname, + }, + 'CompleteMultipartUpload' => { + AccountId => $idname, + ArchiveSize => 3145728, + Checksum => + '9628195fcdbcbbe76cdde456d4646fa7de5f219fb39823836d81f0cc0e18aa67', + UploadId => +'19gaRezEXAMPLES6Ry5YYdqthHOC_kGRCT03L9yetr220UmPtBYKk-OssZtLqyFu7sY1_lR7vgFuJV6NtcV5zpsJ', + VaultName => $vaultname, + }, + 'CreateVault' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'DeleteArchive' => { + AccountId => $idname, + ArchiveId => 'testarchive', + VaultName => $vaultname, + }, + 'DeleteVault' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'DeleteVaultAccessPolicy' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'DeleteVaultNotifications' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'DescribeJob' => { + AccountId => $idname, + JobId => +'zbxcm3Z_3z5UkoroF7SuZKrxgGoDc3RloGduS7Eg-RO47Yc6FxsdGBgf_Q2DK5Ejh18CnTS5XW4_XqlNHS61dsO4Cn', + VaultName => $vaultname, + }, + 'DescribeVault' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'GetDataRetrievalPolicy' => { + AccountId => $idname, + }, + 'GetJobOutput' => { + AccountId => $idname, + JobId => +'zbxcm3Z_3z5UkoroF7SuZKrxgGoDc3RloGduS7Eg-RO47Yc6FxsdGBgf_Q2DK5Ejh18CnTS5XW4_XqlNHS61dsO4CnMW', + Range => '', + VaultName => $vaultname, + }, + 'GetVaultAccessPolicy' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'GetVaultLock' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'GetVaultNotifications' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'InitiateJob' => { + AccountId => $idname, + JobParameters => { + Description => 'My inventory job', + Format => 'CSV', + SNSTopic => +'arn:aws:sns:us-west-2:111111111111:Glacier-InventoryRetrieval-topic-Example', + Type => 'inventory-retrieval' + }, + VaultName => $vaultname, + }, + 'InitiateMultipartUpload' => { + AccountId => $idname, + PartSize => 1048576, + VaultName => $vaultname, + }, + 'InitiateMultipartUpload' => { + AccountId => $idname, + PartSize => 1048576, + VaultName => $vaultname, + }, + 'InitiateVaultLock' => { + AccountId => $idname, + Policy => { + Policy => +'{"Version":"2012-10-17","Statement":[{"Sid":"Define-vault-lock","Effect":"Deny","Principal":{"AWS":"arn:aws:iam::999999999999:root"},"Action":"glacier:DeleteArchive","Resource":"arn:aws:glacier:us-west-2:999999999999:vaults/examplevault","Condition":{"NumericLessThanEquals":{"glacier:ArchiveAgeinDays":"365"}}}]}' + }, + VaultName => $vaultname, + }, + 'ListJobs' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'ListMultipartUploads' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'ListParts' => { + AccountId => $idname, + UploadId => +'OW2fM5iVylEpFEMM9_HpKowRapC3vn5sSL39_396UW9zLFUWVrnRHaPjUJddQ5OxSHVXjYtrN47NBZ-khxOjyEXAMPLE', + VaultName => $vaultname, + }, + 'ListProvisionedCapacity' => { + AccountId => $idname, + }, + 'ListTagsForVault' => { + AccountId => $idname, + VaultName => $vaultname, + }, + 'ListVaults' => { + AccountId => $idname, + Limit => '', + Marker => '' + }, + 'PurchaseProvisionedCapacity' => { + AccountId => $idname, + }, + 'RemoveTagsFromVault' => { + TagKeys => [ 'examplekey1', 'examplekey2' ], + AccountId => $idname, + VaultName => $vaultname, + }, + 'SetDataRetrievalPolicy' => { + Policy => { + Rules => [ + { + BytesPerHour => 10737418240, + Strategy => 'BytesPerHour' + } + ] + }, + AccountId => $idname, + }, + 'SetVaultAccessPolicy' => { + AccountId => $idname, + Policy => { + Policy => +'{"Version":"2012-10-17","Statement":[{"Sid":"Define-owner-access-rights","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::999999999999:root"},"Action":"glacier:DeleteArchive","Resource":"arn:aws:glacier:us-west-2:999999999999:vaults/examplevault"}]}' + }, + VaultName => $vaultname, + }, + 'SetVaultNotifications' => { + AccountId => $idname, + VaultName => $vaultname, + VaultNotificationConfig => { + Events => + [ 'ArchiveRetrievalCompleted', 'InventoryRetrievalCompleted' ], + SNSTopic => 'arn:aws:sns:us-west-2:012345678901:mytopic' + } + }, + 'UploadArchive' => { + AccountId => $idname, + ArchiveDescription => 'testing', + Body => 'example-data-to-upload', + Checksum => 'beb0fe31a1c7ca8c6c04d574ea906e3f97b31fdca7571defb5b44dca89b5af60', + VaultName => $vaultname, + }, + 'UploadMultipartPart' => { + AccountId => $idname, + Body => 'part1', + Checksum => 'c06f7cd4baacb087002a99a5f48bf953', + Range => 'bytes 0-1048575/*', + UploadId => +'19gaRezEXAMPLES6Ry5YYdqthHOC_kGRCT03L9yetr220UmPtBYKk-OssZtLqyFu7sY1_lR7vgFuJV6NtcV5zpsJ', + VaultName => $vaultname, + }, +); + +# x-amz-glacier-version +foreach my $method (qw/AbortMultipartUpload AbortVaultLock AddTagsToVault CompleteMultipartUpload CreateVault DeleteArchive DeleteVault DeleteVaultAccessPolicy DeleteVaultNotifications DescribeJob DescribeVault GetDataRetrievalPolicy GetJobOutput GetVaultAccessPolicy GetVaultLock GetVaultNotifications InitiateJob InitiateMultipartUpload InitiateVaultLock ListJobs ListMultipartUploads ListParts ListProvisionedCapacity ListTagsForVault ListVaults PurchaseProvisionedCapacity RemoveTagsFromVault SetDataRetrievalPolicy SetVaultAccessPolicy SetVaultNotifications UploadArchive UploadMultipartPart/) { + my $response; + eval { $response = $glacier->$method(%{ $md5_methods{$method} }); + } or do { + diag qq[Error creating object: $@]; + }; + + TODO: { + local $TODO = "remove after fixing issue 260 object string errors."; + ## The HTTP headers should contain a x-amz-glacier-version header + if ($response) { + ok($response->header('x-amz-glacier-version'), "Glacier $method header contains x-amz-glacier-version header"); + } else { + fail("Header doesn't exist as it is undefined."); + } + }; +} + +done_testing; diff --git a/t/glacier/uploadid.t b/t/glacier/uploadid.t new file mode 100644 index 0000000000..34f70d20ab --- /dev/null +++ b/t/glacier/uploadid.t @@ -0,0 +1,43 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use URI::Escape; + +use Paws; +use Paws::Net::MockCaller; + +my $paws = Paws->new(config => { + caller => Paws::Net::MockCaller->new( + mock_dir => 't/glacier/uploadid', + mock_mode => 'REPLAY', + ), +# credentials => 'Test::CustomCredentials' +}); + +my $glacier = $paws->service('Glacier', region => 'us-west-2'); + +my $vault_output = $glacier->CreateVault( + AccountId => '-', + VaultName => 'testvault', + ); + +my $upload_output = $glacier->InitiateMultipartUpload( + AccountId => '-', + VaultName => 'testvault', + PartSize => 1048576, + ); + + +ok($upload_output->UploadId, 'Glacier InitiateMultipartUpload returned an uploadId'); + +done_testing; + diff --git a/t/glacier/uploadid/0001.response b/t/glacier/uploadid/0001.response new file mode 100644 index 0000000000..004d2b00ad --- /dev/null +++ b/t/glacier/uploadid/0001.response @@ -0,0 +1 @@ +{"request":{"params":{"AccountId":"-","VaultName":"testvault"},"call":"CreateVault","service":"glacier"},"response":{"headers":{"x-amzn-requestid":"000000000000000000000000000000000000","connection":"close","content-type":"application/json","location":"/807462239483/vaults/testvault","date":"Tue, 03 Jul 2018 11:51:57 GMT","content-length":"2"},"status":"201","content":"{}"}} \ No newline at end of file diff --git a/t/glacier/uploadid/0002.response b/t/glacier/uploadid/0002.response new file mode 100644 index 0000000000..e33a743261 --- /dev/null +++ b/t/glacier/uploadid/0002.response @@ -0,0 +1 @@ +{"request":{"params":{"AccountId":"-","VaultName":"testvault","PartSize":1048576},"call":"InitiateMultipartUpload","service":"glacier"},"response":{"headers":{"x-amzn-requestid":"000000000000000000000000000000000000","connection":"close","content-type":"application/json","location":"/807462239483/vaults/testvault/multipart-uploads/Gj3WZerNPY-WTZGWd9hXhe16g2Vg66sAz7xid7kX0HllvTdli_PsUbQrHwhKUTLBrTi3qBR8e94IibTZgqnTJAd-CnbM","date":"Tue, 03 Jul 2018 11:51:58 GMT","content-length":"2","x-amz-multipart-upload-id":"Gj3WZerNPY-WTZGWd9hXhe16g2Vg66sAz7xid7kX0HllvTdli_PsUbQrHwhKUTLBrTi3qBR8e94IibTZgqnTJAd-CnbM"},"status":"201","content":"{}"}} \ No newline at end of file diff --git a/t/route53/uri_generation.t b/t/route53/uri_generation.t new file mode 100644 index 0000000000..b2314e2775 --- /dev/null +++ b/t/route53/uri_generation.t @@ -0,0 +1,76 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; +use if (!-e 'META.json'), lib => './auto-lib'; + +use English qw(-no-match-vars); +use Data::Dumper; +use Carp; +use Test::More; + +use Paws; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $route53 = Paws->service('Route53', region => 'us-west-2'); + +my %uri_methods = ( + ChangeTagsForResource => { + ResourceId => 'SomeId', + ResourceType => 'hostedzone', + AddTags => [ + { + Key => 'SomeTag', + Value => '100', + } + ], + RemoveTagKeys => ['SomeOtherTag', 'TestTag'], + }, + # Does not use locationName override for URI location with HostedZoneId + CreateQueryLoggingConfig => { + CloudWatchLogsLogGroupArn => 'arn:aws:logs:us-west-2:111111111111:log-group:/aws/route53/example.com', + HostedZoneId => 'SomeId', + }, + # Does use locationName override for URI location with HostedZoneId + ListResourceRecordSets => { + HostedZoneId => 'SomeId', + MaxItems => '1', + }, + # Uses a given ResourceID for Hostedzone + ListTagsForResource => { + ResourceId => 'SomeId', + ResourceType => 'hostedzone', + }, + ); + +my %uri_expected = ( + ChangeTagsForResource => '/2013-04-01/tags/hostedzone/SomeId', + ListTagsForResource => '/2013-04-01/tags/hostedzone/SomeId', + CreateQueryLoggingConfig => '/2013-04-01/queryloggingconfig', + ListResourceRecordSets => '/2013-04-01/hostedzone/SomeId/rrset?maxitems=1', +); + +foreach my $method (qw/ChangeTagsForResource CreateQueryLoggingConfig ListResourceRecordSets ListTagsForResource/) { + my $request; + eval { + $request = $route53->$method( %{ $uri_methods{$method}} ); + } or do { + warn qq[Error calling method: $@]; + }; + + TODO: { + local $TODO = 'Remove when the XML creation has been fixed'; + # check the uri matches using the examples given in the AWS API docs for the method + if ($request) { + is($request->uri, $uri_expected{$method}, "Route53 $method uri matches expected behaviour."); + } else { + fail("Request for $method is undefined.") + } + }; +} +done_testing; diff --git a/t/route53/xml_creation.t b/t/route53/xml_creation.t new file mode 100644 index 0000000000..340d0450c0 --- /dev/null +++ b/t/route53/xml_creation.t @@ -0,0 +1,92 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; +use if (!-e 'META.json'), lib => './auto-lib'; + +use English qw(-no-match-vars); +use Data::Dumper; +use Carp; +use Test::More; + +use Paws; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $route53 = Paws->service('Route53', region => 'us-west-2'); + +my %xml_methods = ( + ChangeResourceRecordSets => { + HostedZoneId => 'SomeId', + ChangeBatch => { + Changes => [ + { + Action => 'CREATE', + ResourceRecordSet => { + Name => 'MyResourceSet', + Type => 'A', + TTL => 200, + ResourceRecords => [ + { + Value => '127.0.0.1', + }, + ], + }, + } + ], + } + }, + ChangeTagsForResource => { + ResourceId => 'SomeId', + ResourceType => 'hostedzone', + AddTags => [ + { + Key => 'Cost Center', + Value => '80432', + } + ], + RemoveTagKeys => ['Owner'], + }, + CreateHostedZone => { + CallerReference => 'MyThing', + Name => 'MyDNSName', + VPC => { + VPCId => 'Something', + VPCRegion => 'us-west-2', + }, + }, + CreateQueryLoggingConfig => { + CloudWatchLogsLogGroupArn => 'MyCloudWatchLogsLogGroupArn', + HostedZoneId => 'myZoneId', + }, + ); + +my %xml_results = ( + ChangeResourceRecordSets => 'CREATEA200127.0.0.1MyResourceSet', + ChangeTagsForResource => 'OwnerCost Center80432', + CreateHostedZone => 'MyThingMyDNSNameSomethingus-west-2', + CreateQueryLoggingConfig => 'MyCloudWatchLogsLogGroupArnmyZoneId', +); + +foreach my $method (qw/ChangeResourceRecordSets ChangeTagsForResource CreateHostedZone CreateQueryLoggingConfig/) { + my $request; + eval { + $request = $route53->$method( %{ $xml_methods{$method}} ); + } or do { + warn qq[Error calling method: $@]; + }; + + TODO: { + local $TODO = 'Remove when the XML creation has been fixed'; + if ($request) { + is($request->content, $xml_results{$method}, "$method XML is ok"); + } else { + fail("Request for $method is undefined.") + } + }; +} +done_testing; diff --git a/t/s3/content_headers.t b/t/s3/content_headers.t new file mode 100644 index 0000000000..bb4632a1e0 --- /dev/null +++ b/t/s3/content_headers.t @@ -0,0 +1,238 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use URI::Escape; + +use Paws; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $bucketname = 'shadowcatjesstest'; +my $s3 = Paws->service('S3', region => 'us-west-2'); + +my %md5_methods = ( + 'PutBucketCors' => { + Bucket => $bucketname, + CORSConfiguration => { + CORSRules => [ + { + 'AllowedMethods' => ['POST'], + 'AllowedOrigins' => ['http://shadowcat.co.uk'], + }, + ], + } + }, + 'PutBucketLifecycle' => { + Bucket => $bucketname, + LifecycleConfiguration => { + Rules => [ + { + Status => 'Enabled', + Filter => { + }, + } + ] + }, + }, + 'PutBucketTagging' => { + Bucket => $bucketname, + Tagging => { + TagSet => [ + { + Key => 'Foo', + Value => 'Bar', + }, + ], + }, + }, + 'DeleteObjects' => { + Bucket => $bucketname, + Delete => { + Objects => [ + { + Key => 'Foo', + }, + ], + }, + }, + 'RestoreObject' => { + Bucket => $bucketname, + Key => 'Foo', + }, + 'PutBucketReplication' => { + Bucket => $bucketname, + ReplicationConfiguration => { + Rules => [ + { + Status => 'Enabled', + Prefix => 'MyPrefix', + Destination => { + Bucket => 'MyBucketName', + }, + }, + ], + Role => 'MyRole', + }, + }, + 'PutObjectTagging' => { + Bucket => $bucketname, + Key => 'SomeKey', + Tagging => { + TagSet => [ + { + Key => 'Foo', + Value => 'Bar', + }, + ], + }, + }, + 'CreateBucket' => { + Bucket => $bucketname, + CreateBucketConfiguration => { + LocationConstraint => 'EU', + }, + }, + 'PutBucketAccelerateConfiguration' => { + Bucket => $bucketname, + AccelerateConfiguration => { + Status => 'Enabled', # values: Enabled, Suspended; OPTIONAL + }, + }, + 'PutBucketAcl' => { + Bucket => $bucketname, + ACL => 'private', + }, + 'PutBucketEncryption' => { + Bucket => $bucketname, + ServerSideEncryptionConfiguration => { + Rules => [ + { + }, + ], + }, + }, + 'PutBucketInventoryConfiguration' => { + Bucket => $bucketname, + Id => 'MyInventoryId', + InventoryConfiguration => { + Destination => { + S3BucketDestination => { + Bucket => 'Stuff', + Format => 'CSV', + }, + }, + Id => 'SomeId', + IncludedObjectVersions => 'All', + IsEnabled => 1, + Schedule => { + Frequency => 'Daily', + }, + }, + }, + 'PutBucketLogging' => { + Bucket => $bucketname, + BucketLoggingStatus => { + }, + }, + 'PutBucketMetricsConfiguration' => { + Bucket => $bucketname, + Id => 'MyId', + MetricsConfiguration => { + Id => 'OtherId', + }, + }, + 'PutBucketNotification' => { + Bucket => $bucketname, + NotificationConfiguration => { + }, + }, + 'PutBucketPolicy' => { + Bucket => $bucketname, + Policy => 'SomePolicy', + }, + 'PutBucketRequestPayment' => { + Bucket => $bucketname, + RequestPaymentConfiguration => { + Payer => 'Requester', + }, + }, + 'PutBucketVersioning' => { + Bucket => $bucketname, + VersioningConfiguration => { + MFADelete => 'Enabled', + Status => 'Enabled', + }, + }, + 'PutBucketWebsite' => { + Bucket => $bucketname, + WebsiteConfiguration => { + }, + }, + 'PutObject' => { + Bucket => $bucketname, + Key => 'MyKey', + }, + 'PutObjectTagging' => { + Bucket => $bucketname, + Key => 'MyKey', + Tagging => { + TagSet => [ + { + Key => 'SomeKey', + Value => 'Val', + } + ], + }, + }, + 'UploadPart' => { + Bucket => $bucketname, + Key => 'MyKey', + PartNumber => 1, + UploadId => 'MyMultipartUploadId', + } + ); + +# content md5 +foreach my $method (qw/DeleteObjects RestoreObject PutBucketLifecycle PutBucketTagging PutBucketCors PutObjectTagging PutBucketReplication/) { + my $response; + eval { $response = $s3->$method(%{ $md5_methods{$method} }); + } or do { + warn qq[Error creating object: $@]; + }; + + TODO: { + local $TODO = 'Remove after fixing content-md5 headers'; + ## The HTTP headers should contain a Content-MD5 header + ok($response->header('Content-MD5'), "S3 $method header contains Content-MD5 header"); + }; +} + +# content length: Length of the message (without the headers) +# according to RFC 2616. This header is required for PUTs and +# operations that load XML, such as logging and ACLs. +foreach my $method (qw/CreateBucket PutBucketAccelerateConfiguration PutBucketAcl PutBucketEncryption PutBucketInventoryConfiguration PutBucketLogging PutBucketMetricsConfiguration PutBucketNotification PutBucketPolicy PutBucketReplication PutBucketRequestPayment PutBucketVersioning PutBucketWebsite PutBucketLifecycle PutBucketTagging PutBucketCors PutObject PutObjectTagging DeleteObjects UploadPart/) { + my $response; + eval { $response = $s3->$method(%{ $md5_methods{$method} }); + } or do { + diag qq[Error creating object: $@]; + }; + + TODO: { + local $TODO = 'Remove after fixing content-length headers'; + ## The HTTP headers should contain a Content-MD5 header + ok($response->header('Content-Length'), "S3 $method header contains Content-Length header"); + }; +} + +done_testing; diff --git a/t/s3/content_headers/0001.response b/t/s3/content_headers/0001.response new file mode 100644 index 0000000000..66db24d929 --- /dev/null +++ b/t/s3/content_headers/0001.response @@ -0,0 +1 @@ +{"request":{"params":{"Delete":{"Objects":[{"Key":"Foo"}]},"Bucket":"shadowcatjesstest"},"call":"DeleteObjects","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","date":"Mon, 18 Jun 2018 11:55:21 GMT","transfer-encoding":"chunked","x-amz-id-2":"XahISykeV0kmpHfJcEQmmp18441+QJ+7OIwmpsfM+kX71qwQCfrFzizsjJz717SYRSct3llN+JM=","server":"AmazonS3"},"status":"200","content":""}} \ No newline at end of file diff --git a/t/s3/content_headers/0002.response b/t/s3/content_headers/0002.response new file mode 100644 index 0000000000..e8813d3ed8 --- /dev/null +++ b/t/s3/content_headers/0002.response @@ -0,0 +1 @@ +{"request":{"params":{"Key":"Foo","Bucket":"shadowcatjesstest"},"call":"RestoreObject","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","date":"Mon, 18 Jun 2018 11:55:21 GMT","transfer-encoding":"chunked","x-amz-id-2":"XahISykeV0kmpHfJcEQmmp18441+QJ+7OIwmpsfM+kX71qwQCfrFzizsjJz717SYRSct3llN+JM=","server":"AmazonS3"},"status":"200","content":""}} \ No newline at end of file diff --git a/t/s3/content_headers/0003.response b/t/s3/content_headers/0003.response new file mode 100644 index 0000000000..e64d450e64 --- /dev/null +++ b/t/s3/content_headers/0003.response @@ -0,0 +1 @@ +{"request":{"params":{"LifecycleConfiguration":{"Rules":[{"Status":"Enabled","Filter":{}}]},"Bucket":"shadowcatjesstest"},"call":"PutBucketLifecycle","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","date":"Mon, 18 Jun 2018 11:55:21 GMT","transfer-encoding":"chunked","x-amz-id-2":"XahISykeV0kmpHfJcEQmmp18441+QJ+7OIwmpsfM+kX71qwQCfrFzizsjJz717SYRSct3llN+JM=","server":"AmazonS3"},"status":"200","content":""}} \ No newline at end of file diff --git a/t/s3/content_headers/0004.response b/t/s3/content_headers/0004.response new file mode 100644 index 0000000000..109b83b89a --- /dev/null +++ b/t/s3/content_headers/0004.response @@ -0,0 +1 @@ +{"request":{"params":{"Tagging":{"TagSet":[{"Key":"Foo","Value":"Bar"}]},"Bucket":"shadowcatjesstest"},"call":"PutBucketTagging","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","date":"Mon, 18 Jun 2018 11:55:21 GMT","transfer-encoding":"chunked","x-amz-id-2":"XahISykeV0kmpHfJcEQmmp18441+QJ+7OIwmpsfM+kX71qwQCfrFzizsjJz717SYRSct3llN+JM=","server":"AmazonS3"},"status":"200","content":""}} \ No newline at end of file diff --git a/t/s3/content_headers/0005.response b/t/s3/content_headers/0005.response new file mode 100644 index 0000000000..58d873e1bd --- /dev/null +++ b/t/s3/content_headers/0005.response @@ -0,0 +1 @@ +{"request":{"params":{"CORSConfiguration":{"CORSRules":[{"AllowedMethods":["POST"],"AllowedOrigins":["http://shadowcat.co.uk"]}]},"Bucket":"shadowcatjesstest"},"call":"PutBucketCors","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","date":"Mon, 18 Jun 2018 11:55:21 GMT","transfer-encoding":"chunked","x-amz-id-2":"XahISykeV0kmpHfJcEQmmp18441+QJ+7OIwmpsfM+kX71qwQCfrFzizsjJz717SYRSct3llN+JM=","server":"AmazonS3"},"status":"200","content":""}} \ No newline at end of file diff --git a/t/s3/multipartupload.t b/t/s3/multipartupload.t new file mode 100644 index 0000000000..f8c11b462a --- /dev/null +++ b/t/s3/multipartupload.t @@ -0,0 +1,83 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use Test::Exception; +use URI::Escape; + +use Paws; +use Paws::Net::MockCaller; + +my $paws = Paws->new(config => { + caller => Paws::Net::MockCaller->new( + mock_dir => 't/s3/multipartupload', + mock_mode => 'REPLAY', + ), +# credentials => 'Test::CustomCredentials' +}); + +my $s3 = $paws->service('S3', region => 'us-west-2'); #, endpoint => 'http://localhost:4572'); + +my $bucketname = 'shadowcatjesstest'; +my $key = 'testkey'; +my $upload_output = $s3->CreateMultipartUpload( + Bucket => $bucketname, + Key => $key, + ); + +ok($upload_output->UploadId, 'S3 CreateMultipartUpload returned an UploadId'); + +TODO: { + $TODO = 'May fail if S3 CreateMultipartUpload fails'; + my $part_output = $s3->UploadPart( + Bucket => $bucketname, + Key => $key, + UploadId => $upload_output->UploadId, + PartNumber => 1, + Body => 'X' x 1000, + ); + ok($part_output->ETag, 'S3 UploadPart returns an ETag'); + + my $parts = $s3->ListParts( + Bucket => $bucketname, + Key => $key, + UploadId => $upload_output->UploadId, + ); + + ok(@{ $parts->Parts }, 'S3 ListParts returns at least one Part'); + ok($parts->Parts->[0]->Size, 'S3 ListParts Part has a size'); + is($parts->Parts->[0]->PartNumber, 1, 'S3 ListParts Part has PartNumber 1'); + + # Can't complete an upload without its MultipartUpload data + dies_ok(sub { $s3->CompleteMultipartUpload( + Bucket => $bucketname, + Key => 'testkey', + UploadId => $upload_output->UploadId, + ); + }, 'S3 CompleteMultipartUpload fails with no multipart data'); + my $complete_output = $s3->CompleteMultipartUpload( + Bucket => $bucketname, + Key => 'testkey', + UploadId => $upload_output->UploadId, + MultipartUpload => { + Parts => [ + { + ETag => $parts->Parts->[0]->ETag, + PartNumber => 1, + }, + ], + }, + ); + ok($complete_output->Location, 'S3 CompleteMultipartUpload returns a Location'); +} + +done_testing; + diff --git a/t/s3/multipartupload/0001.response b/t/s3/multipartupload/0001.response new file mode 100644 index 0000000000..1f6a960ef3 --- /dev/null +++ b/t/s3/multipartupload/0001.response @@ -0,0 +1 @@ +{"request":{"params":{"Bucket":"shadowcatjesstest","Key":"testkey"},"call":"CreateMultipartUpload","service":"s3"},"response":{"headers":{"connection":"close","x-amz-request-id":"000000000000000000000000000000000000","transfer-encoding":"chunked","date":"Wed, 04 Jul 2018 09:48:44 GMT","x-amz-id-2":"d1lqIGbAszh8dYxmaTPw1XOFlY39cmJXzfqBwdvku/4tVXPsxt3TJJNGHe+yq4NPrixxWCvaJVs=","server":"AmazonS3"},"status":"200","content":"\nshadowcatjesstesttestkey855NVKDaxeyRm4ECLX1OWN_Xvj_aEoO.ACQE4FaTk_wiy1adbg9s96c5s2j5bDl_XR_JwVS960qDKY4ueO0j34k0c6za7CXbocLQkLAvGCYH_wmcTVVbFykhizt1xIZ1"}} \ No newline at end of file diff --git a/t/s3/multipartupload/0002.response b/t/s3/multipartupload/0002.response new file mode 100644 index 0000000000..16ca1cec8d --- /dev/null +++ b/t/s3/multipartupload/0002.response @@ -0,0 +1 @@ +{"request":{"params":{"PartNumber":1,"Bucket":"shadowcatjesstest","Key":"testkey","UploadId":"855NVKDaxeyRm4ECLX1OWN_Xvj_aEoO.ACQE4FaTk_wiy1adbg9s96c5s2j5bDl_XR_JwVS960qDKY4ueO0j34k0c6za7CXbocLQkLAvGCYH_wmcTVVbFykhizt1xIZ1","Body":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"},"call":"UploadPart","service":"s3"},"response":{"headers":{"etag":"\"6c35f0c7885fd1218ac65e1d93b2ee60\"","connection":"close","x-amz-request-id":"000000000000000000000000000000000000","date":"Wed, 04 Jul 2018 09:48:45 GMT","x-amz-id-2":"3YcFktVS9FmKOXEt5yHTrghvIHedKzxdWuw7Nql4JoO33i8NJ4vKV3Nb6eancu4+TSHms1phKNA=","server":"AmazonS3","content-length":"0"},"status":"200","content":""}} \ No newline at end of file diff --git a/t/s3/multipartupload/0003.response b/t/s3/multipartupload/0003.response new file mode 100644 index 0000000000..0e336fec96 --- /dev/null +++ b/t/s3/multipartupload/0003.response @@ -0,0 +1 @@ +{"request":{"params":{"Bucket":"shadowcatjesstest","Key":"testkey","UploadId":"855NVKDaxeyRm4ECLX1OWN_Xvj_aEoO.ACQE4FaTk_wiy1adbg9s96c5s2j5bDl_XR_JwVS960qDKY4ueO0j34k0c6za7CXbocLQkLAvGCYH_wmcTVVbFykhizt1xIZ1"},"call":"ListParts","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","transfer-encoding":"chunked","date":"Wed, 04 Jul 2018 09:48:46 GMT","x-amz-id-2":"yvlLXVl1NA0AqVFSX0bIwHLADaVCe3uQBNatHMZx8jG8RGpib2Oi3JHHIy592coaYryE8YRk1yw=","server":"AmazonS3"},"status":"200","content":"\nshadowcatjesstesttestkey855NVKDaxeyRm4ECLX1OWN_Xvj_aEoO.ACQE4FaTk_wiy1adbg9s96c5s2j5bDl_XR_JwVS960qDKY4ueO0j34k0c6za7CXbocLQkLAvGCYH_wmcTVVbFykhizt1xIZ1arn:aws:iam::807462239483:user/castawaycastaway8ff159e3d15b626c341a0232f4ceb32bd6a9210ff78bfe62de20c4e686a0346camazonSTANDARD011000false12018-07-04T09:48:45.000Z"6c35f0c7885fd1218ac65e1d93b2ee60"1000"}} \ No newline at end of file diff --git a/t/s3/multipartupload/0004.response b/t/s3/multipartupload/0004.response new file mode 100644 index 0000000000..d645a226eb --- /dev/null +++ b/t/s3/multipartupload/0004.response @@ -0,0 +1 @@ +{"request":{"params":{"Bucket":"shadowcatjesstest","Key":"testkey","UploadId":"855NVKDaxeyRm4ECLX1OWN_Xvj_aEoO.ACQE4FaTk_wiy1adbg9s96c5s2j5bDl_XR_JwVS960qDKY4ueO0j34k0c6za7CXbocLQkLAvGCYH_wmcTVVbFykhizt1xIZ1"},"call":"CompleteMultipartUpload","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","date":"Wed, 04 Jul 2018 09:48:46 GMT","transfer-encoding":"chunked","x-amz-id-2":"n5AJ5LdJB+6ZJh8Z7r136Mvb80nFGEUHNPwkX0bX1xLd2fdyCRshsROnoNvQM5B0f595K1HYb/8=","server":"AmazonS3"},"status":"400","content":"InvalidRequestYou must specify at least one part9F0E85377C9B1FEAn5AJ5LdJB+6ZJh8Z7r136Mvb80nFGEUHNPwkX0bX1xLd2fdyCRshsROnoNvQM5B0f595K1HYb/8="}} \ No newline at end of file diff --git a/t/s3/multipartupload/0005.response b/t/s3/multipartupload/0005.response new file mode 100644 index 0000000000..c06cc26cb7 --- /dev/null +++ b/t/s3/multipartupload/0005.response @@ -0,0 +1 @@ +{"request":{"params":{"Bucket":"shadowcatjesstest","MultipartUpload":{"Parts":[{"PartNumber":1,"ETag":"\"6c35f0c7885fd1218ac65e1d93b2ee60\""}]},"Key":"testkey","UploadId":"855NVKDaxeyRm4ECLX1OWN_Xvj_aEoO.ACQE4FaTk_wiy1adbg9s96c5s2j5bDl_XR_JwVS960qDKY4ueO0j34k0c6za7CXbocLQkLAvGCYH_wmcTVVbFykhizt1xIZ1"},"call":"CompleteMultipartUpload","service":"s3"},"response":{"headers":{"connection":"close","content-type":"application/xml","x-amz-request-id":"000000000000000000000000000000000000","transfer-encoding":"chunked","date":"Wed, 04 Jul 2018 09:48:48 GMT","x-amz-id-2":"lvNAq0mImYEAbSGIeRW+HPCtaL4BMx9Omp7hZJTewUss3mCyQp8nTP8YWvrnml0Hnd5ftYSQWHo=","server":"AmazonS3"},"status":"200","content":"\n\nhttps://s3-us-west-2.amazonaws.com/shadowcatjesstest/testkeyshadowcatjesstesttestkey"61a2e3a5edeb8059a0ecc4970e82d09c-1""}} \ No newline at end of file diff --git a/t/s3/prefix.t b/t/s3/prefix.t new file mode 100644 index 0000000000..75dd9c123c --- /dev/null +++ b/t/s3/prefix.t @@ -0,0 +1,54 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use URI::Escape; + +use Paws; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $bucketname = 'shadowcatjesstest'; +my $s3 = Paws->service('S3', region => 'us-west-2'); + +my %prefix_methods = ( + ListObjectVersions => { + Bucket => $bucketname, + Prefix => 'TestPrefix', + }, + ListObjects => { + Bucket => $bucketname, + Prefix => 'TestPrefix', + }, + ); + +my %prefix_results = ( + ListObjectVersions => 'https://s3-us-west-2.amazonaws.com/shadowcatjesstest?prefix=TestPrefix', + ListObjects => 'https://s3-us-west-2.amazonaws.com/shadowcatjesstest?prefix=TestPrefix', + ); + +foreach my $method (qw/ListObjectVersions ListObjects/) { + my $response; + eval { $response = $s3->$method(%{ $prefix_methods{$method} }); + } or do { + warn qq[Error creating object: $@]; + }; + + TODO: { + local $TODO = 'Remove after fixing/verifying prefix'; + ## The URI Query params should contain a Prefix param + is($response->url, $prefix_results{$method}, "S3 $method URI contains Prefix"); + }; +} + +done_testing; diff --git a/t/s3/uri_avoid_chars.t b/t/s3/uri_avoid_chars.t new file mode 100644 index 0000000000..fbe87a6b1c --- /dev/null +++ b/t/s3/uri_avoid_chars.t @@ -0,0 +1,54 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use Test::Exception; +use URI::Escape; + +use Paws; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $bucketname = 'test-uri-paws'; +my $s3 = Paws->service('S3', region => 'us-west-2'); + +my @to_encode = ('\\', + '{', + '^', + '}', + '`', + ']', + '>', + '[', + '<', + '#', + '%', + q{'}, + q{"}, + '~', + '|', + (map { chr($_) } (128..255)), + ); + +foreach my $char (@to_encode) { + my $response; + + dies_ok { $response = $s3->PutObject( + "Key" => "test$char", + "Bucket" => $bucketname, + "Body" => 'Blub', + ); + }; +} + +done_testing; diff --git a/t/s3/uri_encoding.t b/t/s3/uri_encoding.t new file mode 100644 index 0000000000..ea75107fb0 --- /dev/null +++ b/t/s3/uri_encoding.t @@ -0,0 +1,68 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use URI::Escape; + +use Paws; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $bucketname = 'test-uri-paws'; +my $s3 = Paws->service('S3', region => 'us-west-2'); + +# https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html +my @to_encode = ('&', + '$', + '@', + '=', + ':', + ';', + '+', + ',', + '?', + ' ', + (map { chr($_) } (0..31)), + chr(127), + ); + +foreach my $char (@to_encode) { + my $response; + eval { $response = $s3->PutObject( + "Key" => "test$char", + "Bucket" => $bucketname, + "Body" => 'Blub', + ); + } or do { + diag qq[Error creating object: $@]; + }; + +## The URI should contain a once-encoded character: + is($response->url, 'https://s3-us-west-2.amazonaws.com/test-uri-paws/test' . uri_escape($char), "S3 uri char " . ord($char) . " encoded correctly"); +} + +# Do NOT encode the "/" in a Key name: +my $response; +eval { $response = $s3->PutObject( + "Key" => "test/foo/bar", + "Bucket" => "$bucketname", + "Body" => 'Blub', + ); +} or do { + diag qq[Error creating object: $@]; +}; + +## The URI should contain a once-encoded character: +is($response->url, 'https://s3-us-west-2.amazonaws.com/test-uri-paws/test/foo/bar', "S3 uri left / unencoded in Key correctly"); + +done_testing; diff --git a/t/s3/uri_other_chars.t b/t/s3/uri_other_chars.t new file mode 100644 index 0000000000..eb94cc66a4 --- /dev/null +++ b/t/s3/uri_other_chars.t @@ -0,0 +1,44 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use lib 't/lib'; + +use English qw(-no-match-vars); +use Data::Printer; +use Data::Dumper; +use Carp; +use Test::More; +use URI::Escape; + +use Paws; +#use Paws::Net::MockCaller; +use TestRequestCaller; + +Paws->default_config->caller(TestRequestCaller->new); +Paws->default_config->credentials('Test::CustomCredentials'); + +my $bucketname = 'test-uri-paws'; +my $s3 = Paws->service('S3', region => 'us-west-2'); + +my @to_encode = ("\0243", # GBP currency sign + "\0251", # copyright symbol + ); + +foreach my $char (@to_encode) { + my $response; + eval { $response = $s3->PutObject( + "Key" => "test$char", + "Bucket" => $bucketname, + "Body" => 'Blub', + ); + } or do { + diag qq[Error creating object: $@]; + }; + +## The URI should contain a once-encoded character: + is($response->url, 'https://s3-us-west-2.amazonaws.com/test-uri-paws/test' . uri_escape($char), "S3 uri encoded correctly"); +} + +done_testing;