File Coverage

blib/lib/Test/HTTP/Response.pm
Criterion Covered Total %
statement 88 108 81.4
branch 18 26 69.2
condition 3 16 18.7
subroutine 15 18 83.3
pod 10 10 100.0
total 134 178 75.2


line stmt bran cond sub pod time code
1             package Test::HTTP::Response;
2 3     3   208213 use strict;
  3         7  
  3         125  
3 3     3   15 use warnings;
  3         6  
  3         118  
4              
5             =head1 NAME
6              
7             Test::HTTP::Response - Perl testing module for HTTP responses
8              
9             =head1 SYNOPSIS
10              
11             use Test::HTTP::Response;
12              
13             ...
14              
15             status_matches($response, 200, 'Response is ok');
16              
17             status_ok($response);
18              
19             status_redirect($response);
20              
21             status_not_found($response);
22              
23             status_error($response);
24              
25             cookie_matches($response, { key => 'sessionid' },'sessionid exists ok'); # check matching cookie found in response
26              
27             my $cookies = extract_cookies($response);
28              
29             =head1 VERSION
30              
31             0.06
32              
33             =head1 DESCRIPTION
34              
35             Simple Perl testing module for HTTP responses and cookies, inspired by Test::HTTP and designed to work nicely with web framework test tools such as Plack::Test and Catalyst::Test
36              
37             =cut
38              
39 3     3   3173 use HTTP::Request;
  3         38904  
  3         101  
40 3     3   3837 use HTTP::Response;
  3         16871  
  3         69  
41 3     3   3151 use HTTP::Cookies;
  3         47227  
  3         162  
42              
43 3     3   31 use base qw( Exporter Test::Builder::Module);
  3         5  
  3         4643  
44              
45             our @EXPORT = qw(status_matches status_ok status_redirect status_not_found status_error
46             header_matches
47             headers_match
48             all_headers_match
49             cookie_matches extract_cookies);
50              
51             our $VERSION = '0.06';
52              
53             my $Test = Test::Builder->new;
54             my $CLASS = __PACKAGE__;
55              
56             =head1 FUNCTIONS
57              
58             =head2 status_matches
59              
60             Test that HTTP status of the response is (like) expected value
61              
62             status_matches($response, 200, 'Response is ok');
63              
64             Pass when status matches, fail when differs.
65              
66             Takes 3 arguments : response object, expected HTTP status code (or quoted-regexp pattern), comment.
67              
68             =head2 status_ok
69              
70             status_ok($response);
71              
72             Takes list of arguments : response object, optional comment
73              
74             Pass if response has status of 'OK', i.e. 200
75              
76             =head2 status_redirect
77              
78             status_redirect($response);
79              
80             Takes list of arguments : response object, optional comment
81              
82             Pass if response has status of 'REDIRECT', i.e. 301
83              
84             =head2 status_not_found
85              
86             status_not_found($response);
87              
88             Takes list of arguments : response object, optional comment
89              
90             Pass if response has status of 'NOT FOUND', i.e. 404
91              
92             =head2 status_error
93              
94             status_error($response);
95              
96             Takes list of arguments : response object, optional comment
97              
98             Pass if response has status of 'OK', i.e. 500
99              
100             =cut
101              
102             sub status_matches {
103 2     2 1 731 my ($response, $code, $comment, $diag) = @_;
104 2         8 my $tb = $CLASS->builder;
105 2 100       27 my $match = (ref($code) eq 'Regexp') ? $response->code =~ m/$code/ : $response->code == $code;
106 2         35 my $ok = $tb->ok( $match, $comment);
107 2 50       500 unless ($ok) {
108 0   0     0 $diag ||= "status doesn't match, expected HTTP status code '$code', got " . $response->code . "\n";
109 0         0 $tb->diag($diag);
110             }
111 2         26 return $ok;
112             }
113              
114             sub status_ok {
115 1     1 1 5 my ($response, $comment) = @_;
116 1   50     18 $comment ||= 'Response has HTTP OK (2xx) status';
117 1         4 my $diag = "status is not HTTP OK, expected 200 or similar, got " . $response->code . "\n";
118 1         18 return status_matches($response, qr/2\d\d/, $comment, $diag );
119             }
120              
121             sub status_redirect {
122 0     0 1 0 my ($response, $comment) = @_;
123 0   0     0 $comment ||= 'Response has HTTP REDIRECT (3xx) status';
124 0         0 my $diag = "status is not HTTP REDIRECT, expected 301 or similar, got " . $response->code . "\n";
125 0         0 return status_matches($response, qr/3\d\d/, $comment, $diag );
126             }
127              
128              
129             sub status_not_found {
130 0     0 1 0 my ($response, $comment) = @_;
131 0   0     0 $comment ||= 'Response has HTTP Not Found (404) status';
132 0         0 my $diag = "status is not HTTP Not Found, expected 404 or similar, got " . $response->code . "\n";
133 0         0 return status_matches($response, 404, $comment, $diag );
134             }
135              
136             sub status_error {
137 0     0 1 0 my ($response, $comment) = @_;
138 0   0     0 $comment ||= 'Response has HTTP Error (5xx) status';
139 0         0 my $diag = "status is not HTTP ERROR, expected 500 or similar, got " . $response->code . "\n";
140 0         0 return status_matches($response, qr/5\d\d/, $comment, $diag );
141             }
142              
143             =head2 header_matches
144              
145             header_matches($response, 'Content-type', 'Text/HTML', 'correct content type');
146              
147             =cut
148              
149             sub header_matches {
150 1     1 1 8 my ($response, $field, $value, $comment) = @_;
151              
152 1         5 my $tb = $CLASS->builder;
153 1 50       16 my $match = (ref($value) eq 'Regexp')
154             ? scalar $response->header($field) =~ $value
155             : scalar $response->header($field) eq $value;
156 1         51 my $ok = $tb->ok( $match, $comment);
157 1 50       272 unless ($ok) {
158 0         0 my $diag = "header doesn't match, expected HTTP header field $field to be '$value', got '" . $response->header($field) . "'\n";
159 0         0 $tb->diag($diag);
160             }
161 1         3 return $ok;
162             }
163              
164             =head2 headers_match
165              
166             Test a list of headers at once
167              
168             headers_match $response, {
169             'Content-Type' => /text/,
170             'Content-Length' => sub { $_ > 10 },
171             'Cache-Control' => 'private, no-cache, no-store',
172             };
173              
174             =cut
175              
176             sub headers_match {
177 6     6 1 2033 my ($response, $expected) = @_;
178              
179 6         35 my $tb = $CLASS->builder;
180              
181 6         75 for my $header (sort keys %$expected) {
182 13         2061 my $val = $response->header($header);
183 13         521 my $exp = $expected->{$header};
184              
185 13         14 my $ok;
186              
187 13 100       39 if(ref($exp) eq 'CODE') {
    100          
188 5         7 $_ = $val;
189 5         6 $ok = &{$exp}($val);
  5         13  
190             } elsif(ref($exp) eq 'Regexp') {
191 4         23 $ok = $val =~ $exp;
192             } else {
193 4         7 $ok = $val eq $exp;
194             }
195              
196 13         61 $tb->ok($ok, "HTTP header field $header matches");
197             }
198             }
199              
200             =head2 all_headers_match
201              
202             Test all headers in a response. Fails if any header field is left untested.
203              
204             all_headers_match $response, {
205             'Content-Type' => /text/,
206             'Content-Length' => sub { $_ > 10 },
207             'Cache-Control' => 'private, no-cache, no-store',
208             };
209              
210             =cut
211              
212             sub all_headers_match {
213 3     3 1 1561 my ($response, $expected) = @_;
214              
215 3         7 headers_match($response, $expected);
216              
217 3         856 my $tb = $CLASS->builder;
218              
219 3         25 $expected = { map { lc($_) => $expected->{$_} } keys %$expected };
  8         25  
220              
221 3         7 my $ok;
222 3         10 for my $header (sort map{ lc } $response->headers->header_field_names) {
  9         105  
223 9 100       27 unless($ok = exists $expected->{$header}) {
224 1         6 $tb->ok($ok, "Test for HTTP header field '$header'");
225 1         469 last;
226             }
227             }
228              
229 3         14 $tb->ok($ok, "Tests for all HTTP header fields");
230             }
231              
232             =head2 cookie_matches
233              
234             Test that a cookie with matching attributes is in the response headers
235              
236             cookie_matches($response, { key => 'sessionid' },'sessionid exists ok'); # check matching cookie found in response
237              
238             Passes when match found, fails if no matches found.
239              
240             Takes a list of arguments filename/response, hashref of attributes and strings or quoted-regexps to match, and optional test comment/name
241              
242             =cut
243              
244             sub cookie_matches {
245 4     4 1 2210 my ($response,$attr_ref,$name) = @_;
246 4         35 my $tb = $CLASS->builder;
247 4         48 my $cookies = _get_cookies($response);
248              
249 4         6 my $match = 0;
250 4         13 my $failure = 'no cookie matching key/name : ' . $attr_ref->{key};
251 4 50       14 if ($cookies->{$attr_ref->{key}}) {
252 4         6 $match = 1;
253 4         6 my $cookie_name = $attr_ref->{key};
254 4         21 foreach my $field ( sort keys %$attr_ref ) {
255 6         7 my $pattern = $attr_ref->{$field};
256 6 50       19 my $this_match = (ref($attr_ref->{$field}) eq 'Regexp') ?
257             $cookies->{$cookie_name}{$field} =~ m/$pattern/ : $cookies->{$cookie_name}{$field} eq $attr_ref->{$field} ;
258              
259 6 50       21 unless ($this_match) {
260 0         0 $match = 0;
261 0   0     0 $failure = join('',"$field doesn't match ", $attr_ref->{$field}, "got ", $cookies->{$cookie_name}{$field} || '' , "instead\n");
262 0         0 last;
263             }
264             }
265             }
266              
267 4         19 my $ok = $tb->ok( $match, $name);
268              
269 4 50       1469 unless ($ok) {
270 0         0 $tb->diag($failure);
271             }
272 4         175 return $ok;
273             }
274              
275             =head2 extract_cookies
276              
277             Get cookies from response as a nested hash
278              
279             my $cookies = extract_cookies($response);
280              
281             Takes 1 argument : HTTP::Response object
282              
283             Returns hashref
284              
285             =cut
286              
287             sub extract_cookies {
288 1     1 1 5 my ($response) = @_;
289 1         3 my $cookies = _get_cookies($response);
290 1         3 return $cookies;
291             }
292              
293              
294             ################
295              
296             my $cookies;
297              
298             sub _get_cookies {
299 5     5   7 my $response = shift;
300 5 100 66     37 if (ref $response and not defined $cookies->{"$response"}) {
301 2 50       10 unless ($response->request) {
302 2         34 $response->request(HTTP::Request->new(GET => 'http://www.example.com/'));
303             }
304 2         10654 my $cookie_jar = HTTP::Cookies->new;
305 2         56 $cookie_jar->extract_cookies($response);
306             $cookie_jar->scan( sub {
307 2     2   49 my %cookie = ();
308 2         20 @cookie{qw(version key value path domain port path domain port path_spec secure expires discard hash)} = @_;
309 2         15 $cookies->{"$response"}{$cookie{key}} = \%cookie;
310             }
311 2         1035 );
312             }
313              
314 5         88 return $cookies->{"$response"};
315             }
316              
317             =head1 SEE ALSO
318              
319             HTTP::Request
320              
321             LWP
322              
323             Plack::Test
324              
325             Catalyst::Test
326              
327             Test::HTML::Form
328              
329             Test::HTTP
330              
331             =head1 AUTHOR
332              
333             Aaron Trevena, Eteejay@cpan.orgE
334              
335             =head1 COPYRIGHT AND LICENSE
336              
337             Copyright (C) 2009 by Aaron Trevena
338              
339             This library is free software; you can redistribute it and/or modify
340             it under the same terms as Perl itself, either Perl version 5.10.0 or,
341             at your option, any later version of Perl 5 you may have available.
342              
343             =cut
344              
345             1;