File Coverage

blib/lib/Shared/Examples/Net/Amazon/S3.pm
Criterion Covered Total %
statement 215 227 94.7
branch 35 50 70.0
condition 15 29 51.7
subroutine 65 67 97.0
pod 0 42 0.0
total 330 415 79.5


line stmt bran cond sub pod time code
1             package Shared::Examples::Net::Amazon::S3;
2             # ABSTRACT: used for testing and as example
3             $Shared::Examples::Net::Amazon::S3::VERSION = '0.99';
4 90     90   6427342 use strict;
  90         538  
  90         2892  
5 90     90   520 use warnings;
  90         214  
  90         2756  
6              
7 90     90   553 use parent qw[ Exporter::Tiny ];
  90         241  
  90         733  
8              
9 90     90   231197 use Hash::Util;
  90         151808  
  90         793  
10             use Ref::Util (
11 90         6462 qw[ is_regexpref ],
12 90     90   5699 );
  90         226  
13              
14 90     90   674 use Test::Deep;
  90         286  
  90         811  
15 90     90   27944 use Test::More;
  90         215  
  90         840  
16 90     90   77745 use Test::LWP::UserAgent;
  90         4036629  
  90         4499  
17              
18 90     90   33457 use Net::Amazon::S3;
  90         355  
  90         3265  
19              
20 90     90   51338 use Shared::Examples::Net::Amazon::S3::API;
  90         314  
  90         1085  
21 90     90   58855 use Shared::Examples::Net::Amazon::S3::Client;
  90         297  
  90         1067  
22 90     90   65942 use Shared::Examples::Net::Amazon::S3::Request;
  90         307  
  90         1070  
23              
24             our @EXPORT_OK = (
25             qw[ s3_api_with_signature_4 ],
26             qw[ s3_api_with_signature_2 ],
27             qw[ expect_net_amazon_s3_feature ],
28             qw[ expect_net_amazon_s3_operation ],
29             qw[ expect_operation_list_all_my_buckets ],
30             qw[ expect_operation_bucket_create ],
31             qw[ expect_operation_bucket_delete ],
32             qw[ with_fixture ],
33             qw[ fixture ],
34             qw[ with_response_fixture ],
35             );
36              
37             my %fixtures;
38             sub fixture {
39 177     177 0 3591 my ($name) = @_;
40              
41             $fixtures{$name} = eval "require Shared::Examples::Net::Amazon::S3::Fixture::$name"
42 177 100       15022 unless defined $fixtures{$name};
43              
44             die "Fixture $name not found: $@"
45 177 50       1176 unless defined $fixtures{$name};
46              
47 177         479 return +{ %{ $fixtures{$name} } };
  177         1185  
48             }
49              
50             sub with_fixture {
51 0     0 0 0 my ($name) = @_;
52              
53 0         0 my $fixture = fixture ($name);
54             return wantarray
55 0 0       0 ? %$fixture
56             : $fixture
57             ;
58             }
59              
60             sub with_response_fixture {
61 167     167 0 4285 my ($name) = @_;
62              
63 167         726 my $fixture = fixture ($name);
64 167         482 my $response_fixture = {};
65              
66 167         717 for my $key (keys %$fixture) {
67 556         879 my $new_key;
68 556 100 50     2012 $new_key ||= "with_response_data" if $key eq 'content';
69 556 100 33     2638 $new_key ||= "with_$key" if $key =~ m/^response/;
70 556   66     1929 $new_key ||= "with_response_header_$key";
71              
72 556         1573 $response_fixture->{$new_key} = $fixture->{$key};
73             }
74              
75             return wantarray
76 167 50       3006 ? %$response_fixture
77             : $response_fixture
78             ;
79             }
80              
81              
82             sub s3_api {
83 405     405 0 15562 my $api = Net::Amazon::S3->new (@_);
84              
85 405         3629 $api->ua (Test::LWP::UserAgent->new (network_fallback => 0));
86              
87 405         7220 $api;
88             }
89              
90             sub s3_api_mock_http_response {
91 210     210 0 1310 my ($self, $api, %params) = @_;
92              
93 210   100     963 $params{with_response_code} ||= HTTP::Status::HTTP_OK;
94              
95             my %headers = (
96             content_type => 'application/xml',
97             (
98             map {
99 2117         3526 m/^with_response_header_(.*)/;
100             defined $1 && length $1
101 2117 100 66     5855 ? ($1 => $params{$_})
102             : ()
103             } keys %params
104             ),
105 210 100       1080 %{ $params{with_response_headers} || {} },
  210         1750  
106             );
107              
108             $api->ua->map_response (
109             sub {
110 210     210   64051 ${ $params{into} } = $_[0];
  210         700  
111 210         563 1;
112             },
113             HTTP::Response->new (
114             $params{with_response_code},
115             HTTP::Status::status_message ($params{with_response_code}),
116             [ %headers ],
117             $params{with_response_data},
118 210         6157 ),
119             );
120             }
121              
122             sub s3_api_with_signature_4 {
123 4     4 0 3434 s3_api (
124             @_,
125             aws_access_key_id => 'AKIDEXAMPLE',
126             aws_secret_access_key => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY',
127             authorization_method => 'Net::Amazon::S3::Signature::V4',
128             secure => 1,
129             use_virtual_host => 1,
130             );
131             }
132              
133             sub s3_api_with_signature_2 {
134 401     401 0 11737 s3_api (
135             @_,
136             aws_access_key_id => 'AKIDEXAMPLE',
137             aws_secret_access_key => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY',
138             authorization_method => 'Net::Amazon::S3::Signature::V2',
139             secure => 1,
140             use_virtual_host => 1,
141             );
142             }
143              
144             sub expect_net_amazon_s3_feature {
145 4     4 0 68 my ($title, %params) = @_;
146              
147 4         13 my $s3 = delete $params{with_s3};
148 4         11 my $feature = delete $params{feature};
149 4         12 my $expectation = "expect_$feature";
150              
151 4         11 local $Test::Builder::Level = $Test::Builder::Level + 1;
152              
153             subtest $title => sub {
154 4     4   5164 plan tests => 2;
155              
156 4 50       3590 if (my $code = Shared::Examples::Net::Amazon::S3::API->can ($expectation)) {
157 4         35 $code->( "using S3 API" => (
158             with_s3 => $s3,
159             %params
160             ));
161             } else {
162 0         0 fail "Net::Amazon::S3 feature expectation $expectation not found";
163             }
164              
165 4 50       2870 if (my $code = Shared::Examples::Net::Amazon::S3::Client->can ($expectation)) {
166 4         172 $code->( "using S3 Client" => (
167             with_client => Net::Amazon::S3::Client->new (s3 => $s3),
168             %params
169             ));
170             } else {
171 0         0 fail "Net::Amazon::S3::Client feature expectation $expectation not found";
172             }
173 4         36 };
174             }
175              
176             sub _operation_parameters {
177 0     0   0 my ($params, @names) = @_;
178 0         0 my $map = {};
179 0 0       0 $map = shift @names if Ref::Util::is_plain_hashref ($names[0]);
180              
181             return
182             map +( ($map->{$_} || $_) => $params->{"with_$_"} ),
183 0   0     0 grep exists $params->{"with_$_"},
184             @names
185             ;
186             }
187              
188             sub _with_keys {
189 210     210   1678 map "with_$_", @_;
190             }
191              
192             sub _keys_operation () {
193             return (
194 210     210   2008 qw[ -shared_examples ],
195             qw[ -method ],
196             qw[ with_s3 ],
197             qw[ with_client ],
198             qw[ shared_examples ],
199             qw[ with_response_code ],
200             qw[ with_response_data ],
201             qw[ with_response_headers ],
202             qw[ with_response_header_content_type ],
203             qw[ with_response_header_content_length ],
204             qw[ expect_s3_err ],
205             qw[ expect_s3_errstr ],
206             qw[ expect_data ],
207             qw[ expect_request ],
208             qw[ expect_request_content ],
209             qw[ expect_request_headers ],
210             qw[ throws ],
211             );
212             }
213              
214             sub _expect_request {
215 201     201   737 my ($request, $expect, $title) = @_;
216              
217 201         596 local $Test::Builder::Level = $Test::Builder::Level + 1;
218              
219 201         878 my ($method, $uri) = %$expect;
220 201   50     1089 cmp_deeply
221             $request,
222             all (
223             methods (method => $method),
224             methods (uri => methods (as_string => $uri)),
225             ),
226             $title || 'expect request'
227             ;
228             }
229              
230             sub _expect_request_content {
231 64     64   225 my ($request, $expected, $title) = @_;
232              
233 64         189 local $Test::Builder::Level = $Test::Builder::Level + 1;
234              
235 64         392 my $got = Shared::Examples::Net::Amazon::S3::Request::_canonical_xml ($request->content);
236 64         218 $expected = Shared::Examples::Net::Amazon::S3::Request::_canonical_xml ($expected);
237              
238 64   50     471 cmp_deeply $got, $expected, $title || "expect request content";
239             }
240              
241             sub _expect_request_headers {
242 51     51   166 my ($request, $expected, $title) = @_;
243              
244 51         139 local $Test::Builder::Level = $Test::Builder::Level + 1;
245              
246 51         392 my %got = map +($_ => scalar $request->header ($_)), keys %$expected;
247              
248 51   50     5741 cmp_deeply
249             \ %got,
250             $expected,
251             $title || "expect request headers"
252             ;
253             }
254              
255             sub _expect_s3_err {
256 62     62   236 my ($got, $expected, $title) = @_;
257              
258             SKIP: {
259 62 50       151 skip "Net::Amazon::S3->err test irrelevant for Client", 1
  62         304  
260             if eq_deeply $got, obj_isa ('Net::Amazon::S3::Client');
261              
262 62   50     29936 cmp_deeply $got, methods (err => $expected), $title || 'expect S3->err';
263             }
264             }
265              
266             sub _expect_s3_errstr {
267 62     62   232 my ($got, $expected, $title) = @_;
268              
269             SKIP: {
270 62 50       141 skip "Net::Amazon::S3->errstr test irrelevant for Client", 1
  62         241  
271             if eq_deeply $got, obj_isa ('Net::Amazon::S3::Client');
272              
273 62   50     13454 cmp_deeply $got, methods (errstr => $expected), $title || 'expect S3->errstr';
274             }
275             }
276              
277             sub _expect_operation {
278 210     210   1505 my ($title, %params) = @_;
279              
280 210         668 local $Test::Builder::Level = $Test::Builder::Level + 1;
281              
282 210         626 my $class = delete $params{-shared_examples};
283 210         577 my $operation = delete $params{-operation};
284              
285 210         2013 my $api = $class->_default_with_api (\%params);
286 210         2344 $class->_mock_http_response ($api, %params, into => \ (my $request));
287              
288 210 50       56847 if (my $code = $class->can ($operation)) {
289             subtest $title => sub {
290             plan tests => 1
291             + int (!! exists $params{expect_request})
292             + int (!! exists $params{expect_request_content})
293             + int (!! exists $params{expect_request_headers})
294             + int (!! exists $params{expect_s3_err})
295             + int (!! exists $params{expect_s3_errstr})
296 210     210   290582 ;
297              
298 210         194030 my $got;
299 210         559 my $lives = eval { $got = $api->$code (%params); 1 };
  210         1512  
  141         21967  
300 210         3068 my $error = $@;
301              
302 210 100       773 if ($lives) {
303             exists $params{throws}
304             ? fail "operation expected to throw but lives"
305 141 50       1236 : cmp_deeply $got, $params{expect_data}, "expect operation return data"
306             ;
307             }
308             else {
309             $params{throws} = re $params{throws}
310 69 100       595 if is_regexpref $params{throws};
311             $params{throws} = obj_isa $params{throws}
312 69 100 66     66571 if defined $params{throws} && ! ref $params{throws};
313              
314             defined $params{throws}
315             ? cmp_deeply $error, $params{throws}, "it should throw"
316 69 50       619 : do { fail "operation expected to live but died" ; diag $error }
  0         0  
  0         0  
317             ;
318             }
319              
320             _expect_request $request, $params{expect_request}
321 210 100       238371 if exists $params{expect_request};
322             _expect_request_content $request, $params{expect_request_content}
323 210 100       337232 if exists $params{expect_request_content};
324             _expect_request_headers ($request, $params{expect_request_headers})
325 210 100       36509 if exists $params{expect_request_headers};
326              
327             _expect_s3_err $api, $params{expect_s3_err}
328 210 100       224501 if exists $params{expect_s3_err};
329             _expect_s3_errstr $api, $params{expect_s3_errstr}
330 210 100       34160 if exists $params{expect_s3_errstr};
331 210         2315 };
332             } else {
333 0 0       0 fail $title or diag "Operation ${class}::$operation not found";
334             }
335             }
336              
337             sub _generate_operation_expectation {
338 1530     1530   4742 my ($name, @parameters) = @_;
339              
340 1530         9190 my @on = (
341             ('bucket') x!! ($name =~ m/^ ( bucket | object )/x),
342             ('key') x!! ($name =~ m/^ ( object )/x),
343             );
344              
345 1530         2757 my $on = "qw[ ${ \ join ' ', @on } ]";
  1530         5678  
346              
347 1530     8 0 3595 eval <<"OPERATION_DECLARATION";
  8     17 0 60  
  8     29 0 28  
  8     10 0 32  
  8     8 0 1300  
  17     15 0 148  
  17     8 0 55  
  17     8 0 66  
  17     8 0 2933  
  29     5 0 263  
  29     18 0 99  
  29     20 0 120  
  29     10 0 5480  
  10     13 0 84  
  10     15 0 37  
  10     8 0 47  
  10     10 0 1795  
  8     8 0 77  
  8     17 0 29  
  8     29 0 36  
  8     10 0 1448  
  15     8 0 137  
  15     15 0 58  
  15     8 0 66  
  15     8 0 2932  
  8     8 0 81  
  8     5 0 27  
  8     18 0 43  
  8     20 0 1555  
  8     10 0 70  
  8     13 0 31  
  8     15 0 45  
  8     8 0 1456  
  8     10 0 66  
  8         33  
  8         41  
  8         1402  
  5         47  
  5         15  
  5         20  
  5         924  
  18         183  
  18         56  
  18         84  
  18         3620  
  20         197  
  20         64  
  20         89  
  20         5146  
  10         135  
  10         35  
  10         51  
  10         2200  
  13         115  
  13         46  
  13         55  
  13         2375  
  15         227  
  15         62  
  15         82  
  15         2943  
  8         79  
  8         25  
  8         32  
  8         1494  
  10         90  
  10         31  
  10         45  
  10         1815  
  8         32  
  17         77  
  29         145  
  10         45  
  8         39  
  15         99  
  8         38  
  8         48  
  8         41  
  5         18  
  18         107  
  20         148  
  10         48  
  13         57  
  15         92  
  8         35  
  10         44  
348             sub parameters_$name {
349 1530         194593 qw[ ${ \ join ' ', @parameters } ]
350             }
351              
352             sub expect_operation_$name {
353             my (\$title, \%params) = \@_;
354             local \$Test::Builder::Level = \$Test::Builder::Level + 1;
355             Hash::Util::lock_keys \%params, _with_keys ($on, parameters_$name), _keys_operation;
356             _expect_operation \$title, \%params, -operation => 'operation_$name';
357             }
358             OPERATION_DECLARATION
359             }
360              
361             _generate_operation_expectation list_all_my_buckets =>
362             ;
363              
364             _generate_operation_expectation bucket_acl_get =>
365             ;
366              
367             _generate_operation_expectation bucket_acl_set =>
368             qw[ acl ],
369             qw[ acl_xml ],
370             qw[ acl_short ],
371             ;
372              
373             _generate_operation_expectation bucket_create =>
374             qw[ acl ],
375             qw[ acl_short ],
376             qw[ region ],
377             ;
378              
379             _generate_operation_expectation bucket_delete =>
380             ;
381              
382             _generate_operation_expectation bucket_objects_list =>
383             qw[ delimiter ],
384             qw[ max_keys ],
385             qw[ marker ],
386             qw[ prefix ],
387             ;
388              
389             _generate_operation_expectation bucket_objects_delete =>
390             qw[ keys ],
391             ;
392              
393             _generate_operation_expectation object_acl_get =>
394             ;
395              
396             _generate_operation_expectation object_acl_set =>
397             qw[ acl ],
398             qw[ acl_xml ],
399             qw[ acl_short ],
400             ;
401              
402             _generate_operation_expectation object_create =>
403             qw[ headers ],
404             qw[ value ],
405             qw[ cache_control ],
406             qw[ content_disposition ],
407             qw[ content_encoding ],
408             qw[ content_type ],
409             qw[ encryption ],
410             qw[ expires ],
411             qw[ storage_class ],
412             qw[ user_metadata ],
413             qw[ acl ],
414             qw[ acl_short ],
415             ;
416              
417             _generate_operation_expectation object_delete =>
418             ;
419              
420             _generate_operation_expectation object_fetch =>
421             qw[ range ],
422             ;
423              
424             _generate_operation_expectation object_head =>
425             ;
426              
427             _generate_operation_expectation bucket_tags_add =>
428             qw[ tags ],
429             ;
430              
431             _generate_operation_expectation object_tags_add =>
432             qw[ tags ],
433             qw[ version_id ],
434             ;
435              
436             _generate_operation_expectation bucket_tags_delete =>
437             ;
438              
439             _generate_operation_expectation object_tags_delete =>
440             qw[ version_id ],
441             ;
442              
443              
444             1;
445              
446             __END__
447              
448             =pod
449              
450             =encoding UTF-8
451              
452             =head1 NAME
453              
454             Shared::Examples::Net::Amazon::S3 - used for testing and as example
455              
456             =head1 VERSION
457              
458             version 0.99
459              
460             =head1 AUTHOR
461              
462             Branislav Zahradník <barney@cpan.org>
463              
464             =head1 COPYRIGHT AND LICENSE
465              
466             This software is copyright (c) 2021 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník.
467              
468             This is free software; you can redistribute it and/or modify it under
469             the same terms as the Perl 5 programming language system itself.
470              
471             =cut