File Coverage

blib/lib/Test/LWP/UserAgent.pm
Criterion Covered Total %
statement 157 173 90.7
branch 71 90 78.8
condition 20 29 68.9
subroutine 34 38 89.4
pod 11 11 100.0
total 293 341 85.9


line stmt bran cond sub pod time code
1 10     10   1123665 use strict;
  10         107  
  10         310  
2 10     10   56 use warnings;
  10         19  
  10         562  
3             package Test::LWP::UserAgent; # git description: v0.033-10-g54ec8f2
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.034';
9              
10 10     10   3223 use parent 'LWP::UserAgent';
  10         2152  
  10         88  
11 10     10   408655 use Scalar::Util qw(blessed reftype);
  10         28  
  10         538  
12 10     10   67 use Storable 'freeze';
  10         21  
  10         661  
13 10     10   69 use HTTP::Request;
  10         25  
  10         266  
14 10     10   56 use HTTP::Response;
  10         20  
  10         208  
15 10     10   49 use URI;
  10         20  
  10         224  
16 10     10   48 use HTTP::Date;
  10         18  
  10         592  
17 10     10   68 use HTTP::Status qw(:constants status_message);
  10         18  
  10         5008  
18 10     10   80 use Try::Tiny;
  10         23  
  10         516  
19 10     10   4996 use Safe::Isa;
  10         4816  
  10         1127  
20 10     10   110 use Carp;
  10         20  
  10         652  
21 10     10   4904 use namespace::clean 0.19 -also => [qw(__isa_coderef __is_regexp __isa_response)];
  10         135685  
  10         105  
22              
23             my @response_map;
24             my $network_fallback;
25             my $last_useragent;
26              
27             sub new
28             {
29 23     23 1 153484 my ($class, %options) = @_;
30              
31 23         65 my $_network_fallback = delete $options{network_fallback};
32              
33 23         170 my $self = $class->SUPER::new(%options);
34 23         25621 $self->{__last_http_request_sent} = undef;
35 23         61 $self->{__last_http_response_received} = undef;
36 23         57 $self->{__response_map} = [];
37 23         55 $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     73 $self->agent(undef) if defined $self->agent and $self->agent eq $self->_agent;
42              
43 23         3518 return $self;
44             }
45              
46             sub map_response
47             {
48 30     30 1 21268 my ($self, $request_specification, $response) = @_;
49              
50 30 100 66     124 if (not defined $response and blessed $self)
51             {
52             # mask a global domain mapping
53 4         8 my $matched;
54 4         6 foreach my $mapping (@{$self->{__response_map}})
  4         10  
55             {
56 6 100       52 if ($mapping->[0] eq $request_specification)
57             {
58 2         9 $matched = 1;
59 2         12 undef $mapping->[1];
60             }
61             }
62              
63 4 100       21 push @{$self->{__response_map}}, [ $request_specification, undef ]
  2         7  
64             if not $matched;
65              
66 4         11 return;
67             }
68              
69 26         71 my ($isa_response, $error_message) = __isa_response($response);
70 26 100       1441 if (not $isa_response)
71             {
72 3 100   3   14 if (try { $response->can('request') })
  3         94  
73             {
74 2         25 my $oldres = $response;
75 2     2   9 $response = sub { $oldres->request($_[0]) };
  2         10  
76             }
77             else
78             {
79 1         221 carp 'map_response: ', $error_message;
80             }
81             }
82              
83 26 100       170 if (blessed $self)
84             {
85 18         33 push @{$self->{__response_map}}, [ $request_specification, $response ];
  18         62  
86             }
87             else
88             {
89 8         22 push @response_map, [ $request_specification, $response ];
90             }
91 26         67 return $self;
92             }
93              
94             sub map_network_response
95             {
96 2     2 1 721 my ($self, $request_specification) = @_;
97              
98             push (
99 2 100       5 @{ blessed($self) ? $self->{__response_map} : \@response_map },
  2         20  
100             [ $request_specification, $self->_response_send_request ],
101             );
102              
103 2         8 return $self;
104             }
105              
106             sub unmap_all
107             {
108 3     3 1 3860 my ($self, $instance_only) = @_;
109              
110 3 100       16 if (blessed $self)
111             {
112 2         9 $self->{__response_map} = [];
113 2 100       33 @response_map = () unless $instance_only;
114             }
115             else
116             {
117 1 50       4 carp 'instance-only unmap requests make no sense when called globally'
118             if $instance_only;
119 1         8 @response_map = ();
120             }
121 3         11 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  
143              
144 0 0       0 @response_map = grep $_->[0] ne $domain, @response_map
145             unless $instance_only;
146             }
147             else
148             {
149 0         0 @response_map = grep $_->[0] ne $domain, @response_map;
150             }
151 0         0 return $self;
152             }
153              
154             sub last_http_request_sent
155             {
156 29     29 1 14347 my $self = shift;
157             return blessed($self)
158             ? $self->{__last_http_request_sent}
159 29 50       173 : $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 7489 my $self = shift;
167             return blessed($self)
168             ? $self->{__last_http_response_received}
169 16 50       113 : $last_useragent
    100          
170             ? $last_useragent->last_http_response_received
171             : undef;
172             }
173              
174             sub last_useragent
175             {
176 23     23 1 10032 return $last_useragent;
177             }
178              
179             sub network_fallback
180             {
181 20     20 1 2264 my ($self, $value) = @_;
182              
183 20 100       55 if (@_ == 1)
184             {
185             return blessed $self
186             ? $self->{__network_fallback}
187 15 100       108 : $network_fallback;
188             }
189              
190 5 100       28 return $self->{__network_fallback} = $value if blessed $self;
191 2         7 $network_fallback = $value;
192             }
193              
194             sub send_request
195             {
196 42     42 1 166991 my ($self, $request, $arg, $size) = @_;
197              
198 42         191 $self->progress('begin', $request);
199 42         272 my $matched_response = $self->run_handlers('request_send', $request);
200              
201 42         1614 my $uri = $request->uri;
202              
203 42         305 foreach my $entry (@{$self->{__response_map}}, @response_map)
  42         147  
204             {
205 57 50       2139 last if $matched_response;
206 57 50       136 next if not defined $entry;
207 57         126 my ($request_desc, $response) = @$entry;
208              
209 57 100       157 if ($request_desc->$_isa('HTTP::Request'))
    100          
    100          
210             {
211 3         44 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       136 $matched_response = $response, last
218             if $uri =~ $request_desc;
219             }
220             elsif (__isa_coderef($request_desc))
221             {
222 5 100       91 $matched_response = $response, last
223             if $request_desc->($request);
224             }
225             else
226             {
227 24 50       777 $uri = URI->new($uri) if not $uri->$_isa('URI');
228 24 100       385 $matched_response = $response, last
229             if $uri->host eq $request_desc;
230             }
231             }
232              
233 42         2070 $last_useragent = $self;
234 42         106 $self->{__last_http_request_sent} = $request;
235              
236 42 100 100     219 if (not defined $matched_response and
      100        
237             ($self->{__network_fallback} or $network_fallback))
238             {
239 8         44 my $response = $self->SUPER::send_request($request, $arg, $size);
240 8         1721508 $self->{__last_http_response_received} = $response;
241 8         55 return $response;
242             }
243              
244 34 100       120 my $response = defined $matched_response
245             ? $matched_response
246             : HTTP::Response->new('404');
247              
248 34 100       613 if (__isa_coderef($response))
249             {
250             # emulates handling in LWP::UserAgent::send_request
251 11 100       103 if ($self->use_eval)
252             {
253 10     10   439 $response = try { $response->($request) }
254             catch {
255 2     2   83 my $exception = $_;
256 2 100       6 if ($exception->$_isa('HTTP::Response'))
257             {
258 1         17 $response = $exception;
259             }
260             else
261             {
262 1         23 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       5 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         206 }
272             else
273             {
274 1         15 $response = $response->($request);
275             }
276             }
277              
278 33 100       545408 if (not $response->$_isa('HTTP::Response'))
279             {
280 2   33     361 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         126 $response = LWP::UserAgent::_new_response($request, HTTP_INTERNAL_SERVER_ERROR, status_message(HTTP_INTERNAL_SERVER_ERROR));
284             }
285             else
286             {
287 31         602 $response->request($request); # record request for reference
288 31         451 $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         3590 my $protocol = LWP::Protocol->new('no-schemes-from-TLWPUA', $self);
294 33         344 my $complete;
295             $response = $protocol->collect($arg, $response, sub {
296             # remove content from $response and stream it back
297 41 100   41   13537 return \'' if $complete;
298 33         133 my $content = $response->content;
299 33         497 $response->content('');
300 33         719 $complete++;
301 33         95 \$content;
302 33         229 });
303              
304 33         780 $self->run_handlers('response_done', $response);
305 33         2164 $self->progress('end', $response);
306              
307 33         235 $self->{__last_http_response_received} = $response;
308              
309 33         212 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   5 my $self = shift;
327              
328             # we cannot call ::request here, or we end up in an infinite loop
329 2 100   1   14 return sub { $self->SUPER::send_request($_[0]) } if blessed $self;
  1         6  
330 1     1   6 return sub { LWP::UserAgent->new->send_request($_[0]) };
  1         9  
331             }
332              
333             sub __isa_coderef
334             {
335 95 100 100 95   2038 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   1026 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   72 __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__