File Coverage

blib/lib/Web/MREST/Test.pm
Criterion Covered Total %
statement 97 131 74.0
branch 15 26 57.6
condition 7 7 100.0
subroutine 21 23 91.3
pod 5 5 100.0
total 145 192 75.5


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2015-2015, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33             # ------------------------
34             # Test helper functions module
35             # ------------------------
36              
37             package Web::MREST::Test;
38              
39 21     21   2523583 use strict;
  21         127  
  21         569  
40 21     21   111 use warnings;
  21         42  
  21         976  
41              
42 21     21   113 use App::CELL qw( $CELL $log $meta $site );
  21         39  
  21         2116  
43 21     21   145 use Data::Dumper;
  21         46  
  21         922  
44 21     21   6678 use File::HomeDir;
  21         90836  
  21         1160  
45 21     21   4317 use HTTP::Request;
  21         206624  
  21         664  
46 21     21   7188 use JSON;
  21         136606  
  21         166  
47 21     21   8231 use Log::Any::Adapter;
  21         5926  
  21         109  
48 21     21   623 use Params::Validate qw( :all );
  21         37  
  21         3368  
49 21     21   4966 use Plack::Test;
  21         9787  
  21         984  
50 21     21   8186 use Test::JSON;
  21         660035  
  21         191  
51 21     21   6313 use Test::More;
  21         29253  
  21         159  
52 21     21   5070 use Try::Tiny;
  21         47  
  21         1027  
53 21     21   6550 use Web::Machine;
  21         1829904  
  21         696  
54 21     21   8339 use Web::MREST;
  21         55  
  21         682  
55              
56              
57              
58             =head1 NAME
59              
60             Web::MREST::Test - Test helper functions
61              
62              
63              
64              
65              
66             =head1 DESCRIPTION
67              
68             This module provides helper code for unit tests.
69              
70             =cut
71              
72              
73              
74              
75             =head1 EXPORTS
76              
77             =cut
78              
79 21     21   129 use Exporter qw( import );
  21         36  
  21         21591  
80             our @EXPORT = qw( initialize_unit req llreq docu_check );
81              
82              
83              
84              
85             =head1 PACKAGE VARIABLES
86              
87             =cut
88              
89             # dispatch table with references to HTTP::Request::Common functions
90             my %methods = (
91             GET => \&GET,
92             PUT => \&PUT,
93             POST => \&POST,
94             DELETE => \&DELETE,
95             HEAD => \&HEAD,
96             );
97              
98              
99              
100              
101             =head1 FUNCTIONS
102              
103             =cut
104              
105              
106             =head2 initialize_unit
107              
108             Perform the boilerplate tasks that have to be done at the beginning of every
109             unit. Takes a PARAMHASH with two optional parameters:
110              
111             'class' => class into which Web::Machine object is to be blessed
112             'sitedir' => sitedir parameter to be passed to Web::MREST::init
113              
114             =cut
115              
116             sub initialize_unit {
117 21     21 1 1812 my %ARGS = @_;
118 21         222 note( "Initializing unit " . (caller)[1] . " with arguments " . Dumper( \%ARGS ) );
119 21   100     14195 my $class = $ARGS{'class'} || undef;
120             my %init_options = $ARGS{'sitedir'}
121 21 50       119 ? ( 'sitedir' => $ARGS{'sitedir'} )
122             : ();
123              
124             # zero logfile and tell Log::Any to log to it
125 21         204 my $log_file_spec = File::HomeDir->my_home . "/mrest.log";
126 21         41169 unlink $log_file_spec;
127 21         301 Log::Any::Adapter->set( 'File', $log_file_spec );
128 21         57128 $log->init( ident => 'MREST_UNIT_TEST' );
129              
130             # load configuration parameters
131 21         6779 my $status = Web::MREST::init( %init_options );
132 21         1310 is( $status->level, 'OK' );
133              
134 21         11135 note( 'check that site configuration parameters were loaded' );
135 21         7843 is_deeply( [ $site->MREST_SUPPORTED_CONTENT_TYPES ], [ [ 'application/json' ] ],
136             'configuration parameters loaded?' );
137              
138             # set debug mode
139 21         19515 $log->debug_mode( $site->MREST_DEBUG_MODE );
140              
141 21   100     1000 my $app = Web::Machine->new(
142             resource => ( $class || 'Web::MREST::Dispatch' )
143             )->to_app;
144              
145 21         2007 my $test = Plack::Test->create( $app );
146 21         100496 isa_ok( $test, 'Plack::Test::MockHTTP' );
147 21         14173 return $test;
148              
149             }
150              
151              
152             =head2 status_from_json
153              
154             L<Web::MREST> is designed to return status objects in the HTTP response entity.
155             Before inclusion in the response, the status object is converted to JSON. This
156             routine goes the opposite direction, taking a JSON string and converting it
157             back into a status object.
158              
159             FIXME: There may be some encoding issues here!
160              
161             =cut
162              
163             sub status_from_json {
164 37     37 1 134 my ( $json ) = @_;
165 37         360 $log->debug( "Entering " . __PACKAGE__ . "::status_from_json" );
166 37         7497 my $obj;
167             try {
168 37     37   2019 $obj = bless from_json( $json ), 'App::CELL::Status';
169             } catch {
170 0     0   0 $obj = $_;
171 37         374 };
172 37 50       2240 return $obj if ref( $obj) eq 'App::CELL::Status';
173 0         0 die "\n\nfrom_json died";
174             }
175              
176              
177             =head2 req
178              
179             Assemble and process a HTTP request. Takes the following positional arguments:
180              
181             * Plack::Test object
182             * expected HTTP result code
183             * user to authenticate with (can be 'root', 'demo', or 'active')
184             * HTTP method
185             * resource string
186             * optional JSON string
187              
188             If the HTTP result code is 200, the return value will be a status object, undef
189             otherwise.
190              
191             =cut
192              
193             sub req {
194 37     37 1 78986 my ( $test, $code, $method, $resource, $json ) = validate_pos( @_, 1, 1, 1, 1, 0 );
195 37         427 $log->debug( "Entering " . __PACKAGE__ . "::req" );
196              
197 37 50       8284 if ( ref( $test ) ne 'Plack::Test::MockHTTP' ) {
198 0         0 diag( "Plack::Test::MockHTTP object not passed to 'req' from " . (caller)[1] . " line " . (caller)[2] );
199 0         0 BAIL_OUT(0);
200             }
201              
202             # assemble request
203 37         175 my @headers = (
204             'accept' => 'application/json',
205             'content-type' => 'application/json',
206             );
207 37         174 my $r = llreq( $method, $resource, \@headers, $json );
208              
209             # send request; get response
210 37         43161 my $res = $test->request( $r );
211 37         25420 isa_ok( $res, 'HTTP::Response' );
212 37 50       23145 diag( Dumper $res ) if ( $res->code == 500 );
213              
214             #diag( $res->code . " " . $res->message );
215 37 100       631 is( $res->code, $code, "$method $resource" . ( $json ? " with $json" : "" ) . " 1" );
216 37         12666 my $content = $res->content;
217 37 50       655 if ( $content ) {
218             #diag( Dumper $content );
219 37 100       421 is_valid_json( $content, "$method $resource" . ( $json ? " with $json" : "" ) . " 2" );
220 37         14369 my $status = status_from_json( $content );
221 37 100       324 if ( my $location_header = $res->header( 'Location' ) ) {
222 1         83 $status->{'location_header'} = $location_header;
223             }
224 37         2669 return $status;
225             }
226 0         0 return;
227             }
228              
229              
230             =head2 llreq
231              
232             Low-level request generator
233              
234             =cut
235              
236             sub llreq {
237 67     67 1 47526 my ( $method, $uri, @args ) = @_;
238 67         187 my ( $headers, $content );
239 67 100       239 if ( @args ) {
240 50         117 $headers = shift @args;
241 50         292 $log->debug( "llreq: headers set to " . Dumper( $headers ) );
242             } else {
243 17         55 $headers = [
244             'accept' => 'application/json',
245             'content-type' => 'application/json',
246             ];
247             }
248 67 100 100     13280 if ( @args and defined( $args[0] ) ) {
249 12         67 $log->debug( "llreq: args is " . Dumper( \@args ) );
250 12         2855 $content = join( ' ', @args );
251             }
252 67         621 return HTTP::Request->new( $method, $uri, $headers, $content );
253             }
254              
255              
256             =head2 docu_check
257              
258             Check that the resource has on-line documentation (takes Plack::Test object
259             and resource name without quotes)
260              
261             =cut
262              
263             sub docu_check {
264 0     0 1   my ( $test, $resource ) = @_;
265              
266             #diag( "Entering " . __PACKAGE__ . "::docu_check with argument $resource" );
267              
268 0 0         if ( ref( $test ) ne 'Plack::Test::MockHTTP' ) {
269 0           diag( "Plack::Test::MockHTTP object not passed to 'req' from " . (caller)[1] . " line " . (caller)[2] );
270 0           BAIL_OUT(0);
271             }
272              
273 0           my $tn = "docu_check $resource ";
274 0           my $t = 0;
275 0           my ( $docustr, $docustr_len );
276             #
277             # - straight 'docu' resource
278 0           my $status = req( $test, 200, 'demo', 'POST', '/docu', <<"EOH" );
279             { "resource" : "$resource" }
280             EOH
281 0           is( $status->level, 'OK', $tn . ++$t );
282 0           is( $status->code, 'DISPATCH_ONLINE_DOCUMENTATION', $tn . ++$t );
283 0 0         if ( exists $status->{'payload'} ) {
284 0           ok( exists $status->payload->{'resource'}, $tn . ++$t );
285 0           is( $status->payload->{'resource'}, $resource, $tn . ++$t );
286 0           ok( exists $status->payload->{'documentation'}, $tn . ++$t );
287 0           $docustr = $status->payload->{'documentation'};
288 0           $docustr_len = length( $docustr );
289 0           ok( $docustr_len > 10, $tn . ++$t );
290 0           isnt( $docustr, 'NOT WRITTEN YET', $tn . ++$t );
291             }
292             #
293             # - not a very thorough examination of the 'docu/html' version
294 0           $status = req( $test, 200, 'demo', 'POST', '/docu/html', <<"EOH" );
295             { "resource" : "$resource" }
296             EOH
297 0           is( $status->level, 'OK', $tn . ++$t );
298 0           is( $status->code, 'DISPATCH_ONLINE_DOCUMENTATION', $tn . ++$t );
299 0 0         if ( exists $status->{'payload'} ) {
300 0           ok( exists $status->payload->{'resource'}, $tn . ++$t );
301 0           is( $status->payload->{'resource'}, $resource, $tn . ++$t );
302 0           ok( exists $status->payload->{'documentation'}, $tn . ++$t );
303 0           $docustr = $status->payload->{'documentation'};
304 0           $docustr_len = length( $docustr );
305 0           ok( $docustr_len > 10, $tn . ++$t );
306 0           isnt( $docustr, 'NOT WRITTEN YET', $tn . ++$t );
307             }
308             }
309              
310             1;