File Coverage

blib/lib/Test/Mojo/Plack.pm
Criterion Covered Total %
statement 84 102 82.3
branch 18 44 40.9
condition 10 28 35.7
subroutine 15 16 93.7
pod 1 1 100.0
total 128 191 67.0


line stmt bran cond sub pod time code
1             package Test::Mojo::Plack;
2              
3 3     3   70133 use strict;
  3         17  
  3         90  
4 3     3   16 use warnings;
  3         6  
  3         87  
5              
6 3     3   1695 use Mojo::Base 'Test::Mojo';
  3         644708  
  3         28  
7 3     3   1063270 use Mojo::Headers;
  3         10  
  3         35  
8 3     3   155 use Mojo::Transaction::HTTP;
  3         7  
  3         23  
9 3     3   91 use Mojo::URL;
  3         11  
  3         21  
10 3     3   92 use Mojo::Util qw(encode decode url_unescape);
  3         7  
  3         233  
11              
12 3     3   2050 use Class::Load qw(load_class is_class_loaded);
  3         61478  
  3         371  
13 3     3   1740 use IO::String;
  3         8511  
  3         140  
14 3     3   2226 use List::MoreUtils;
  3         43491  
  3         28  
15 3     3   3113 use Scalar::Util qw(blessed);
  3         9  
  3         4429  
16              
17             sub new {
18 2     2 1 17519 my ($class, $app_class) = @_;
19              
20 2         14 my $t = $class->SUPER::new();
21              
22 2 100       48 return $t unless $app_class;
23              
24 1         8 $ENV{PLACK_ENV} = 1;
25              
26 1 50       6 if (ref $app_class eq 'CODE') {
27 1     1   6 $t->{psgi_app} = sub { my $res = $app_class->(shift); sub { shift->($res); } };
  1         4  
  1         28  
  1         3  
28             } else {
29 0 0       0 load_class($app_class) unless is_class_loaded($app_class);
30 0         0 $app_class->import;
31 0 0       0 if ($app_class->can("_finalized_psgi_app") ) { # Catalyst
    0          
32 0         0 $t->{psgi_app} = $app_class->_finalized_psgi_app;
33             }
34             elsif ($app_class->can("dance") ) { # Dancer
35             $t->{psgi_app} = sub {
36 0     0   0 my $request = Dancer::Request->new( env => shift );
37 0         0 my $res = Dancer->dance( $request );
38 0         0 sub { shift->($res); };
  0         0  
39             }
40 0         0 }
41             }
42 1 50       5 die "Unable to instantiate application as a PSGI application: '$app_class'" unless $t->{psgi_app};
43              
44 1         3 return $t;
45             }
46              
47             sub _request_ok {
48 2     2   100078 my ($self, $tx, $url) = @_;
49              
50             # Let Mojo::Test handle it if no app has been instantiated
51 2 100       16 return $self->SUPER::_request_ok(@_[1..2]) unless $self->{psgi_app};
52              
53 1         6 $url = Mojo::URL->new($url);
54              
55 1 50 50     51 my $env = {
    50 50        
      50        
      33        
56             PATH_INFO => url_unescape($url->path || '/'),
57             QUERY_STRING => $url->query || '',
58             SCRIPT_NAME => '',
59             SERVER_NAME => $url->host,
60             SERVER_PORT => $url->port,
61             SERVER_PROTOCOL => $tx->req->version ? ('HTTP/' . $tx->req->version ) : 'HTTP/1.1',
62             REMOTE_ADDR => '127.0.0.1',
63             REMOTE_HOST => 'localhost',
64             REMOTE_PORT => int( rand(64000) + 1000 ), # not in RFC 3875
65             REQUEST_URI => (join '?', $url->path, $url->query) || '/', # not in RFC 3875
66             REQUEST_METHOD => $tx->req->method,
67             'psgi.version' => [ 1, 1 ],
68             'psgi.url_scheme' => $url->scheme && $url->scheme eq 'https' ? 'https' : 'http',
69             'psgi.input' => IO::String->new($tx->req->body . "\r\n"),
70             'psgi.errors' => *STDERR,
71             'psgi.multithread' => 0,
72             'psgi.multiprocess' => 0,
73             'psgi.run_once' => 1,
74             'psgi.streaming' => 1,
75             'psgi.nonblocking' => 0,
76             'HTTP_CONTENT_LENGTH' => length($tx->req->body),
77             };
78              
79 1 50       643 for my $field ( @{ $tx->req->headers->names || [] }) {
  1         5  
80 2         55 my $key = uc("HTTP_$field");
81 2         5 $key =~ tr/-/_/;
82 2 50       7 $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
83              
84 2 50       6 unless ( exists $env->{$key} ) {
85 2         5 $env->{$key} = $tx->req->headers->header($field);
86             }
87             }
88              
89 1 50       23 if ($env->{SCRIPT_NAME}) {
90 0         0 $env->{PATH_INFO} =~ s/^\Q$env->{SCRIPT_NAME}\E/\//;
91 0         0 $env->{PATH_INFO} =~ s/^\/+/\//;
92             }
93              
94 1 50 33     8 if (!defined($env->{HTTP_HOST}) && $url->host) {
95 0         0 $env->{HTTP_HOST} = $url->host;
96 0 0       0 $env->{HTTP_HOST} .= ':' . $url->port
97             if $url->port;
98             }
99 1   50     14 $env->{HTTP_HOST} ||= 'localhost';
100              
101 1         5 my $ret = $self->{psgi_app}->($env);
102 1         7 my $res = Mojo::Message::Response->new();
103              
104             $ret->(sub {
105 1     1   3 my ($code, $headers, $body) = @{+shift};
  1         4  
106 1         2 my $header_hash;
107 1         2 my $it = List::MoreUtils::natatime 2, @{$headers};
  1         15  
108 1         12 while (my($k, $v) = $it->()) {
109 1         14 $res->headers->append($k, $v);
110             }
111 1         86 $res->code($code);
112              
113 1         6 my $body_str = '';
114 1 50 33     10 if (defined $body && blessed($body)) {
    50          
115 0 0       0 if ($body->can('getline')) {
116 0         0 while (my $line = $body->getline) {
117 0   0     0 $body_str .= ($line || '');
118             }
119             }
120             } elsif(my $type = ref $body) {
121 1 50       4 if ($type eq 'ARRAY') {
    0          
122 1         3 $body_str = join '', @{$body};
  1         4  
123             } elsif ($type eq 'GLOB') {
124 0         0 local $/;
125 0         0 $body_str = <$body>;
126             }
127             };
128              
129 1   33     3 $body_str //= $body;
130              
131 1         4 $res->body($body_str);
132 1         12 });
133              
134 1         65 $self->tx(Mojo::Transaction::HTTP->new);
135 1         14 $self->tx->req->env($env);
136 1         21 $self->tx->res($res);
137              
138 1         10 my $err = $self->tx->error;
139             Test::More::diag $err->{message}
140 1 50 33     38 if !(my $ok = !$err->{message} || $err->{code}) && $err;
      33        
141 1         4 my $desc = encode 'UTF-8', "@{[uc $tx->req->method]} $url";
  1         4  
142 1 50       167 return $self->can('_test') ? $self->_test('ok', $ok, $desc) : $self->test('ok', $ok, $desc);
143             }
144              
145             =head1 NAME
146              
147             Test::Mojo::Plack - Test Plack-compatible applications with Test:Mojo
148              
149             =head1 VERSION
150              
151             Version 0.13
152              
153             =cut
154              
155             our $VERSION = '0.13';
156              
157             =head1 SYNOPSIS
158              
159             use Test::Mojo::Plack;
160             use Test::More;
161              
162             my $foo = Test::Mojo::Plack->new('My::Catalyst::App');
163             my $foo = Test::Mojo::Plack->new('My::Dancer::App');
164              
165             $foo->get_ok("/")
166             ->status_is(200)
167             ->content_type_is('text/html')
168             ->text_is('#footer a.author', 'mendoza@pvv.ntnu.no');
169              
170             done_testing;
171              
172             =head1 SUBROUTINES/METHODS
173              
174             =head2 new
175              
176             =head2 new($app)
177              
178             Returns a L object that is a subclass of L
179              
180             If $app is provided, it tries to set app a PSGI application by guessing the framework of it.
181              
182             =head1 METHODS
183              
184             L inherits all methods from L and overrides the following:
185              
186             =head2 _request_ok
187              
188             Hijacks the setup and sending of a request to send it to a pre-defined PSGI application.
189              
190             =head1 AUTHOR
191              
192             Nicolas Mendoza, C<< >>
193              
194             =head1 BUGS
195              
196             =head1 SUPPORT
197              
198             You can find documentation for this module with the perldoc command.
199              
200             perldoc Test::Mojo::Plack
201              
202             =head1 SEE ALSO
203              
204             =over
205              
206             =item L
207              
208             =item L
209              
210             =item L
211              
212             =item L - Newer alternative approach
213              
214             =back
215              
216             =head1 REPOSITORY
217              
218             L
219              
220             =head1 ACKNOWLEDGEMENTS
221              
222             Heavily inspired by L and L and of course L
223              
224             =head1 LICENSE AND COPYRIGHT
225              
226             Copyright 2014 Nicolas Mendoza.
227              
228             This program is free software; you can redistribute it and/or modify it
229             under the terms of either: the GNU General Public License as published
230             by the Free Software Foundation; or the Artistic License.
231              
232             See http://dev.perl.org/licenses/ for more information.
233              
234              
235             =cut
236              
237             1; # End of Test::Mojo::Plack
238              
239