File Coverage

blib/lib/Test/Mojo/Plack.pm
Criterion Covered Total %
statement 84 101 83.1
branch 18 44 40.9
condition 10 28 35.7
subroutine 15 16 93.7
pod 1 1 100.0
total 128 190 67.3


line stmt bran cond sub pod time code
1             package Test::Mojo::Plack;
2              
3 3     3   76593 use strict;
  3         19  
  3         91  
4 3     3   22 use warnings;
  3         6  
  3         88  
5              
6 3     3   1699 use Mojo::Base 'Test::Mojo';
  3         651851  
  3         28  
7 3     3   1073444 use Mojo::Headers;
  3         11  
  3         51  
8 3     3   159 use Mojo::Transaction::HTTP;
  3         8  
  3         24  
9 3     3   96 use Mojo::URL;
  3         13  
  3         24  
10 3     3   99 use Mojo::Util qw(encode decode url_unescape);
  3         7  
  3         259  
11              
12 3     3   2222 use Class::Load qw(load_class is_class_loaded);
  3         64221  
  3         321  
13 3     3   1858 use IO::String;
  3         8829  
  3         133  
14 3     3   2395 use List::MoreUtils;
  3         44529  
  3         27  
15 3     3   3287 use Scalar::Util qw(blessed);
  3         6  
  3         4588  
16              
17             sub new {
18 2     2 1 19577 my ($class, $app_class) = @_;
19              
20 2         15 my $t = $class->SUPER::new();
21              
22 2 100       52 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   8 $t->{psgi_app} = sub { my $res = $app_class->(shift); sub { shift->($res); } };
  1         10  
  1         31  
  1         5  
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       4 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   108561 my ($self, $tx, $url) = @_;
49              
50             # Let Mojo::Test handle it if no app has been instantiated
51 2 100       18 return $self->SUPER::_request_ok(@_[1..2]) unless $self->{psgi_app};
52              
53 1         7 $url = Mojo::URL->new($url);
54              
55 1 50 50     57 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       680 for my $field ( @{ $tx->req->headers->names || [] }) {
  1         7  
80 2         67 my $key = uc("HTTP_$field");
81 2         8 $key =~ tr/-/_/;
82 2 50       7 $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
83              
84 2 50       7 unless ( exists $env->{$key} ) {
85 2         6 $env->{$key} = $tx->req->headers->header($field);
86             }
87             }
88              
89 1 50       22 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     9 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         15 my $ret = $self->{psgi_app}->($env);
102 1         9 my $res = Mojo::Message::Response->new();
103              
104             $ret->(sub {
105 1     1   2 my ($code, $headers, $body) = @{+shift};
  1         3  
106 1         3 my $header_hash;
107 1         2 my $it = List::MoreUtils::natatime 2, @{$headers};
  1         19  
108 1         14 while (my($k, $v) = $it->()) {
109 1         3 $res->headers->append($k, $v);
110             }
111 1         105 $res->code($code);
112              
113 1         7 my $body_str = '';
114 1 50 33     27 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       6 if ($type eq 'ARRAY') {
    0          
122 1         3 $body_str = join '', @{$body};
  1         4  
123             } elsif ($type eq 'GLOB') {
124 0         0 $body_str = <$body>;
125             }
126             };
127              
128 1   33     6 $body_str //= $body;
129              
130 1         6 $res->body($body_str);
131 1         16 });
132              
133 1         82 $self->tx(Mojo::Transaction::HTTP->new);
134 1         17 $self->tx->req->env($env);
135 1         27 $self->tx->res($res);
136              
137 1         10 my $err = $self->tx->error;
138             Test::More::diag $err->{message}
139 1 50 33     27 if !(my $ok = !$err->{message} || $err->{code}) && $err;
      33        
140 1         4 my $desc = encode 'UTF-8', "@{[uc $tx->req->method]} $url";
  1         3  
141 1 50       188 return $self->can('_test') ? $self->_test('ok', $ok, $desc) : $self->test('ok', $ok, $desc);
142             }
143              
144             =head1 NAME
145              
146             Test::Mojo::Plack - Test Plack-compatible applications with Test:Mojo
147              
148             =head1 VERSION
149              
150             Version 0.12
151              
152             =cut
153              
154             our $VERSION = '0.12';
155              
156             =head1 SYNOPSIS
157              
158             use Test::Mojo::Plack;
159             use Test::More;
160              
161             my $foo = Test::Mojo::Plack->new('My::Catalyst::App');
162             my $foo = Test::Mojo::Plack->new('My::Dancer::App');
163              
164             $foo->get_ok("/")
165             ->status_is(200)
166             ->content_type_is('text/html')
167             ->text_is('#footer a.author', 'mendoza@pvv.ntnu.no');
168              
169             done_testing;
170              
171             =head1 SUBROUTINES/METHODS
172              
173             =head2 new
174              
175             =head2 new($app)
176              
177             Returns a L object that is a subclass of L
178              
179             If $app is provided, it tries to set app a PSGI application by guessing the framework of it.
180              
181             =head1 METHODS
182              
183             L inherits all methods from L and overrides the following:
184              
185             =head2 _request_ok
186              
187             Hijacks the setup and sending of a request to send it to a pre-defined PSGI application.
188              
189             =head1 AUTHOR
190              
191             Nicolas Mendoza, C<< >>
192              
193             =head1 BUGS
194              
195             =head1 SUPPORT
196              
197             You can find documentation for this module with the perldoc command.
198              
199             perldoc Test::Mojo::Plack
200              
201             =head1 SEE ALSO
202              
203             =over
204              
205             =item L
206              
207             =item L
208              
209             =item L
210              
211             =item L - Newer alternative approach
212              
213             =back
214              
215             =head1 REPOSITORY
216              
217             L
218              
219             =head1 ACKNOWLEDGEMENTS
220              
221             Heavily inspired by L and L and of course L
222              
223             =head1 LICENSE AND COPYRIGHT
224              
225             Copyright 2014 Nicolas Mendoza.
226              
227             This program is free software; you can redistribute it and/or modify it
228             under the terms of either: the GNU General Public License as published
229             by the Free Software Foundation; or the Artistic License.
230              
231             See http://dev.perl.org/licenses/ for more information.
232              
233              
234             =cut
235              
236             1; # End of Test::Mojo::Plack
237              
238