File Coverage

blib/lib/Test/HTTP/Syntax.pm
Criterion Covered Total %
statement 57 82 69.5
branch 25 56 44.6
condition 1 3 33.3
subroutine 11 11 100.0
pod 0 4 0.0
total 94 156 60.2


line stmt bran cond sub pod time code
1             package Test::HTTP::Syntax;
2 3     3   1656 use warnings;
  3         6  
  3         107  
3 3     3   18 use strict;
  3         6  
  3         174  
4              
5             =head1 NAME
6              
7             Test::HTTP::Syntax - HTTP tests in a natural style.
8              
9             =head1 SYNOPSIS
10              
11             use Test::HTTP::Syntax;
12             use Test::HTTP tests => 9;
13              
14             or
15              
16             use Test::HTTP '-syntax', tests => 9;
17              
18             then
19              
20             test_http 'echo test' {
21             >> GET /echo/foo
22             >> Accept: text/plain
23              
24             << 200
25             ~< Content-type: ^text/plain\b
26             <<
27             << foo
28             }
29              
30              
31             =head1 DESCRIPTION
32              
33             L is a source filter module designed to work with
34             L. It provides a simple, linewise syntax for specifying HTTP
35             tests in a way that looks a lot like HTTP request and response packets.
36              
37             All this module does is translate the linewise packet syntax into calls to
38             L.
39              
40             The actual module used for the tests can be set by setting the variable
41             C<$Test::HTTP::Syntax::Test_package>. It defaults to C.
42              
43             =head1 SYNTAX
44              
45             =head2 test_http block
46              
47             L only filters sections of code which are delimited by a
48             C block.
49              
50             test_http TEST_NAME {
51             # Code to be filtered
52             # ...
53             }
54              
55             This gets translated into
56              
57             {
58             my $test = Test::HTTP->new(TEST_NAME);
59             # Filtered code
60             # ...
61             }
62              
63             =head2 REQUESTS
64              
65             A request packet consists of a REQUEST START line, 0 or more REQUEST HEADER
66             lines, and an optional REQUEST BODY. The packet ends when a blank line is
67             encountered.
68              
69             The presence of a REQUEST packet only constructs the request within C<$test>.
70             The request does not get run unless a RESPONSE packet is encountered or
71             C<< $test->run_request() >> is called explicitly.
72              
73             =head3 REQUEST START
74              
75             This line marks the start of a request block.
76              
77             >> METHOD URI
78              
79             C is one of C, C, C, C, or C, and C
80             is a URI. This line is followed by 0 or more REQUEST HEADERS, and then
81             optionally a REQUEST BODY.
82              
83             =head3 REQUEST HEADER
84              
85             >> HEADER: VALUE
86              
87             This sets the value of an HTTP request header.
88              
89             =head3 REQUEST BODY
90              
91             >>
92             >> body line 1
93             >> body line 2
94              
95             This sets the contents of the body of the HTTP packet.
96              
97             =head2 RESPONSES
98              
99             A response packet consists of a RESPONSE START line, 0 or more LITERAL or
100             REGEX RESPONSE HEADER lines, and an optional RESPONSE BODY.
101              
102             The start of a response packet triggers the execution of the pending request,
103             and starts testing the response received therefrom.
104              
105             =head3 RESPONSE START
106              
107             << NNN
108              
109             C is a 3-digit HTTP response code which we expect to receive.
110              
111             =head3 LITERAL RESPONSE HEADER
112              
113             << HEADER: VALUE
114              
115             Performs a literal match on the value of the C
header in the HTTP
116             response packet.
117              
118             =head3 REGEX RESPONSE HEADER
119              
120             ~< HEADER: REGEX
121              
122             Performs a regular expression match on the value of C
against the
123             REGEX qr{REGEX}.
124              
125             =head3 RESPONSE BODY
126              
127             <<
128             << body line 1
129             << body line 2
130              
131             Performs a literal match on the given body with the body of the HTTP response
132             packet.
133              
134             =cut
135              
136 3     3   4048 use Filter::Simple;
  3         123524  
  3         32  
137 3     3   193 use Text::Balanced ':ALL';
  3         8  
  3         1598  
138              
139             our $Test_package = 'Test::HTTP';
140              
141             FILTER {
142             my $result;
143             my $n;
144              
145             while ($_) {
146             if (s/^\s*test_http\s+(.*?)\s+{/{/) {
147             my $name = $1;
148             my $block;
149             ($block, $_) = extract_bracketed($_, '{}');
150             $result .= filter_block( $name, $block );
151             }
152             else {
153             s/^.*\n//;
154             $result .= "$&\n";
155             }
156             }
157              
158             $_ = $result;
159             };
160              
161             # The current state of the input block is kept in @lines, while output is
162             # built in $result. When filter_block finds the start of a request, it passes
163             # off to filter_request, and when it finds the start of a response
164             # specification, it passes it off to filter_response.
165             #
166             # Each of these two, in turn, is a linewise finite state machine.
167             {
168             # This quells the warning from using a 'last' to exit a 'while_line' loop.
169 3     3   21 no warnings 'exiting';
  3         5  
  3         5539  
170              
171             my @lines;
172             my $result;
173              
174             sub while_line(&) {
175 6     6 0 13 my ( $coderef ) = @_;
176              
177 6         50 while (defined(local $_ = shift @lines)) { $coderef->() }
  20         39  
178             }
179              
180             sub filter_block {
181 3     3 0 7 my ( $name, $block ) = @_;
182              
183 3         24 $block =~ s{^\{\n}
184             {\{
185             my \$test = $Test_package->new($name);
186             };
187 3         15 $block =~ s/\}\z//;
188              
189 3         5 $result = '';
190 3         26 @lines = split /\n/, $block;
191             while_line {
192 14 100   14   54 if (/^\s*>> /) {
    100          
193 3         8 unshift @lines, $_;
194 3         6 filter_request();
195             }
196             elsif (/^\s*<< /) {
197 3         6 unshift @lines, $_;
198 3         8 filter_response();
199             }
200             else {
201 8         35 $result .= "$_\n";
202             }
203 3         37 };
204              
205 3         12 $result .= "}\n";
206              
207 3         14 return $result;
208             }
209              
210             sub filter_request {
211 3     3 0 5 my $state = 'first line';
212 3         5 my @body;
213              
214             while_line {
215 6 50   6   57 next if /^\s*#/;
216 6 100       21 if ( $state eq 'first line' ) {
    50          
    0          
217 3 50       15 /^\s*>> (\S+) (.*)/
218             or die "unparseable first request line: '$_'\n";
219 3         15 $result .= " \$test->new_request($1 => \"$2\");\n";
220 3         10 $state = 'in request';
221             }
222             elsif ( $state eq 'in request' ) {
223 3 50       21 if (/^\s*>>\s*$/) {
    50          
    50          
224 0         0 $state = 'in body';
225             }
226             elsif (/^\s*>> ([A-Za-z-]+): (.*)/) {
227 0         0 $result
228             .= " \$test->request->header(\"$1\" => \"$2\");\n";
229             }
230             elsif (/^\s*$/) {
231 3         7 $result .= "$_\n";
232 3         7 last;
233             }
234             else {
235 0         0 die "unparseable line in request: '$_'\n";
236             }
237             }
238             elsif ( $state eq 'in body' ) {
239 0 0       0 if (/^\s*>> (.*)/) {
    0          
240 0         0 push @body, $1;
241             } elsif (/^\s*$/) {
242 0         0 $result .= "$_\n";
243 0         0 last;
244             }
245             else {
246 0         0 die "unparseable line in request body: '$_'\n";
247             }
248             }
249 3         17 };
250 3 50       36 if (@body) {
251 0         0 my $body = join "\n", @body;
252 0         0 $result .= <
253             {
254             local \$_ = <
255             $body
256             END_OF_BODY
257             s/\\n\\z//g; # Remove newline before END_OF_BODY marker.
258             \$test->request->content( \$_ );
259             }
260             END_OF_CODE
261             }
262             }
263              
264             sub filter_response {
265 3     3 0 4 my $state = 'first line';
266 3         5 my @body_exact;
267             my @body_res;
268              
269 3         10 while (defined(local $_ = shift @lines)) {
270 4 50       12 next if /^\s*#/;
271 4 100       20 if ($state eq 'first line') {
    50          
    0          
272 3 50       18 /^\s*<< (\d+)\s*$/
273             or die "unparseable first response line: '$_'\n";
274 3         5 $result .= " \$test->run_request();\n";
275 3         10 $result .= " \$test->status_code_is($1);\n";
276 3         13 $state = 'in header';
277             }
278             elsif ($state eq 'in header') {
279 1 50       9 if (/^\s*<< ([A-Za-z-]+): (.*)/) {
    50          
    50          
    50          
280 0         0 $result .= " \$test->header_is( \"$1\", \"$2\" );\n";
281             }
282             elsif (/^\s*~< ([A-Za-z-]+): (.*)/) {
283 0         0 $result .= " \$test->header_like( \"$1\", qr{$2} );\n";
284             }
285             elsif (/^\s*<<\s*$/) {
286 0         0 $state = 'in body';
287             }
288             elsif (/^\s*$/) {
289 1         3 $result .= "$_\n";
290 1         1 last;
291             }
292             else {
293 0         0 die "unparseable line in response header: '$_'\n";
294             }
295             }
296             elsif ($state eq 'in body') {
297 0 0       0 if (/^\s*<< (.*)/) {
    0          
    0          
298 0         0 push @body_exact, $1;
299             }
300             elsif (/^\s*~< (.*)/) {
301 0         0 push @body_res, $1;
302             }
303             elsif (/^\s*$/) {
304 0         0 $result .= "$_\n";
305 0         0 last;
306             }
307             else {
308 0         0 die "unparseable line in response body: '$_'\n";
309             }
310             }
311             }
312              
313 3 50 33     37 if (@body_exact && @body_res) {
    50          
    50          
314 0           die "Can't have both regexes and exact matches for the body.\n";
315             }
316             elsif (@body_exact) {
317 0           my $body = join "\n", @body_exact;
318 0           $result .= <
319             {
320             local \$_ = <
321             $body
322             END_OF_BODY
323             s/\\n\\z//g;
324             \$test->body_is( \$_ );
325             }
326             END_OF_CODE
327             }
328             elsif (@body_res) {
329 0           foreach (@body_res) {
330 0           $result .= " \$test->body_like(qr{$_});\n";
331             }
332             }
333             }
334             }
335              
336             =head1 SEE ALSO
337              
338             L,
339             L
340              
341             =head1 AUTHOR
342              
343             Socialtext, Inc. C<< >>
344              
345             =head1 COPYRIGHT & LICENSE
346              
347             Copyright 2006 Socialtext, Inc., all rights reserved.
348              
349             Same terms as Perl.
350              
351             =cut
352              
353             1;