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.98';
4 89     89   6537621 use strict;
  89         548  
  89         2984  
5 89     89   553 use warnings;
  89         187  
  89         3179  
6              
7 89     89   491 use parent qw[ Exporter::Tiny ];
  89         281  
  89         646  
8              
9 89     89   233678 use Hash::Util;
  89         159312  
  89         785  
10             use Ref::Util (
11 89         6261 qw[ is_regexpref ],
12 89     89   6443 );
  89         214  
13              
14 89     89   604 use Test::Deep;
  89         225  
  89         868  
15 89     89   28729 use Test::More;
  89         253  
  89         819  
16 89     89   81221 use Test::LWP::UserAgent;
  89         4846514  
  89         4668  
17              
18 89     89   57766 use Net::Amazon::S3;
  89         433  
  89         4601  
19              
20 89     89   57592 use Shared::Examples::Net::Amazon::S3::API;
  89         342  
  89         1155  
21 89     89   62825 use Shared::Examples::Net::Amazon::S3::Client;
  89         304  
  89         1144  
22 89     89   68991 use Shared::Examples::Net::Amazon::S3::Request;
  89         294  
  89         1144  
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 173     173 0 3921 my ($name) = @_;
40              
41             $fixtures{$name} = eval "require Shared::Examples::Net::Amazon::S3::Fixture::$name"
42 173 100       14833 unless defined $fixtures{$name};
43              
44             die "Fixture $name not found: $@"
45 173 50       1195 unless defined $fixtures{$name};
46              
47 173         446 return +{ %{ $fixtures{$name} } };
  173         1171  
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 163     163 0 5310 my ($name) = @_;
62              
63 163         754 my $fixture = fixture ($name);
64 163         451 my $response_fixture = {};
65              
66 163         698 for my $key (keys %$fixture) {
67 543         871 my $new_key;
68 543 100 50     1973 $new_key ||= "with_response_data" if $key eq 'content';
69 543 100 33     2901 $new_key ||= "with_$key" if $key =~ m/^response/;
70 543   66     1910 $new_key ||= "with_response_header_$key";
71              
72 543         1486 $response_fixture->{$new_key} = $fixture->{$key};
73             }
74              
75             return wantarray
76 163 50       2907 ? %$response_fixture
77             : $response_fixture
78             ;
79             }
80              
81              
82             sub s3_api {
83 398     398 0 14753 my $api = Net::Amazon::S3->new (@_);
84              
85 398         3742 $api->ua (Test::LWP::UserAgent->new (network_fallback => 0));
86              
87 398         7165 $api;
88             }
89              
90             sub s3_api_mock_http_response {
91 203     203 0 1205 my ($self, $api, %params) = @_;
92              
93 203   100     929 $params{with_response_code} ||= HTTP::Status::HTTP_OK;
94              
95             my %headers = (
96             content_type => 'application/xml',
97             (
98             map {
99 2038         3290 m/^with_response_header_(.*)/;
100             defined $1 && length $1
101 2038 100 66     5730 ? ($1 => $params{$_})
102             : ()
103             } keys %params
104             ),
105 203 100       951 %{ $params{with_response_headers} || {} },
  203         1613  
106             );
107              
108             $api->ua->map_response (
109             sub {
110 203     203   59132 ${ $params{into} } = $_[0];
  203         684  
111 203         520 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 203         6085 ),
119             );
120             }
121              
122             sub s3_api_with_signature_4 {
123 4     4 0 4183 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 394     394 0 11462 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 82 my ($title, %params) = @_;
146              
147 4         13 my $s3 = delete $params{with_s3};
148 4         11 my $feature = delete $params{feature};
149 4         14 my $expectation = "expect_$feature";
150              
151 4         12 local $Test::Builder::Level = $Test::Builder::Level + 1;
152              
153             subtest $title => sub {
154 4     4   5389 plan tests => 2;
155              
156 4 50       3549 if (my $code = Shared::Examples::Net::Amazon::S3::API->can ($expectation)) {
157 4         30 $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       2769 if (my $code = Shared::Examples::Net::Amazon::S3::Client->can ($expectation)) {
166 4         175 $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         44 };
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 203     203   1555 map "with_$_", @_;
190             }
191              
192             sub _keys_operation () {
193             return (
194 203     203   1841 qw[ -shared_examples ],
195             qw[ with_s3 ],
196             qw[ with_client ],
197             qw[ shared_examples ],
198             qw[ with_response_code ],
199             qw[ with_response_data ],
200             qw[ with_response_headers ],
201             qw[ with_response_header_content_type ],
202             qw[ with_response_header_content_length ],
203             qw[ expect_s3_err ],
204             qw[ expect_s3_errstr ],
205             qw[ expect_data ],
206             qw[ expect_request ],
207             qw[ expect_request_content ],
208             qw[ expect_request_headers ],
209             qw[ throws ],
210             );
211             }
212              
213             sub _expect_request {
214 194     194   661 my ($request, $expect, $title) = @_;
215              
216 194         501 local $Test::Builder::Level = $Test::Builder::Level + 1;
217              
218 194         872 my ($method, $uri) = %$expect;
219 194   50     1027 cmp_deeply
220             $request,
221             all (
222             methods (method => $method),
223             methods (uri => methods (as_string => $uri)),
224             ),
225             $title || 'expect request'
226             ;
227             }
228              
229             sub _expect_request_content {
230 64     64   229 my ($request, $expected, $title) = @_;
231              
232 64         191 local $Test::Builder::Level = $Test::Builder::Level + 1;
233              
234 64         327 my $got = Shared::Examples::Net::Amazon::S3::Request::_canonical_xml ($request->content);
235 64         213 $expected = Shared::Examples::Net::Amazon::S3::Request::_canonical_xml ($expected);
236              
237 64   50     475 cmp_deeply $got, $expected, $title || "expect request content";
238             }
239              
240             sub _expect_request_headers {
241 48     48   165 my ($request, $expected, $title) = @_;
242              
243 48         122 local $Test::Builder::Level = $Test::Builder::Level + 1;
244              
245 48         342 my %got = map +($_ => scalar $request->header ($_)), keys %$expected;
246              
247 48   50     5606 cmp_deeply
248             \ %got,
249             $expected,
250             $title || "expect request headers"
251             ;
252             }
253              
254             sub _expect_s3_err {
255 62     62   227 my ($got, $expected, $title) = @_;
256              
257             SKIP: {
258 62 50       151 skip "Net::Amazon::S3->err test irrelevant for Client", 1
  62         293  
259             if eq_deeply $got, obj_isa ('Net::Amazon::S3::Client');
260              
261 62   50     30107 cmp_deeply $got, methods (err => $expected), $title || 'expect S3->err';
262             }
263             }
264              
265             sub _expect_s3_errstr {
266 62     62   249 my ($got, $expected, $title) = @_;
267              
268             SKIP: {
269 62 50       139 skip "Net::Amazon::S3->errstr test irrelevant for Client", 1
  62         246  
270             if eq_deeply $got, obj_isa ('Net::Amazon::S3::Client');
271              
272 62   50     13884 cmp_deeply $got, methods (errstr => $expected), $title || 'expect S3->errstr';
273             }
274             }
275              
276             sub _expect_operation {
277 203     203   1450 my ($title, %params) = @_;
278              
279 203         632 local $Test::Builder::Level = $Test::Builder::Level + 1;
280              
281 203         650 my $class = delete $params{-shared_examples};
282 203         580 my $operation = delete $params{-operation};
283              
284 203         2147 my $api = $class->_default_with_api (\%params);
285 203         2312 $class->_mock_http_response ($api, %params, into => \ (my $request));
286              
287 203 50       55305 if (my $code = $class->can ($operation)) {
288             subtest $title => sub {
289             plan tests => 1
290             + int (!! exists $params{expect_request})
291             + int (!! exists $params{expect_request_content})
292             + int (!! exists $params{expect_request_headers})
293             + int (!! exists $params{expect_s3_err})
294             + int (!! exists $params{expect_s3_errstr})
295 203     203   267443 ;
296              
297 203         182590 my $got;
298 203         531 my $lives = eval { $got = $api->$code (%params); 1 };
  203         1423  
  138         21381  
299 203         3028 my $error = $@;
300              
301 203 100       698 if ($lives) {
302             exists $params{throws}
303             ? fail "operation expected to throw but lives"
304 138 50       1067 : cmp_deeply $got, $params{expect_data}, "expect operation return data"
305             ;
306             }
307             else {
308             $params{throws} = re $params{throws}
309 65 100       628 if is_regexpref $params{throws};
310             $params{throws} = obj_isa $params{throws}
311 65 100 66     66369 if defined $params{throws} && ! ref $params{throws};
312              
313             defined $params{throws}
314             ? cmp_deeply $error, $params{throws}, "it should throw"
315 65 50       669 : do { fail "operation expected to live but died" ; diag $error }
  0         0  
  0         0  
316             ;
317             }
318              
319             _expect_request $request, $params{expect_request}
320 203 100       209165 if exists $params{expect_request};
321             _expect_request_content $request, $params{expect_request_content}
322 203 100       328261 if exists $params{expect_request_content};
323             _expect_request_headers ($request, $params{expect_request_headers})
324 203 100       34234 if exists $params{expect_request_headers};
325              
326             _expect_s3_err $api, $params{expect_s3_err}
327 203 100       202899 if exists $params{expect_s3_err};
328             _expect_s3_errstr $api, $params{expect_s3_errstr}
329 203 100       33332 if exists $params{expect_s3_errstr};
330 203         2055 };
331             } else {
332 0 0       0 fail $title or diag "Operation ${class}::$operation not found";
333             }
334             }
335              
336             sub _generate_operation_expectation {
337 1513     1513   4569 my ($name, @parameters) = @_;
338              
339 1513         9425 my @on = (
340             ('bucket') x!! ($name =~ m/^ ( bucket | object )/x),
341             ('key') x!! ($name =~ m/^ ( object )/x),
342             );
343              
344 1513         2773 my $on = "qw[ ${ \ join ' ', @on } ]";
  1513         6123  
345              
346 1513     8 0 3459 eval <<"OPERATION_DECLARATION";
  8     17 0 62  
  8     29 0 27  
  8     10 0 42  
  8     8 0 1345  
  17     15 0 156  
  17     8 0 47  
  17     8 0 69  
  17     8 0 3090  
  29     5 0 246  
  29     18 0 100  
  29     20 0 128  
  29     10 0 5241  
  10     11 0 93  
  10     10 0 40  
  10     8 0 49  
  10     10 0 1812  
  8     8 0 103  
  8     17 0 28  
  8     29 0 48  
  8     10 0 1503  
  15     8 0 120  
  15     15 0 43  
  15     8 0 62  
  15     8 0 2780  
  8     8 0 81  
  8     5 0 32  
  8     18 0 42  
  8     20 0 1453  
  8     10 0 76  
  8     11 0 34  
  8     10 0 45  
  8     8 0 1490  
  8     10 0 70  
  8         40  
  8         33  
  8         1169  
  5         57  
  5         18  
  5         29  
  5         1006  
  18         155  
  18         55  
  18         64  
  18         3347  
  20         206  
  20         62  
  20         99  
  20         4594  
  10         102  
  10         34  
  10         47  
  10         1756  
  11         108  
  11         37  
  11         53  
  11         2114  
  10         89  
  10         33  
  10         41  
  10         1709  
  8         75  
  8         25  
  8         35  
  8         1573  
  10         90  
  10         35  
  10         50  
  10         1816  
  8         37  
  17         80  
  29         153  
  10         55  
  8         52  
  15         84  
  8         45  
  8         47  
  8         30  
  5         25  
  18         79  
  20         133  
  10         47  
  11         55  
  10         41  
  8         41  
  10         58  
347             sub parameters_$name {
348 1513         195831 qw[ ${ \ join ' ', @parameters } ]
349             }
350              
351             sub expect_operation_$name {
352             my (\$title, \%params) = \@_;
353             local \$Test::Builder::Level = \$Test::Builder::Level + 1;
354             Hash::Util::lock_keys \%params, _with_keys ($on, parameters_$name), _keys_operation;
355             _expect_operation \$title, \%params, -operation => 'operation_$name';
356             }
357             OPERATION_DECLARATION
358             }
359              
360             _generate_operation_expectation list_all_my_buckets =>
361             ;
362              
363             _generate_operation_expectation bucket_acl_get =>
364             ;
365              
366             _generate_operation_expectation bucket_acl_set =>
367             qw[ acl ],
368             qw[ acl_xml ],
369             qw[ acl_short ],
370             ;
371              
372             _generate_operation_expectation bucket_create =>
373             qw[ acl ],
374             qw[ acl_short ],
375             qw[ region ],
376             ;
377              
378             _generate_operation_expectation bucket_delete =>
379             ;
380              
381             _generate_operation_expectation bucket_objects_list =>
382             qw[ delimiter ],
383             qw[ max_keys ],
384             qw[ marker ],
385             qw[ prefix ],
386             ;
387              
388             _generate_operation_expectation bucket_objects_delete =>
389             qw[ keys ],
390             ;
391              
392             _generate_operation_expectation object_acl_get =>
393             ;
394              
395             _generate_operation_expectation object_acl_set =>
396             qw[ acl ],
397             qw[ acl_xml ],
398             qw[ acl_short ],
399             ;
400              
401             _generate_operation_expectation object_create =>
402             qw[ headers ],
403             qw[ value ],
404             qw[ cache_control ],
405             qw[ content_disposition ],
406             qw[ content_encoding ],
407             qw[ content_type ],
408             qw[ encryption ],
409             qw[ expires ],
410             qw[ storage_class ],
411             qw[ user_metadata ],
412             qw[ acl ],
413             qw[ acl_short ],
414             ;
415              
416             _generate_operation_expectation object_delete =>
417             ;
418              
419             _generate_operation_expectation object_fetch =>
420             ;
421              
422             _generate_operation_expectation object_head =>
423             ;
424              
425             _generate_operation_expectation bucket_tags_add =>
426             qw[ tags ],
427             ;
428              
429             _generate_operation_expectation object_tags_add =>
430             qw[ tags ],
431             qw[ version_id ],
432             ;
433              
434             _generate_operation_expectation bucket_tags_delete =>
435             ;
436              
437             _generate_operation_expectation object_tags_delete =>
438             qw[ version_id ],
439             ;
440              
441              
442             1;
443              
444             __END__
445              
446             =pod
447              
448             =encoding UTF-8
449              
450             =head1 NAME
451              
452             Shared::Examples::Net::Amazon::S3 - used for testing and as example
453              
454             =head1 VERSION
455              
456             version 0.98
457              
458             =head1 AUTHOR
459              
460             Branislav Zahradník <barney@cpan.org>
461              
462             =head1 COPYRIGHT AND LICENSE
463              
464             This software is copyright (c) 2021 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník.
465              
466             This is free software; you can redistribute it and/or modify it under
467             the same terms as the Perl 5 programming language system itself.
468              
469             =cut