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   1170452 use strict;
  10         121  
  10         337  
2 10     10   61 use warnings;
  10         20  
  10         617  
3             package Test::LWP::UserAgent; # git description: v0.035-2-g1324b10
4             # vim: set ts=8 sts=2 sw=2 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.036';
9              
10 10     10   3213 use parent 'LWP::UserAgent';
  10         2563  
  10         63  
11 10     10   404654 use Scalar::Util qw(blessed reftype);
  10         29  
  10         795  
12 10     10   7512 use Storable 'freeze';
  10         36231  
  10         1014  
13 10     10   105 use HTTP::Request;
  10         24  
  10         295  
14 10     10   61 use HTTP::Response;
  10         23  
  10         214  
15 10     10   52 use URI;
  10         20  
  10         212  
16 10     10   50 use HTTP::Date;
  10         18  
  10         582  
17 10     10   66 use HTTP::Status qw(:constants status_message);
  10         20  
  10         5347  
18 10     10   83 use Try::Tiny;
  10         24  
  10         617  
19 10     10   5611 use Safe::Isa;
  10         5445  
  10         1095  
20 10     10   76 use Carp;
  10         20  
  10         745  
21 10     10   5924 use namespace::clean 0.19 -also => [qw(__isa_coderef __is_regexp __isa_response)];
  10         153313  
  10         109  
22              
23             my @response_map;
24             my $network_fallback;
25             my $last_useragent;
26              
27             sub new
28             {
29 23     23 1 17563 my ($class, %options) = @_;
30              
31 23         80 my $_network_fallback = delete $options{network_fallback};
32              
33 23         218 my $self = $class->SUPER::new(%options);
34 23         27447 $self->{__last_http_request_sent} = undef;
35 23         68 $self->{__last_http_response_received} = undef;
36 23         62 $self->{__response_map} = [];
37 23         59 $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     80 $self->agent(undef) if defined $self->agent and $self->agent eq $self->_agent;
42              
43 23         3608 return $self;
44             }
45              
46             sub map_response
47             {
48 30     30 1 24157 my ($self, $request_specification, $response) = @_;
49              
50 30 100 66     119 if (not defined $response and blessed $self)
51             {
52             # mask a global domain mapping
53 4         10 my $matched;
54 4         9 foreach my $mapping (@{$self->{__response_map}})
  4         17  
55             {
56 6 100       66 if ($mapping->[0] eq $request_specification)
57             {
58 2         11 $matched = 1;
59 2         15 undef $mapping->[1];
60             }
61             }
62              
63 4 100       28 push @{$self->{__response_map}}, [ $request_specification, undef ]
  2         6  
64             if not $matched;
65              
66 4         13 return;
67             }
68              
69 26         85 my ($isa_response, $error_message) = __isa_response($response);
70 26 100       1676 if (not $isa_response)
71             {
72 3 100   3   14 if (try { $response->can('request') })
  3         94  
73             {
74 2         24 my $oldres = $response;
75 2     2   7 $response = sub { $oldres->request($_[0]) };
  2         9  
76             }
77             else
78             {
79 1         213 carp 'map_response: ', $error_message;
80             }
81             }
82              
83 26 100       194 if (blessed $self)
84             {
85 18         30 push @{$self->{__response_map}}, [ $request_specification, $response ];
  18         70  
86             }
87             else
88             {
89 8         24 push @response_map, [ $request_specification, $response ];
90             }
91 26         80 return $self;
92             }
93              
94             sub map_network_response
95             {
96 2     2 1 852 my ($self, $request_specification) = @_;
97              
98             push (
99 2 100       3 @{ 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 4620 my ($self, $instance_only) = @_;
109              
110 3 100       20 if (blessed $self)
111             {
112 2         13 $self->{__response_map} = [];
113 2 100       59 @response_map = () unless $instance_only;
114             }
115             else
116             {
117 1 50       6 carp 'instance-only unmap requests make no sense when called globally'
118             if $instance_only;
119 1         6 @response_map = ();
120             }
121 3         10 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 16535 my $self = shift;
157             return blessed($self)
158             ? $self->{__last_http_request_sent}
159 29 50       560 : $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 9455 my $self = shift;
167             return blessed($self)
168             ? $self->{__last_http_response_received}
169 16 50       164 : $last_useragent
    100          
170             ? $last_useragent->last_http_response_received
171             : undef;
172             }
173              
174             sub last_useragent
175             {
176 23     23 1 11797 return $last_useragent;
177             }
178              
179             sub network_fallback
180             {
181 20     20 1 2906 my ($self, $value) = @_;
182              
183 20 100       60 if (@_ == 1)
184             {
185             return blessed $self
186             ? $self->{__network_fallback}
187 15 100       117 : $network_fallback;
188             }
189              
190 5 100       31 return $self->{__network_fallback} = $value if blessed $self;
191 2         9 $network_fallback = $value;
192             }
193              
194             sub send_request
195             {
196 42     42 1 184048 my ($self, $request, $arg, $size) = @_;
197              
198 42         218 $self->progress('begin', $request);
199 42         306 my $matched_response = $self->run_handlers('request_send', $request);
200              
201 42         1675 my $uri = $request->uri;
202              
203 42         333 foreach my $entry (@{$self->{__response_map}}, @response_map)
  42         173  
204             {
205 57 50       2507 last if $matched_response;
206 57 50       197 next if not defined $entry;
207 57         155 my ($request_desc, $response) = @$entry;
208              
209 57 100       193 if ($request_desc->$_isa('HTTP::Request'))
    100          
    100          
210             {
211 3         46 local $Storable::canonical = 1;
212 3 100       35 $matched_response = $response, last
213             if freeze($request) eq freeze($request_desc);
214             }
215             elsif (__is_regexp($request_desc))
216             {
217 25 100       142 $matched_response = $response, last
218             if $uri =~ $request_desc;
219             }
220             elsif (__isa_coderef($request_desc))
221             {
222 5 100       95 $matched_response = $response, last
223             if $request_desc->($request);
224             }
225             else
226             {
227 24 50       955 $uri = URI->new($uri) if not $uri->$_isa('URI');
228 24 100       436 $matched_response = $response, last
229             if $uri->host eq $request_desc;
230             }
231             }
232              
233 42         2198 $last_useragent = $self;
234 42         112 $self->{__last_http_request_sent} = $request;
235              
236 42 100 100     265 if (not defined $matched_response and
      100        
237             ($self->{__network_fallback} or $network_fallback))
238             {
239 8         43 my $response = $self->SUPER::send_request($request, $arg, $size);
240 8         3781561 $self->{__last_http_response_received} = $response;
241 8         54 return $response;
242             }
243              
244 34 100       139 my $response = defined $matched_response
245             ? $matched_response
246             : HTTP::Response->new('404');
247              
248 34 100       667 if (__isa_coderef($response))
249             {
250             # emulates handling in LWP::UserAgent::send_request
251 11 100       112 if ($self->use_eval)
252             {
253 10     10   455 $response = try { $response->($request) }
254             catch {
255 2     2   86 my $exception = $_;
256 2 100       5 if ($exception->$_isa('HTTP::Response'))
257             {
258 1         16 $response = $exception;
259             }
260             else
261             {
262 1         21 my $full = $exception;
263 1         8 (my $status = $exception) =~ s/\n.*//s;
264 1         7 $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         225 }
272             else
273             {
274 1         14 $response = $response->($request);
275             }
276             }
277              
278 33 100       1159434 if (not $response->$_isa('HTTP::Response'))
279             {
280 2   33     339 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         84 $response = LWP::UserAgent::_new_response($request, HTTP_INTERNAL_SERVER_ERROR, status_message(HTTP_INTERNAL_SERVER_ERROR));
284             }
285             else
286             {
287 31         645 $response->request($request); # record request for reference
288 31         468 $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         4179 my $protocol = LWP::Protocol->new('no-schemes-from-TLWPUA', $self);
294 33         404 my $complete;
295             $response = $protocol->collect($arg, $response, sub {
296             # remove content from $response and stream it back
297 41 100   41   15440 return \'' if $complete;
298 33         143 my $content = $response->content;
299 33         592 $response->content('');
300 33         796 $complete++;
301 33         103 \$content;
302 33         267 });
303              
304 33         758 $self->run_handlers('response_done', $response);
305 33         1799 $self->progress('end', $response);
306              
307 33         254 $self->{__last_http_response_received} = $response;
308              
309 33         280 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   6 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         4  
330 1     1   6 return sub { LWP::UserAgent->new->send_request($_[0]) };
  1         8  
331             }
332              
333             sub __isa_coderef
334             {
335 95 100 100 95   3238 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   1265 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   111 __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__