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   1551376 use strict;
  21         31  
  21         474  
40 21     21   72 use warnings;
  21         22  
  21         450  
41              
42 21     21   65 use App::CELL qw( $CELL $log $meta $site );
  21         23  
  21         1802  
43 21     21   82 use Data::Dumper;
  21         24  
  21         728  
44 21     21   9340 use File::HomeDir;
  21         78831  
  21         838  
45 21     21   5657 use HTTP::Request;
  21         164230  
  21         508  
46 21     21   10320 use JSON;
  21         140420  
  21         77  
47 21     21   9787 use Log::Any::Adapter;
  21         4023  
  21         92  
48 21     21   467 use Params::Validate qw( :all );
  21         27  
  21         2882  
49 21     21   7742 use Plack::Test;
  21         7515  
  21         782  
50 21     21   7381 use Test::JSON;
  21         308077  
  21         139  
51 21     21   6277 use Test::More;
  21         23085  
  21         111  
52 21     21   3594 use Try::Tiny;
  21         31  
  21         846  
53 21     21   8142 use Web::Machine;
  21         2383473  
  21         588  
54 21     21   9933 use Web::MREST;
  21         38  
  21         607  
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   86 use Exporter qw( import );
  21         25  
  21         18680  
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 96 my %ARGS = @_;
118 21         158 note( "Initializing unit " . (caller)[1] . " with arguments " . Dumper( \%ARGS ) );
119 21   100     4345 my $class = $ARGS{'class'} || undef;
120             my %init_options = $ARGS{'sitedir'}
121 21 50       83 ? ( 'sitedir' => $ARGS{'sitedir'} )
122             : ();
123              
124             # zero logfile and tell Log::Any to log to it
125 21         146 my $log_file_spec = File::HomeDir->my_home . "/mrest.log";
126 21         66775 unlink $log_file_spec;
127 21         221 Log::Any::Adapter->set( 'File', $log_file_spec );
128 21         50240 $log->init( ident => 'MREST_UNIT_TEST' );
129              
130             # load configuration parameters
131 21         32807 my $status = Web::MREST::init( %init_options );
132 21         625 is( $status->level, 'OK' );
133              
134 21         5580 note( 'check that site configuration parameters were loaded' );
135 21         1043 is_deeply( [ $site->MREST_SUPPORTED_CONTENT_TYPES ], [ [ 'application/json' ] ],
136             'configuration parameters loaded?' );
137              
138             # set debug mode
139 21         10278 $log->debug_mode( $site->MREST_DEBUG_MODE );
140              
141 21   100     613 my $app = Web::Machine->new(
142             resource => ( $class || 'Web::MREST::Dispatch' )
143             )->to_app;
144              
145 21         1433 my $test = Plack::Test->create( $app );
146 21         89933 isa_ok( $test, 'Plack::Test::MockHTTP' );
147 21         7350 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 72 my ( $json ) = @_;
165 37         205 $log->debug( "Entering " . __PACKAGE__ . "::status_from_json" );
166 37         3665 my $obj;
167             try {
168 37     37   984 $obj = bless from_json( $json ), 'App::CELL::Status';
169             } catch {
170 0     0   0 $obj = $_;
171 37         246 };
172 37 50       1205 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 48599 my ( $test, $code, $method, $resource, $json ) = validate_pos( @_, 1, 1, 1, 1, 0 );
195 37         303 $log->debug( "Entering " . __PACKAGE__ . "::req" );
196              
197 37 50       4634 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         94 my @headers = (
204             'accept' => 'application/json',
205             'content-type' => 'application/json',
206             );
207 37         91 my $r = llreq( $method, $resource, \@headers, $json );
208              
209             # send request; get response
210 37         33759 my $res = $test->request( $r );
211 37         12507 isa_ok( $res, 'HTTP::Response' );
212 37 50       9535 diag( Dumper $res ) if ( $res->code == 500 );
213              
214             #diag( $res->code . " " . $res->message );
215 37 100       333 is( $res->code, $code, "$method $resource" . ( $json ? " with $json" : "" ) . " 1" );
216 37         6718 my $content = $res->content;
217 37 50       346 if ( $content ) {
218             #diag( Dumper $content );
219 37 100       298 is_valid_json( $content, "$method $resource" . ( $json ? " with $json" : "" ) . " 2" );
220 37         7775 my $status = status_from_json( $content );
221 37 100       139 if ( my $location_header = $res->header( 'Location' ) ) {
222 1         28 $status->{'location_header'} = $location_header;
223             }
224 37         1333 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 27902 my ( $method, $uri, @args ) = @_;
238 67         75 my ( $headers, $content );
239 67 100       156 if ( @args ) {
240 50         66 $headers = shift @args;
241 50         155 $log->debug( "llreq: headers set to " . Dumper( $headers ) );
242             } else {
243 17         50 $headers = [
244             'accept' => 'application/json',
245             'content-type' => 'application/json',
246             ];
247             }
248 67 100 100     8101 if ( @args and defined( $args[0] ) ) {
249 12         42 $log->debug( "llreq: args is " . Dumper( \@args ) );
250 12         1749 $content = join( ' ', @args );
251             }
252 67         441 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;