Amazon-S3-Lite
view release on metacpan or search on metacpan
t/01-s3-lite.t view on Meta::CPAN
return 0 if $@ || !$res;
# Verify it's actually LocalStack by checking the response body
return 0 if $res->{status} != 200;
my $status = JSON::PP->new->decode( $res->{content} );
return $status->{services}{s3} =~ /^(?:available|running)$/xsm;
}
sub new_s3 {
return Amazon::S3::Lite->new(
{ region => 'us-east-1',
aws_access_key_id => 'test',
aws_secret_access_key => 'test',
@_,
}
);
}
sub new_localstack_s3 {
return new_s3(
host => 'localhost:4566',
secure => 0,
);
}
# Returns a mock _request sub that captures calls and returns a canned response
sub mock_request {
my (%args) = @_;
my $status = $args{status} // 200;
my $content = $args{content} // '';
my $headers = $args{headers} // {};
my $captured = $args{capture};
return sub {
my ( $self, $method, $url, $req_headers, $body, $extra, $region ) = @_;
if ($captured) {
$$captured = {
method => $method,
url => $url,
headers => $req_headers,
body => $body,
region => $region,
};
}
return {
status => $status,
reason => 'OK',
headers => $headers,
content => $content,
};
};
}
########################################################################
# Unit tests â no network required
########################################################################
subtest 'constructor' => sub {
# no credentials â stub _init_credentials so the test is immune to whether
# Amazon::Credentials is installed or finds real creds (e.g. on an EC2 instance)
{
local $ENV{AWS_ACCESS_KEY_ID} = undef;
local $ENV{AWS_SECRET_ACCESS_KEY} = undef;
no warnings 'redefine';
local *Amazon::S3::Lite::_init_credentials = sub {
my ( $self, $args ) = @_;
Carp::croak 'No AWS credentials found.'
if !$args->{credentials}
&& !$args->{aws_access_key_id}
&& !$ENV{AWS_ACCESS_KEY_ID};
};
eval { Amazon::S3::Lite->new( { region => 'us-east-1' } ) };
like $@, qr/No AWS credentials/, 'croaks without credentials';
}
# explicit credentials
my $s3 = new_s3();
isa_ok $s3, 'Amazon::S3::Lite';
is $s3->region, 'us-east-1', 'region set';
is $s3->host, 's3.amazonaws.com', 'default host';
# env credentials
{
local $ENV{AWS_ACCESS_KEY_ID} = 'envkey';
local $ENV{AWS_SECRET_ACCESS_KEY} = 'envsecret';
local $ENV{AWS_SESSION_TOKEN} = 'envtoken';
my $s3e = Amazon::S3::Lite->new( { region => 'us-east-1' } );
is $s3e->credentials->aws_access_key_id, 'envkey', 'env key';
is $s3e->credentials->token, 'envtoken', 'env token';
}
# duck-typed credentials object
{
package MyCreds;
sub new { bless {}, shift }
sub aws_access_key_id {'duckkey'}
sub aws_secret_access_key {'ducksecret'}
sub token {undef}
package main;
my $s3d = Amazon::S3::Lite->new(
{ region => 'us-east-1',
credentials => MyCreds->new,
}
);
is $s3d->credentials->aws_access_key_id, 'duckkey', 'duck-type creds';
}
# bad credentials object
{
package BadCreds;
sub new { bless {}, shift }
sub aws_access_key_id {'key'}
package main;
eval { Amazon::S3::Lite->new( { region => 'us-east-1', credentials => BadCreds->new } ) };
like $@, qr/must implement aws_secret_access_key/, 'bad creds object croaks';
}
# custom logger
{
my $warned = 0;
my $logger = bless {}, 'MyLogger';
{
no strict 'refs';
for my $m (qw(trace debug info error)) {
*{"MyLogger::$m"} = sub { };
}
*{"MyLogger::warn"} = sub { $warned++ };
}
my $s3l = new_s3( logger => $logger );
isa_ok $s3l->logger, 'MyLogger', 'custom logger accepted';
}
};
subtest '_endpoint' => sub {
my $s3 = new_s3();
is $s3->_endpoint, 'https://s3.amazonaws.com', 'root endpoint';
is $s3->_endpoint('my-bucket'), 'https://s3.amazonaws.com/my-bucket', 'bucket endpoint';
is $s3->_endpoint( 'my-bucket', 'path/to/key.txt' ),
'https://s3.amazonaws.com/my-bucket/path/to/key.txt',
'bucket+key endpoint';
is $s3->_endpoint( 'my-bucket', 'path/to/my file+thing.txt' ),
'https://s3.amazonaws.com/my-bucket/path/to/my%20file%2Bthing.txt',
'key encoding preserves slashes, encodes special chars';
};
subtest 'list_buckets' => sub {
my $s3 = new_s3( region => 'eu-west-1' );
my $captured = {};
my $xml = <<'XML';
<?xml version="1.0" encoding="UTF-8"?>
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Owner><ID>owner123</ID><DisplayName>rob</DisplayName></Owner>
<Buckets>
<Bucket><Name>bucket-a</Name><CreationDate>2024-01-01T00:00:00.000Z</CreationDate></Bucket>
<Bucket><Name>bucket-b</Name><CreationDate>2024-06-01T00:00:00.000Z</CreationDate></Bucket>
</Buckets>
</ListAllMyBucketsResult>
XML
no warnings 'redefine';
local *Amazon::S3::Lite::_request = mock_request(
content => $xml,
capture => \$captured,
);
my $r = $s3->list_buckets;
is $captured->{method}, 'GET', 'method is GET';
is $captured->{url}, 'https://s3.amazonaws.com/', 'hits root endpoint';
is $captured->{region}, 'us-east-1', 'always signs with us-east-1';
is $s3->region, 'eu-west-1', 'object region unchanged';
is $r->{owner_id}, 'owner123', 'owner_id';
is $r->{owner_name}, 'rob', 'owner_name';
( run in 0.553 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )