File Coverage

blib/lib/Test/Apache/RewriteRules.pm
Criterion Covered Total %
statement 44 214 20.5
branch 0 48 0.0
condition 0 35 0.0
subroutine 13 36 36.1
pod 9 22 40.9
total 66 355 18.5


line stmt bran cond sub pod time code
1             package Test::Apache::RewriteRules;
2 3     3   2555 use 5.008001;
  3         10  
  3         115  
3 3     3   15 use strict;
  3         4  
  3         85  
4 3     3   12 use warnings;
  3         5  
  3         88  
5 3     3   13 use Carp qw(croak);
  3         4  
  3         143  
6              
7 3     3   3438 use JSON::XS qw(decode_json);
  3         20839  
  3         219  
8 3     3   918 use Path::Class qw(dir file);
  3         53620  
  3         187  
9 3     3   20 use File::Temp qw(tempfile);
  3         6  
  3         189  
10              
11 3     3   3504 use LWP::UserAgent;
  3         162026  
  3         110  
12 3     3   32 use HTTP::Request;
  3         7  
  3         71  
13              
14 3     3   3328 use Test::Differences;
  3         42475  
  3         315  
15 3     3   3506 use Test::Httpd::Apache2;
  3         36795  
  3         154  
16 3     3   31 use Test::TCP qw(empty_port);
  3         6  
  3         7878  
17              
18             our $VERSION = '1.0.1';
19              
20             sub new {
21 0     0 1 0 my ($class, %args) = @_;
22 0         0 bless { backends => [], %args }, $class;
23             }
24              
25             sub available {
26 2     2 1 840 my ($class, %apache_options) = @_;
27 2         127 my $apache = Test::Httpd::Apache2->new(
28             %apache_options,
29             auto_start => 0,
30             );
31 2         3213 eval { $apache->start };
  2         13  
32 2         22573 my $is_available = !$@;
33 2         20 eval { undef $apache };
  2         30  
34 2         46 $is_available;
35             }
36              
37             sub add_backend {
38 0     0 1   my ($self, %backend) = @_;
39 0   0       $backend{port} ||= empty_port();
40 0   0       $backend{apache} ||= $self->create_backend_apache(%backend);
41 0           push @{$self->{backends}}, \%backend;
  0            
42             }
43              
44             sub proxy_port {
45 0     0 0   my $self = shift;
46 0   0       $self->{proxy_port} ||= empty_port();
47             }
48              
49             sub proxy_host {
50 0     0 0   my $self = shift;
51 0           sprintf q, $self->proxy_port;
52             }
53              
54             sub proxy_http_url {
55 0     0 0   my $self = shift;
56 0   0       my $path = shift || q;
57 0           $path =~ s[^//[^/]*/][/];
58              
59 0           sprintf q, $self->proxy_host, $path;
60             }
61              
62             sub backend_port {
63 0     0 0   my ($self, $backend_name) = @_;
64              
65 0           for my $backend (@{$self->{backends}}) {
  0            
66 0 0         return $backend->{port}
67             if $backend->{name} eq $backend_name;
68             }
69              
70 0           croak qq;
71             }
72              
73             sub backend_host {
74 0     0 0   my ($self, $backend_name) = @_;
75 0           sprintf q, $self->backend_port($backend_name);
76             }
77              
78             sub get_backend_name_by_port {
79 0     0 0   my ($self, $port) = @_;
80              
81 0           for my $backend (@{$self->{backends}}) {
  0            
82 0 0 0       return $backend->{name}
83             if ($backend->{port} || 0) == $port
84             }
85             }
86              
87             sub rewrite_conf {
88 0     0 1   my ($self, $rewrite_conf) = @_;
89 0   0       $self->{rewrite_conf} ||= $rewrite_conf && file($rewrite_conf);
      0        
90             }
91              
92             *rewrite_conf_f = \&rewrite_conf;
93              
94             sub copy_config {
95 0     0 1   my ($self, $original_conf, $patterns) = @_;
96 0   0       $patterns ||= [];
97 0           $original_conf = file($original_conf);
98 0           my $config = eval { $original_conf->slurp };
  0            
99              
100 0 0         croak $@ if $@;
101              
102 0           while (@$patterns) {
103 0           my $pattern = shift @$patterns;
104 0 0         $pattern = ref $pattern eq 'Regexp' ? $pattern : qr/\Q$pattern\E/;
105 0           my $replace = shift @$patterns;
106 0 0   0     my $code = ref $replace eq 'CODE' ? $replace : sub { $replace };
  0            
107              
108 0           $config =~ s/$pattern/$code->()/ge;
  0            
109             }
110              
111 0           my $copied_conf = $original_conf->basename;
112 0           $copied_conf =~ s/\.[^.]*//g;
113 0           $copied_conf .= 'XXXXX';
114              
115 0           (undef, $copied_conf) = tempfile(
116             $copied_conf,
117             DIR => $self->server_root,
118             );
119 0           $copied_conf = file($copied_conf);
120              
121 0           my $fh = $copied_conf->openw;
122 0           print $fh $config;
123 0           close $fh;
124              
125 0           $copied_conf;
126             }
127              
128             *copy_conf_as_f = \©_config;
129              
130             sub server_root {
131 0     0 0   my $self = shift;
132 0           dir($self->apache->server_root);
133             }
134              
135             *server_root_d = \&server_root;
136              
137             sub proxy_document_root_d {
138 0     0 0   my $self = shift;
139 0           $self->server_root->absolute->cleanup;
140             }
141              
142             sub receiver {
143 0     0 0   my $self = shift;
144 0 0         return $self->{receiver} if $self->{receiver};
145              
146 0           my $receiver_path_name = 'url.cgi';
147 0           my $receiver = <<"EOS";
148             #!/usr/bin/env perl
149             use strict;
150             use warnings;
151             use JSON::XS;
152              
153             print "Content-Type: application/json;\\n\\n";
154             print encode_json({
155             host => \$ENV{HTTP_HOST},
156             path => \$ENV{REQUEST_URI},
157             path_translated => \$ENV{PATH_TRANSLATED} . (\$ENV{REQUEST_URI} =~ /\\?/ ? "?\$ENV{QUERY_STRING}" : '')
158             });
159             EOS
160              
161 0           my $receiver_file = sprintf '%s/%s', $self->server_root, $receiver_path_name;
162 0 0         open my $fh, "> $receiver_file" or die $!;
163 0           print $fh $receiver;
164 0           close $fh;
165 0 0         chmod 0755, $receiver_file
166             or die "Couldn't chmod receiver file: $receiver_file";
167              
168 0           $self->{receiver} = file($receiver_file);
169             }
170              
171             sub custom_conf {
172 0     0 0   my $self = shift;
173              
174 0 0         croak "rewrite conf is required"
175             if !$self->rewrite_conf;
176              
177 0           my $custom_conf = '';
178 0           for my $backend (@{$self->{backends}}) {
  0            
179 0           $custom_conf .= sprintf "SetEnvIf Request_URI .* %s=localhost:%s\n",
180             $backend->{name}, $backend->{port};
181             }
182              
183 0           $custom_conf .= <<"EOS";
184 0           ServerName proxy.test:@{[$self->proxy_port]}
  0            
185 0           DocumentRoot @{[$self->server_root]}
186              
187             RewriteRule ^/url\\.cgi/ - [L]
188              
189 0           Include "@{[$self->rewrite_conf]}"
190              
191 0           Action default-proxy-handler /@{[$self->receiver->basename]} virtual
192             SetHandler default-proxy-handler
193              
194             receiver->basename]}>
195             SetHandler cgi-script
196            
197             EOS
198             }
199              
200             my @required_modules = qw(
201             log_config
202             setenvif
203             alias
204             rewrite
205             authn_file
206             authz_host
207             auth_basic
208             mime
209             proxy
210             proxy_http
211             cgi
212             actions
213             );
214              
215             sub apache {
216 0     0 0   my $self = shift;
217 0 0         return $self->{apache} if $self->{apache};
218              
219 0   0       my $apache_options = $self->{apache_options} || {};
220 0           $self->{apache} = Test::Httpd::Apache2->new(
221             auto_start => 0,
222             listen => $self->proxy_port,
223             required_modules => \@required_modules,
224             %$apache_options,
225             );
226 0           $self->{apache}->server_root($self->{apache}->tmpdir);
227 0           $self->{apache}
228             }
229              
230             sub start_apache {
231 0     0 1   my $self = shift;
232 0           $self->apache->custom_conf($self->custom_conf);
233 0           $self->apache->start;
234              
235 0           for my $backend (@{$self->{backends}}) {
  0            
236 0           $backend->{apache}->start;
237             }
238             }
239              
240             sub stop_apache {
241 0     0 1   my $self = shift;
242 0 0         $self->apache->stop if $self->apache->pid;
243              
244 0           for my $backend (@{$self->{backends}}) {
  0            
245 0 0         $backend->{apache}->stop if $backend->{apache}->pid;
246             }
247             }
248              
249             sub create_backend_apache {
250 0     0 0   my ($self, %backend) = @_;
251 0   0       my $apache_options = $self->{apache_options} || {};
252 0           my $proxy_apache = $self->apache;
253 0           my $backend_apache = Test::Httpd::Apache2->new(
254             auto_start => 0,
255             listen => $backend{port},
256             required_modules => \@required_modules,
257             %$apache_options,
258             );
259 0           $backend_apache->server_root($proxy_apache->server_root);
260 0           $backend_apache->custom_conf(<<"EOS");
261 0           ServerName @{[$backend{name}]}.test:@{[$backend{port}]}
  0            
  0            
262 0           DocumentRoot @{[$backend_apache->server_root]}
263              
264             AddHandler cgi-script .cgi
265 0           server_root]}>
266             Options +ExecCGI
267            
268              
269             RewriteEngine on
270             RewriteRule /(.*) /@{[$self->receiver->basename]}/\$1 [L]
271             EOS
272 0           $backend_apache;
273             }
274              
275             sub get_rewrite_result {
276 0     0 0   my ($self, %args) = @_;
277              
278 0           my $url = $self->proxy_http_url($args{orig_path});
279 0   0       my $method = $Test::Apache::RewriteRules::ClientEnvs::RequestMethod || 'GET';
280              
281 0           my $req = HTTP::Request->new($method => $url);
282 0           my $ua = LWP::UserAgent->new(max_redirect => 0, agent => '');
283              
284 0           my $UA = $Test::Apache::RewriteRules::ClientEnvs::UserAgent;
285 0 0         if (defined $UA) {
286 0           $UA =~ s/%%SBSerialNumber%%//g;
287 0           $req->header('User-Agent' => $UA);
288             }
289              
290 0 0         if ($args{orig_path} =~ m[^//([^/]*)/]) {
291 0           $req->header(Host => $1);
292             }
293              
294 0   0       my $cookies = $Test::Apache::RewriteRules::ClientEnvs::Cookies || [];
295 0 0         if (@$cookies) {
296 0           $cookies = [@$cookies];
297 0           my @c;
298 0           while (@$cookies) {
299 0           my $n = shift @$cookies;
300 0           my $v = shift @$cookies;
301 0           push @c, $n . '=' . $v;
302             }
303 0           $req->header(Cookie => join '; ', @c);
304             }
305              
306 0   0       my $header = $Test::Apache::RewriteRules::ClientEnvs::HttpHeader || [];
307 0 0         if (@$header) {
308 0           $header = [@$header];
309 0           my @c;
310 0           while (@$header) {
311 0           my $n = shift @$header;
312 0           my $v = shift @$header;
313 0           $req->header($n => $v);
314             }
315             }
316              
317 0           my $res = $ua->request($req);
318 0 0         die $res->status_line if $res->is_error;
319              
320 0           my $code = $res->code;
321 0           my $result;
322              
323 0 0         if ($code >= 300) {
324 0           $result = {
325             code => $code,
326             };
327 0 0         $result->{location} = $res->header('Location') if $res->header('Location');
328             }
329             else {
330 0           $result = eval { decode_json($res->content) };
  0            
331 0 0         die $@ if $@;
332 0           $result->{code} = $code;
333 0           $result->{host} =~ s!
334             ^(localhost:(\d+))!
335 0   0       qq[$1 (@{[($self->get_backend_name_by_port($2) || '')]})]
  0            
336             !xe;
337              
338 0           my $path_translated = delete $result->{path_translated};
339 0 0         if ($args{use_path_translated}) {
340 0           $result->{path} = $path_translated;
341             }
342             }
343              
344 0           $result;
345             }
346              
347             sub is_host_path {
348 0     0 1   local $Test::Builder::Level = $Test::Builder::Level + 1;
349              
350 0           my ($self, $orig_path, $backend_name, $path, $name) = @_;
351 0 0         $backend_name = defined $backend_name ? $backend_name : '';
352              
353 0           my $use_path_translated = !$backend_name;
354 0           my $result = $self->get_rewrite_result(
355             orig_path => $orig_path,
356             use_path_translated => $use_path_translated,
357             );
358              
359 0 0         my $host = $backend_name
360             ? $self->backend_host($backend_name)
361             : $self->proxy_host;
362 0           $host .= " ($backend_name)";
363              
364 0           my $expected = {
365             code => 200,
366             host => $host,
367             path => $path,
368             };
369              
370 0           eq_or_diff $result, $expected, $name;
371             }
372              
373             sub is_redirect {
374 0     0 1   local $Test::Builder::Level = $Test::Builder::Level + 1;
375              
376 0           my ($self, $orig_path, $redirect_url, $name, %args) = @_;
377 0           my $result = $self->get_rewrite_result(orig_path => $orig_path);
378 0   0       my $code = $args{code} || 302;
379              
380 0           my $expected = {
381             code => $code,
382             };
383 0 0         $expected->{location} = $redirect_url if $redirect_url;
384              
385 0           eq_or_diff $result, $expected, $name;
386             }
387              
388             sub DESTROY {
389 0     0     my $self = shift;
390 0           $self->stop_apache;
391             }
392              
393             1;
394              
395             __END__