File Coverage

blib/lib/Test/Override/UserAgent.pm
Criterion Covered Total %
statement 192 192 100.0
branch 53 54 98.1
condition 22 23 95.6
subroutine 39 39 100.0
pod 7 7 100.0
total 313 315 99.3


line stmt bran cond sub pod time code
1             package Test::Override::UserAgent;
2              
3 18     18   1530990 use 5.008001;
  18         79  
  18         785  
4 18     18   115 use strict;
  18         41  
  18         666  
5 18     18   148 use warnings 'all';
  18         44  
  18         1505  
6              
7             ###########################################################################
8             # METADATA
9             our $AUTHORITY = 'cpan:DOUGDUDE';
10             our $VERSION = '0.004001';
11              
12             ###########################################################################
13             # MODULE IMPORTS
14 18     18   107 use Carp qw(croak);
  18         63  
  18         1352  
15 18     18   19567 use Clone;
  18         60928  
  18         1067  
16 18     18   16130 use HTTP::Config 5.815;
  18         72784  
  18         544  
17 18     18   3349 use HTTP::Date ();
  18         14408  
  18         386  
18 18     18   2309 use HTTP::Headers;
  18         19043  
  18         534  
19 18     18   1958 use HTTP::Response;
  18         695529  
  18         634  
20 18     18   297 use HTTP::Status 5.817 ();
  18         565  
  18         378  
21 18     18   18021 use LWP::UserAgent; # Not actually required here, but want it to be loaded
  18         75573  
  18         512  
22 18     18   141 use Scalar::Util;
  18         34  
  18         1408  
23 18     18   17825 use Sub::Install 0.90;
  18         33051  
  18         132  
24 18     18   13763 use Test::Override::UserAgent::Scope;
  18         68  
  18         709  
25 18     18   201 use Try::Tiny;
  18         35  
  18         1232  
26 18     18   110 use URI;
  18         37  
  18         694  
27              
28             ###########################################################################
29             # ALL IMPORTS BEFORE THIS WILL BE ERASED
30 18     18   110 use namespace::clean 0.04 -except => [qw(meta)];
  18         330  
  18         156  
31              
32             ###########################################################################
33             # METHODS
34             sub allow_live_requests {
35 26     26 1 6481 my ($self, $new_value) = @_;
36              
37 26 100       103 if (defined $new_value) {
38             # Set the new value
39 6         16 $self->{allow_live_requests} = $new_value;
40             }
41              
42 26         128 return $self->{allow_live_requests};
43             }
44             sub handle_request {
45 60     60 1 11299 my ($self, $request, %args) = @_;
46              
47             # Lookup the handler for the request
48 60         234 my $handler = $self->_get_handler_for($request);
49              
50             # Hold the response
51 60         119 my $response;
52              
53 60 100       233 if (defined $handler) {
54             # Get the response
55 44         197 $response = _convert_psgi_response($handler->($request));
56              
57 44 100       262 if (!defined $response->request) {
58             # Set the request that made this response
59 43         667 $response->request($request);
60             }
61             }
62              
63 60 100 100     846 if (!defined $response && exists $args{live_request_handler}) {
64             # There was no handler/response and a live requestor was provided
65 15 100       139 if ($self->allow_live_requests) {
66             # Make the live request
67 2         10 $response = $args{live_request_handler}->($request);
68             }
69             else {
70             # Make an internal response for not successful since no
71             # live requests are allowed.
72 13         143 $response = _new_internal_response(
73             HTTP::Status::HTTP_NOT_FOUND,
74             'Not Found (No Live Requests)',
75             );
76             }
77             }
78              
79 60         267 return $response;
80             }
81             sub install_in_scope {
82 2     2 1 5731 my ($self) = @_;
83              
84             # Return the scope variable
85 2         33 return Test::Override::UserAgent::Scope->new(
86             override => $self,
87             );
88             }
89             sub install_in_user_agent {
90 15     15 1 70419 my ($self, $user_agent, %args) = @_;
91              
92             # Get the clone argument
93 15 100       79 my $clone = exists $args{clone} ? $args{clone} : 0;
94              
95 15 100       78 if ($clone) {
96             # Make a clone of the user agent
97 1         7 $user_agent = $user_agent->clone;
98             }
99              
100             # Add as a handler in the user agent
101             $user_agent->add_handler(
102             request_send => sub {
103             # Get the response
104             my $response = $self->handle_request(
105             shift,
106 1         3 live_request_handler => sub { return; },
107 51     51   2704834 );
108              
109 51         251 return $response;
110             },
111 15         500 owner => Scalar::Util::refaddr($self),
112             );
113              
114             # Return the user agent
115 15         830 return $user_agent;
116             }
117             sub override_request {
118 45     45 1 3867 my ($self, @args) = @_;
119              
120             # Get the handler from the end
121 45         78 my $handler = pop @args;
122              
123             # Convert the arguments into a hash
124 45         141 my %args = @args;
125              
126             # Register the handler
127 45         166 $self->_register_handler($handler, %args);
128              
129             # Enable chaining
130 45         187 return $self;
131             }
132             sub uninstall_from_user_agent {
133 2     2 1 54200 my ($self, $user_agent) = @_;
134              
135             # Remove our handlers from the user agent
136 2         27 $user_agent->remove_handler(
137             'request_send',
138             owner => Scalar::Util::refaddr($self),
139             );
140              
141             # Return the user agent for some reason
142 2         430 return $user_agent;
143             }
144              
145             ###########################################################################
146             # STATIC METHODS
147             sub import {
148 19     19   1620 my ($class, %args) = @_;
149              
150             # What this module is being used for
151 19   100     133 my $use_for = $args{for} || 'testing';
152              
153 19 100       83 if ($use_for eq 'configuration') {
154             # Get the calling package
155 4         12 my $caller = caller;
156              
157             # Create a new configuration object that will be wrapped in
158             # closures.
159 4         36 my $conf = $class->new;
160              
161             # Create a defaults hash for colsures
162 4         9 my $defaults = {};
163              
164             # Install override_request
165             Sub::Install::install_sub({
166 12     12   62 code => sub { return $conf->override_request(%{$defaults}, @_); },
  12         41  
167 4         40 into => $caller,
168             as => 'override_request',
169             });
170              
171             # Install override_for
172             Sub::Install::install_sub({
173             code => sub {
174 3     3   19 my $block = pop;
175              
176             # Rember the current defaults
177 3         5 my $previous_defaults = $defaults;
178              
179             # Set the new defaults as an extension of the current
180 3         4 $defaults = {%{Clone::clone($defaults)}, @_};
  3         35  
181              
182             # Run the block with the defaults in effect
183 3         11 $block->();
184              
185             # Restore the defaults
186 3         8 $defaults = $previous_defaults;
187             },
188 4         305 into => $caller,
189             as => 'override_for',
190             });
191              
192             # Install allow_live
193             Sub::Install::install_sub({
194             code => sub {
195 4     4   17 my $allow = shift;
196              
197             # Set the allow live requests (no arguments defaults to 1)
198 4 100       21 $conf->allow_live_requests(defined $allow ? $allow : 1);
199             },
200 4         208 into => $caller,
201             as => 'allow_live',
202             });
203              
204             # Install custom configuration which retuns the config object
205             Sub::Install::install_sub({
206 5     5   2361 code => sub { return $conf; },
207 4         218 into => $caller,
208             as => 'configuration',
209             });
210             }
211              
212 19         35073 return;
213             }
214              
215             ###########################################################################
216             # CONSTRUCTOR
217             sub new {
218 20     20 1 1844 my ($class, @args) = @_;
219              
220             # Get the arguments as a plain hash
221 20 100       126 my %args = @args == 1 ? %{shift @args}
  1         4  
222             : @args
223             ;
224              
225             # Create a hash with configuration information
226 20         274 my %data = (
227             # Attributes
228             allow_live_requests => 0,
229              
230             # Private attributes
231             _lookup_table => HTTP::Config->new,
232             _protocol_classes => {},
233             );
234              
235             # Set attributes
236 20         311 foreach my $arg (grep { m{\A [^_]}msx } keys %data) {
  60         255  
237 20 100       189 if (exists $args{$arg}) {
238 2         7 $data{$arg} = $args{$arg};
239             }
240             }
241              
242             # Bless the hash to this class
243 20         88 my $self = bless \%data, $class;
244              
245             # Set our unique name
246 20         253 $self->{_uniq_name} = $class . '::Number' . Scalar::Util::refaddr($self);
247              
248             # Return our blessed configuration
249 20         148 return $self;
250             }
251              
252             ###########################################################################
253             # PRIVATE METHODS
254             sub _get_handler_for {
255 60     60   150 my ($self, $request) = @_;
256              
257             # Get the handler
258 60         354 my @handlers = $self->{_lookup_table}->matching_items($request);
259              
260 60         19475 return $handlers[0];
261             }
262             sub _register_handler {
263 45     45   122 my ($self, $handler, %args) = @_;
264              
265             # Add m_ to the beginning of the arguments
266 45         122 for my $key (keys %args) {
267             # Specially handle "url" key as HTTP::Config does not
268 76 100 100     781 if ($key eq 'url' || $key eq 'uri') {
    50          
269             # Get the URI from the arguments
270 3         21 my $uri = URI->new(delete $args{$key});
271              
272             # Set a match against it's canonical value
273 3         12184 $args{m_uri__canonical} = $uri->canonical;
274             }
275             elsif (q{m_} ne substr $key, 0, 2) {
276             # Add m_
277 73         351 $args{"m_$key"} = delete $args{$key};
278             }
279             }
280              
281             # Set the handler
282 45         1094 $self->{_lookup_table}->add_item($handler, %args);
283              
284 45         645 return;
285             }
286              
287             ###########################################################################
288             # PRIVATE FUNCTIONS
289             sub _convert_psgi_response {
290 44     44   528 my ($response) = @_;
291              
292 44 100       255 if (!defined Scalar::Util::blessed($response)) {
293             # Get the type of the response
294 43         178 my $response_type = Scalar::Util::reftype($response);
295              
296 43 100 100     409 if (defined $response_type && $response_type eq 'ARRAY') {
297             # This is a PSGI-formatted response
298             try {
299             # Validate the response
300 41     41   1732 _validate_psgi_response($response);
301              
302             # Unwrap the PSGI response
303 32         49 my ($status_code, $headers, $body) = @{$response};
  32         81  
304              
305             # Change the headers to a header object
306 32         61 $headers = HTTP::Headers->new(@{$headers});
  32         225  
307              
308 32 100       2873 if (ref $body ne 'ARRAY') {
309             # The body is a filehandle
310 1         2 my $fh = $body;
311              
312             # Change the body to an array reference
313 1         4 $body = [];
314              
315 1         8 while (defined(my $line = $fh->getline)) {
316             # Push the line into the body
317 2         52 push @{$body}, $line;
  2         10  
318             }
319              
320             # Close the file
321 1         14 $fh->close;
322             }
323              
324             # Create the response object
325 32         257 $response = HTTP::Response->new(
326 32         91 $status_code, undef, $headers, join q{}, @{$body});
327             }
328             catch {
329             # Invalid PSGI response
330 9     9   587 my $error = "$_"; # stringify error
331              
332             # Remove line information from croak
333 9         59 $error =~ s{\s at \s .+ \z}{}msx;
334              
335             # Set the response
336 9         32 $response = _new_internal_response(
337             HTTP::Status::HTTP_EXPECTATION_FAILED,
338             $error,
339             );
340 41         531 };
341             }
342             else {
343             # Bad return value from handler
344 2         12 $response = _new_internal_response(
345             HTTP::Status::HTTP_EXPECTATION_FAILED,
346             'Override handler returned invalid value: ' . $response
347             );
348             }
349             }
350              
351 44         6986 return $response;
352             }
353             sub _is_invalid_psgi_header_key {
354 59     59   94 my ($key) = @_;
355              
356 59   100     890 return $key =~ m{(?:\A status \z | [:\n] | [_-] \z)}imsx
357             || $key !~ m{\A [a-z] [a-z0-9_-]* \z}imsx;
358             }
359             sub _is_invalid_psgi_header_value {
360 59     59   106 my ($value) = @_;
361              
362 59   100     3223 return ref $value ne q{} || $value =~ m{[\x00-\x19\x21-\x25]}imsx;
363             }
364             sub _new_internal_response {
365 24     24   54 my ($code, $message) = @_;
366              
367             # Make a new response
368 24         212 my $response = HTTP::Response->new($code, $message);
369              
370             # Set some headers for client information
371 24         1326 $response->header(
372             'Client-Date' => HTTP::Date::time2str(time),
373             'Client-Response-Source' => __PACKAGE__,
374             'Client-Warning' => 'Internal response',
375             'Content-Type' => 'text/plain',
376             );
377              
378             # Set the content as the status_line
379 24         5130 $response->content("$code $message");
380              
381 24         587 return $response;
382             }
383             sub _validate_psgi_response {
384 41     41   87 my ($psgi) = @_;
385              
386             # Unwrap the response
387 41         78 my ($code, $headers, $body) = @{$psgi};
  41         103  
388              
389 41 100       261 if ($code !~ m{\A [1-9] \d{2,} \z}msx) {
390 1         193 croak 'PSGI HTTP status code MUST be 100 or greater';
391             }
392              
393 40 100       164 if (ref $headers ne 'ARRAY') {
394 1         167 croak 'PSGI headers MUST be an array reference';
395             }
396              
397 39 100       83 if (@{$headers} % 2 != 0) {
  39         180  
398 1         162 croak 'PSGI headers MUST have even number of elements';
399             }
400              
401             # Headers copied
402 38         75 my @headers = @{$headers};
  38         118  
403              
404             # Hold invalid stuff
405 38         702 my (@invalid_header_keys, @invalid_header_values,
406             $has_content_type, $has_content_length);
407              
408 38         195 while (my ($key, $value) = splice @headers, 0, 2) {
409 59 100       179 if (_is_invalid_psgi_header_key($key)) {
    100          
    100          
410             # Remember the invalid key
411 3         8 push @invalid_header_keys, $key;
412             }
413             elsif (lc $key eq 'content-type') {
414             # The response has a defined content type
415 32         68 $has_content_type = 1;
416             }
417             elsif (lc $key eq 'content-length') {
418             # The response has a defined content length
419 2         4 $has_content_length = 1;
420             }
421              
422 59 100       227 if (_is_invalid_psgi_header_value($value)) {
423             # Remember the key of the invalid value
424 2         10 push @invalid_header_values, $key;
425             }
426             }
427              
428 38 100       198 if (@invalid_header_keys) {
429 1         174 croak 'PSGI headers have invalid key(s): ',
430             join q{, }, sort @invalid_header_keys;
431             }
432              
433 37 100       188 if (@invalid_header_values) {
434 1         164 croak 'PSGI headers have invalid value(s): ',
435             join q{, }, sort @invalid_header_values;
436             }
437              
438 36 100 66     171 if (!$has_content_type && $code !~ m{\A 1 | [23]04}msx) {
439 3         513 croak 'There MUST be a Content-Type for code other than 1xx, 204, and 304';
440             }
441              
442 33 100 100     171 if ($has_content_length && $code =~ m{\A 1 | [23]04}msx) {
443 1         163 croak 'There MUST NOT be a Content-Length for 1xx, 204, and 304';
444             }
445              
446             # Return true for successful check
447 32         96 return 1;
448             }
449              
450             1;
451              
452             __END__