File Coverage

blib/lib/Test/LWP/UserAgent.pm
Criterion Covered Total %
statement 157 176 89.2
branch 71 90 78.8
condition 19 29 65.5
subroutine 34 38 89.4
pod 11 11 100.0
total 292 344 84.8


line stmt bran cond sub pod time code
1 10     10   509755 use strict;
  10         30  
  10         384  
2 10     10   62 use warnings;
  10         24  
  10         609  
3             package Test::LWP::UserAgent; # git description: v0.032-3-g468b77d
4             # vim: set ts=8 sts=4 sw=4 tw=115 et :
5             # ABSTRACT: A LWP::UserAgent suitable for simulating and testing network calls
6             # KEYWORDS: testing useragent networking mock server client
7              
8             our $VERSION = '0.033';
9              
10 10     10   3652 use parent 'LWP::UserAgent';
  10         2408  
  10         80  
11 10     10   487913 use Scalar::Util qw(blessed reftype);
  10         32  
  10         688  
12 10     10   70 use Storable 'freeze';
  10         24  
  10         671  
13 10     10   67 use HTTP::Request;
  10         63  
  10         310  
14 10     10   62 use HTTP::Response;
  10         25  
  10         217  
15 10     10   57 use URI;
  10         29  
  10         261  
16 10     10   68 use HTTP::Date;
  10         24  
  10         589  
17 10     10   170 use HTTP::Status qw(:constants status_message);
  10         25  
  10         5034  
18 10     10   78 use Try::Tiny;
  10         24  
  10         488  
19 10     10   5572 use Safe::Isa;
  10         4392  
  10         1079  
20 10     10   78 use Carp;
  10         23  
  10         663  
21 10     10   4859 use namespace::clean 0.19 -also => [qw(__isa_coderef __is_regexp __isa_response)];
  10         150487  
  10         96  
22              
23             my @response_map;
24             my $network_fallback;
25             my $last_useragent;
26              
27             sub new
28             {
29 23     23 1 14321 my ($class, %options) = @_;
30              
31 23         86 my $_network_fallback = delete $options{network_fallback};
32              
33 23         246 my $self = $class->SUPER::new(%options);
34 23         28594 $self->{__last_http_request_sent} = undef;
35 23         113 $self->{__last_http_response_received} = undef;
36 23         84 $self->{__response_map} = [];
37 23         65 $self->{__network_fallback} = $_network_fallback;
38              
39             # strips default User-Agent header added by LWP::UserAgent, to make it
40             # easier to define literal HTTP::Requests to match against
41 23 50 33     107 $self->agent(undef) if defined $self->agent and $self->agent eq $self->_agent;
42              
43 23         4735 return $self;
44             }
45              
46             sub map_response
47             {
48 30     30 1 17049 my ($self, $request_specification, $response) = @_;
49              
50 30 100 66     146 if (not defined $response and blessed $self)
51             {
52             # mask a global domain mapping
53 4         7 my $matched;
54 4         7 foreach my $mapping (@{$self->{__response_map}})
  4         10  
55             {
56 6 100       47 if ($mapping->[0] eq $request_specification)
57             {
58 2         9 $matched = 1;
59 2         12 undef $mapping->[1];
60             }
61             }
62              
63 4 100       24 push @{$self->{__response_map}}, [ $request_specification, undef ]
  2         4  
64             if not $matched;
65              
66 4         10 return;
67             }
68              
69 26         87 my ($isa_response, $error_message) = __isa_response($response);
70 26 100       8330 if (not $isa_response)
71             {
72 3 100   3   20 if (try { $response->can('request') })
  3         115  
73             {
74 2         30 my $oldres = $response;
75 2     2   11 $response = sub { $oldres->request($_[0]) };
  2         21  
76             }
77             else
78             {
79 1         182 carp 'map_response: ', $error_message;
80             }
81             }
82              
83 26 100       188 if (blessed $self)
84             {
85 18         37 push @{$self->{__response_map}}, [ $request_specification, $response ];
  18         76  
86             }
87             else
88             {
89 8         20 push @response_map, [ $request_specification, $response ];
90             }
91 26         146 return $self;
92             }
93              
94             sub map_network_response
95             {
96 2     2 1 815 my ($self, $request_specification) = @_;
97              
98             push (
99 2 100       6 @{ blessed($self) ? $self->{__response_map} : \@response_map },
  2         22  
100             [ $request_specification, $self->_response_send_request ],
101             );
102              
103 2         9 return $self;
104             }
105              
106             sub unmap_all
107             {
108 3     3 1 5581 my ($self, $instance_only) = @_;
109              
110 3 100       21 if (blessed $self)
111             {
112 2         6 $self->{__response_map} = [];
113 2 100       44 @response_map = () unless $instance_only;
114             }
115             else
116             {
117 1 50       8 carp 'instance-only unmap requests make no sense when called globally'
118             if $instance_only;
119 1         11 @response_map = ();
120             }
121 3         13 return $self;
122             }
123              
124             sub register_psgi
125             {
126 0     0 1 0 my ($self, $domain, $app) = @_;
127              
128 0 0       0 return $self->map_response($domain, undef) if not defined $app;
129              
130 0 0       0 carp 'register_psgi: app is not a coderef, it\'s a ', ref($app)
131             unless __isa_coderef($app);
132              
133 0         0 return $self->map_response($domain, $self->_psgi_to_response($app));
134             }
135              
136             sub unregister_psgi
137             {
138 0     0 1 0 my ($self, $domain, $instance_only) = @_;
139              
140 0 0       0 if (blessed $self)
141             {
142 0         0 @{$self->{__response_map}} = grep { $_->[0] ne $domain } @{$self->{__response_map}};
  0         0  
  0         0  
  0         0  
143              
144 0 0       0 @response_map = grep { $_->[0] ne $domain } @response_map
  0         0  
145             unless $instance_only;
146             }
147             else
148             {
149 0         0 @response_map = grep { $_->[0] ne $domain } @response_map;
  0         0  
150             }
151 0         0 return $self;
152             }
153              
154             sub last_http_request_sent
155             {
156 29     29 1 16629 my $self = shift;
157             return blessed($self)
158             ? $self->{__last_http_request_sent}
159 29 50       213 : $last_useragent
    100          
160             ? $last_useragent->last_http_request_sent
161             : undef;
162             }
163              
164             sub last_http_response_received
165             {
166 16     16 1 11098 my $self = shift;
167             return blessed($self)
168             ? $self->{__last_http_response_received}
169 16 50       155 : $last_useragent
    100          
170             ? $last_useragent->last_http_response_received
171             : undef;
172             }
173              
174             sub last_useragent
175             {
176 23     23 1 10279 return $last_useragent;
177             }
178              
179             sub network_fallback
180             {
181 20     20 1 4090 my ($self, $value) = @_;
182              
183 20 100       100 if (@_ == 1)
184             {
185             return blessed $self
186             ? $self->{__network_fallback}
187 15 100       196 : $network_fallback;
188             }
189              
190 5 100       48 return $self->{__network_fallback} = $value if blessed $self;
191 2         10 $network_fallback = $value;
192             }
193              
194             sub send_request
195             {
196 42     42 1 192449 my ($self, $request, $arg, $size) = @_;
197              
198 42         267 $self->progress('begin', $request);
199 42         393 my $matched_response = $self->run_handlers('request_send', $request);
200              
201 42         1661 my $uri = $request->uri;
202              
203 42         410 foreach my $entry (@{$self->{__response_map}}, @response_map)
  42         184  
204             {
205 57 50       2310 last if $matched_response;
206 57 50       172 next if not defined $entry;
207 57         151 my ($request_desc, $response) = @$entry;
208              
209 57 100       188 if ($request_desc->$_isa('HTTP::Request'))
    100          
    100          
210             {
211 3         40 local $Storable::canonical = 1;
212 3 100       12 $matched_response = $response, last
213             if freeze($request) eq freeze($request_desc);
214             }
215             elsif (__is_regexp($request_desc))
216             {
217 25 100       134 $matched_response = $response, last
218             if $uri =~ $request_desc;
219             }
220             elsif (__isa_coderef($request_desc))
221             {
222 5 100       74 $matched_response = $response, last
223             if $request_desc->($request);
224             }
225             else
226             {
227 24 50       937 $uri = URI->new($uri) if not $uri->$_isa('URI');
228 24 100       452 $matched_response = $response, last
229             if $uri->host eq $request_desc;
230             }
231             }
232              
233 42         1850 $last_useragent = $self;
234 42         348 $self->{__last_http_request_sent} = $request;
235              
236 42 100 100     317 if (not defined $matched_response and
      66        
237             ($self->{__network_fallback} or $network_fallback))
238             {
239 8         63 my $response = $self->SUPER::send_request($request, $arg, $size);
240 8         1697977 $self->{__last_http_response_received} = $response;
241 8         94 return $response;
242             }
243              
244 34 100       164 my $response = defined $matched_response
245             ? $matched_response
246             : HTTP::Response->new('404');
247              
248 34 100       872 if (__isa_coderef($response))
249             {
250             # emulates handling in LWP::UserAgent::send_request
251 11 100       115 if ($self->use_eval)
252             {
253 10     10   639 $response = try { $response->($request) }
254             catch {
255 2     2   144 my $exception = $_;
256 2 100       9 if ($exception->$_isa('HTTP::Response'))
257             {
258 1         31 $response = $exception;
259             }
260             else
261             {
262 1         10 my $full = $exception;
263 1         7 (my $status = $exception) =~ s/\n.*//s;
264 1         6 $status =~ s/ at .* line \d+.*//s; # remove file/line number
265 1 50       6 my $code = ($status =~ s/^(\d\d\d)\s+//) ? $1 : HTTP_INTERNAL_SERVER_ERROR;
266             # note that _new_response did not always take a fourth
267             # parameter - content used to always be "$code $message"
268 1         5 $response = LWP::UserAgent::_new_response($request, $code, $status, $full);
269             }
270             }
271 10         301 }
272             else
273             {
274 1         21 $response = $response->($request);
275             }
276             }
277              
278 33 100       377317 if (not $response->$_isa('HTTP::Response'))
279             {
280 2   33     615 carp 'response from coderef is not a HTTP::Response, it\'s a ',
281             (blessed($response) || ( ref($response) ? ('unblessed ' . ref($response)) : 'non-reference' ));
282              
283 2         201 $response = LWP::UserAgent::_new_response($request, HTTP_INTERNAL_SERVER_ERROR, status_message(HTTP_INTERNAL_SERVER_ERROR));
284             }
285             else
286             {
287 31         735 $response->request($request); # record request for reference
288 31         568 $response->header('Client-Date' => HTTP::Date::time2str(time));
289             }
290              
291             # handle any additional arguments that were provided, such as saving the
292             # content to a file. this also runs additional handlers for us.
293 33         4848 my $protocol = LWP::Protocol->new('no-schemes-from-TLWPUA', $self);
294 33         456 my $complete;
295             $response = $protocol->collect($arg, $response, sub {
296             # remove content from $response and stream it back
297 41 100   41   19414 return \'' if $complete;
298 33         170 my $content = $response->content;
299 33         596 $response->content('');
300 33         859 $complete++;
301 33         117 \$content;
302 33         283 });
303              
304 33         1569 $self->run_handlers('response_done', $response);
305 33         1575 $self->progress('end', $response);
306              
307 33         228 $self->{__last_http_response_received} = $response;
308              
309 33         373 return $response;
310             }
311              
312             # turns a PSGI app into a subref returning an HTTP::Response
313             sub _psgi_to_response
314             {
315 0     0   0 my ($self, $app) = @_;
316              
317 0 0 0     0 carp 'register_psgi: did you forget to load HTTP::Message::PSGI?'
318             unless HTTP::Request->can('to_psgi') and HTTP::Response->can('from_psgi');
319              
320 0     0   0 return sub { HTTP::Response->from_psgi($app->($_[0]->to_psgi)) };
  0         0  
321             }
322              
323             # returns a subref that returns an HTTP::Response from a real network request
324             sub _response_send_request
325             {
326 2     2   8 my $self = shift;
327              
328             # we cannot call ::request here, or we end up in an infinite loop
329 2 100   1   19 return sub { $self->SUPER::send_request($_[0]) } if blessed $self;
  1         7  
330 1     1   5 return sub { LWP::UserAgent->new->send_request($_[0]) };
  1         9  
331             }
332              
333             sub __isa_coderef
334             {
335 95 100 100 95   3938 ref $_[0] eq 'CODE'
      100        
336             or (reftype($_[0]) || '') eq 'CODE'
337             or overload::Method($_[0], '&{}')
338             }
339              
340             sub __is_regexp
341             {
342 54 50   54   1162 re->can('is_regexp') ? re::is_regexp($_[0]) : ref($_[0]) eq 'Regexp';
343             }
344              
345             # returns true if is expected type for all response mappings,
346             # or (false, error message);
347             sub __isa_response
348             {
349 26 100 100 26   85 __isa_coderef($_[0]) || $_[0]->$_isa('HTTP::Response')
      66        
350             ? (1)
351             : (0, 'response is not a coderef or an HTTP::Response, it\'s a '
352             . (blessed($_[0]) || ( ref($_[0]) ? 'unblessed ' . ref($_[0]) : 'non-reference' )));
353             }
354              
355             1;
356              
357             __END__