File Coverage

blib/lib/LWP/UserAgent/Mockable.pm
Criterion Covered Total %
statement 96 101 95.0
branch 30 36 83.3
condition 5 5 100.0
subroutine 18 18 100.0
pod 6 6 100.0
total 155 166 93.3


line stmt bran cond sub pod time code
1             package LWP::UserAgent::Mockable;
2              
3 11     11   405667 use warnings;
  11         17  
  11         358  
4 11     11   40 use strict;
  11         13  
  11         208  
5              
6 11     11   4364 use Hook::LexWrap;
  11         11696  
  11         37  
7 11     11   806 use LWP::UserAgent;
  11         29269  
  11         223  
8 11     11   4239 use Safe::Isa '$_isa';
  11         3813  
  11         1147  
9 11     11   50 use Storable qw( dclone nstore retrieve );
  11         11  
  11         9229  
10              
11             our $VERSION = '1.16';
12              
13             my $instance = __PACKAGE__->__instance;
14             sub __instance {
15 11     11   14 my ( $class ) = @_;
16              
17 11 50       41 if ( not defined $instance ) {
18 11         45 $instance = bless {
19             action => undef,
20             file => undef,
21             current_request => undef,
22             actions => [],
23             callbacks => {},
24             wrappers => {},
25             }, $class;
26              
27             my $action = defined $ENV{ LWP_UA_MOCK }
28             ? lc $ENV{ LWP_UA_MOCK }
29 11 100       55 : 'passthrough';
30              
31 11         44 $instance->reset( $action, $ENV{ LWP_UA_MOCK_FILE } );
32             }
33              
34 9         267 return $instance;
35             }
36              
37             sub reset {
38 22     22 1 79 my ( $class, $action, $file ) = @_;
39              
40 22 50       24 if ( scalar @{ $instance->{ actions } } ) {
  22         159  
41 0         0 die "Can't reset state whilst pending actions. Need to call finish first";
42             }
43              
44 22 100       69 if ( not defined $action ) {
45 9         18 $action = "passthrough";
46             }
47              
48 22 50       112 if ( $action !~ /^(playback|record|passthrough)/ ) {
49 0         0 die "Action must be one of 'passthrough', 'playback' or 'record'";
50             }
51              
52 22 100 100     98 if ( $action ne 'passthrough' and not defined $file ) {
53 1         15 die "No file defined. Should point to file you wish to record to or playback from";
54             }
55              
56 21         35 $instance->{ wrappers } = {};
57 21         56 $instance->{ action } = $action;
58 21         120 $instance->{ file } = $file;
59 21         29 $instance->{ callbacks } = {};
60              
61 21         47 $instance->__reset;
62             }
63              
64             sub __reset {
65 21     21   25 my ( $self ) = @_;
66              
67             my ( $action, $file, $callbacks, $wrappers )
68 21         25 = @{ $self }{ qw( action file callbacks wrappers ) };
  21         45  
69              
70 21 100       49 if ( $action eq 'playback' ) {
71 5         8 local $Storable::Eval = 1;
72              
73 5         17 $self->{ actions } = retrieve( $file );
74              
75             $wrappers->{ pre } = wrap 'LWP::UserAgent::simple_request',
76             pre => sub {
77 18     18   41177 my ( $wrapped, $request ) = @_;
78              
79 18         19 my $current = shift @{ $self->{ actions } };
  18         40  
80 18 50       43 if ( not defined $current ) {
81 0         0 die "No further HTTP requests exist. You possibly need to re-record the LWP session";
82             }
83              
84 18         23 my $response = $current->{ response };
85              
86 18 100       38 if ( $callbacks->{ playback_validation } ) {
87 3         4 my $mock_request = $current->{ request };
88              
89 3         5 $callbacks->{ playback_validation }( $request, $mock_request );
90             }
91              
92 18 100       114 if ( $callbacks->{ playback }) {
93 1         3 $response = $callbacks->{ playback }( $request, $response );
94              
95 1 50       28 if ( not $response->$_isa( 'HTTP::Response' ) ) {
96 0         0 die "playback callback didn't return an HTTP::Response object";
97             }
98             }
99              
100 18         125 $_[ -1 ] = $response;
101 4         17026 };
102             } else {
103             $wrappers->{ pre } = wrap 'LWP::UserAgent::simple_request',
104             pre => sub {
105 27     27   108256 my ( $wrapped, $request ) = @_;
106              
107 27         63 $self->{ current_request } = $request;
108              
109 27 100       177 if ( $callbacks->{ pre_record } ) {
110 9         27 $_[ -1 ] = $callbacks->{ pre_record }( $request );
111              
112 9 100       357 if ( not $_[ -1 ]->$_isa( 'HTTP::Response' ) ) {
113 3         79 die "pre-record callback didn't return an HTTP::Response object";
114             }
115             }
116 16         95 };
117              
118             # It's intentional that wrap is called separately for this. We want the
119             # post action to always be called, even if the pre-action short-circuits
120             # the request. Otherwise, would need to duplicate the storing logic.
121             # This does mean that, when both pre- and post-record callbacks are being
122             # used, that the post-callback will take precedence.
123              
124             $wrappers->{ post } = wrap 'LWP::UserAgent::simple_request',
125             post => sub {
126 24     24   1068903 my $response = $_[ -1 ];
127 24 100       94 if ( $callbacks->{ record }) {
128             $response = $callbacks->{ record }(
129             $self->{ current_request },
130 9         42 $response
131             );
132              
133 9 100       202 if ( not $response->$_isa( 'HTTP::Response' ) ) {
134 3         81 die "record callback didn't return an HTTP::Response object";
135             }
136             }
137              
138 21 100       140 if ( $action eq 'record' ) {
139 13         24 local $Storable::Eval = 1;
140 13         18 local $Storable::Deparse = 1;
141              
142             my $cloned = dclone {
143             request => $self->{ current_request },
144 13         737 response => $response
145             };
146              
147 13         973 push @{ $self->{ actions } }, $cloned;
  13         76  
148             }
149 16         604 };
150             }
151             }
152              
153             sub finished {
154 9     9 1 7261 my ( $class ) = @_;
155              
156 9         23 my $action = $instance->{ action };
157              
158 9 100 100     57 if ( $action eq 'record' ) {
    50          
159 3         25 local $Storable::Deparse = 1;
160 3         5 local $Storable::Eval = 1;
161              
162 3         16 nstore $instance->{ actions }, $instance->{ file };
163 4         15 } elsif ( $action eq 'playback' and scalar @{ $instance->{ actions } } ) {
164 0         0 warn "Not all HTTP requests have been played back. You possibly need to re-record the LWP session";
165             }
166              
167 9         975 $instance->{ actions } = [];
168 9         97 $instance->{ action } = 'passthrough';
169 9         17 $instance->{ file } = undef;
170              
171 9         31 $instance->reset;
172             }
173              
174             sub set_playback_callback {
175 2     2 1 12574 my ( $class, $cb ) = @_;
176              
177 2         7 $instance->__set_cb( playback => $cb );
178             }
179              
180             sub set_record_callback {
181 15     15 1 5668 my ( $class, $cb ) = @_;
182              
183 15         40 $instance->__set_cb( record => $cb );
184             }
185              
186             sub set_record_pre_callback {
187 12     12 1 5373 my ( $class, $cb ) = @_;
188              
189 12         34 $instance->__set_cb( pre_record => $cb );
190             }
191              
192             sub set_playback_validation_callback {
193 1     1 1 871 my ( $class, $cb ) = @_;
194              
195 1         2 $instance->__set_cb( playback_validation => $cb );
196             }
197              
198             sub __set_cb {
199 30     30   43 my ( $self, $type, $cb ) = @_;
200              
201 30         72 $self->{ callbacks }{ $type } = $cb;
202             }
203              
204             1;
205              
206             __END__