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-2022, 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              
38             use strict;
39 21     21   2812858 use warnings;
  21         114  
  21         516  
40 21     21   92  
  21         33  
  21         526  
41             use App::CELL qw( $CELL $log $meta $site );
42 21     21   98 use Data::Dumper;
  21         37  
  21         1991  
43 21     21   124 use File::HomeDir;
  21         34  
  21         796  
44 21     21   8808 use HTTP::Request;
  21         96367  
  21         978  
45 21     21   5552 use JSON;
  21         214449  
  21         584  
46 21     21   9871 use Log::Any::Adapter;
  21         149405  
  21         117  
47 21     21   9442 use Params::Validate qw( :all );
  21         5769  
  21         76  
48 21     21   573 use Plack::Test;
  21         35  
  21         2831  
49 21     21   6998 use Test::JSON;
  21         8940  
  21         910  
50 21     21   6763 use Test::More;
  21         650767  
  21         206  
51 21     21   6053 use Try::Tiny;
  21         29916  
  21         149  
52 21     21   4252 use Web::Machine;
  21         47  
  21         836  
53 21     21   8139 use Web::MREST;
  21         1735165  
  21         626  
54 21     21   10023  
  21         44  
  21         643  
55              
56              
57             =head1 NAME
58              
59             Web::MREST::Test - Test helper functions
60              
61              
62              
63              
64              
65             =head1 DESCRIPTION
66              
67             This module provides helper code for unit tests.
68              
69             =cut
70              
71              
72              
73              
74             =head1 EXPORTS
75              
76             =cut
77              
78             use Exporter qw( import );
79 21     21   112 our @EXPORT = qw( initialize_unit req llreq docu_check );
  21         35  
  21         23010  
80              
81              
82              
83              
84             =head1 PACKAGE VARIABLES
85              
86             =cut
87              
88             # dispatch table with references to HTTP::Request::Common functions
89             my %methods = (
90             GET => \&GET,
91             PUT => \&PUT,
92             POST => \&POST,
93             DELETE => \&DELETE,
94             HEAD => \&HEAD,
95             );
96              
97              
98              
99              
100             =head1 FUNCTIONS
101              
102             =cut
103              
104              
105             =head2 initialize_unit
106              
107             Perform the boilerplate tasks that have to be done at the beginning of every
108             unit. Takes a PARAMHASH with two optional parameters:
109              
110             'class' => class into which Web::Machine object is to be blessed
111             'sitedir' => sitedir parameter to be passed to Web::MREST::init
112              
113             =cut
114              
115             my %ARGS = @_;
116             note( "Initializing unit " . (caller)[1] . " with arguments " . Dumper( \%ARGS ) );
117 21     21 1 1669 my $class = $ARGS{'class'} || undef;
118 21         188 my %init_options = $ARGS{'sitedir'}
119 21   100     13591 ? ( 'sitedir' => $ARGS{'sitedir'} )
120             : ();
121 21 50       83  
122             # zero logfile and tell Log::Any to log to it
123             my $log_file_spec = File::HomeDir->my_home . "/mrest.log";
124             unlink $log_file_spec;
125 21         163 Log::Any::Adapter->set( 'File', $log_file_spec );
126 21         2957 $log->init( ident => 'MREST_UNIT_TEST' );
127 21         255  
128 21         57836 # load configuration parameters
129             my $status = Web::MREST::init( %init_options );
130             is( $status->level, 'OK' );
131 21         6881  
132 21         1025 note( 'check that site configuration parameters were loaded' );
133             is_deeply( [ $site->MREST_SUPPORTED_CONTENT_TYPES ], [ [ 'application/json' ] ],
134 21         10071 'configuration parameters loaded?' );
135 21         6522  
136             # set debug mode
137             $log->debug_mode( $site->MREST_DEBUG_MODE );
138              
139 21         15068 my $app = Web::Machine->new(
140             resource => ( $class || 'Web::MREST::Dispatch' )
141 21   100     828 )->to_app;
142              
143             my $test = Plack::Test->create( $app );
144             isa_ok( $test, 'Plack::Test::MockHTTP' );
145 21         1672 return $test;
146 21         105581  
147 21         13158 }
148              
149              
150             =head2 status_from_json
151              
152             L<Web::MREST> is designed to return status objects in the HTTP response entity.
153             Before inclusion in the response, the status object is converted to JSON. This
154             routine goes the opposite direction, taking a JSON string and converting it
155             back into a status object.
156              
157             FIXME: There may be some encoding issues here!
158              
159             =cut
160              
161             my ( $json ) = @_;
162             $log->debug( "Entering " . __PACKAGE__ . "::status_from_json" );
163             my $obj;
164 37     37 1 88 try {
165 37         254 $obj = bless from_json( $json ), 'App::CELL::Status';
166 37         6423 } catch {
167             $obj = $_;
168 37     37   1459 };
169             return $obj if ref( $obj) eq 'App::CELL::Status';
170 0     0   0 die "\n\nfrom_json died";
171 37         302 }
172 37 50       1437  
173 0         0  
174             =head2 req
175              
176             Assemble and process a HTTP request. Takes the following positional arguments:
177              
178             * Plack::Test object
179             * expected HTTP result code
180             * user to authenticate with (can be 'root', 'demo', or 'active')
181             * HTTP method
182             * resource string
183             * optional JSON string
184              
185             If the HTTP result code is 200, the return value will be a status object, undef
186             otherwise.
187              
188             =cut
189              
190             my ( $test, $code, $method, $resource, $json ) = validate_pos( @_, 1, 1, 1, 1, 0 );
191             $log->debug( "Entering " . __PACKAGE__ . "::req" );
192              
193             if ( ref( $test ) ne 'Plack::Test::MockHTTP' ) {
194 37     37 1 70989 diag( "Plack::Test::MockHTTP object not passed to 'req' from " . (caller)[1] . " line " . (caller)[2] );
195 37         309 BAIL_OUT(0);
196             }
197 37 50       7468  
198 0         0 # assemble request
199 0         0 my @headers = (
200             'accept' => 'application/json',
201             'content-type' => 'application/json',
202             );
203 37         139 my $r = llreq( $method, $resource, \@headers, $json );
204              
205             # send request; get response
206             my $res = $test->request( $r );
207 37         113 isa_ok( $res, 'HTTP::Response' );
208             diag( Dumper $res ) if ( $res->code == 500 );
209              
210 37         42066 #diag( $res->code . " " . $res->message );
211 37         17409 is( $res->code, $code, "$method $resource" . ( $json ? " with $json" : "" ) . " 1" );
212 37 50       17602 my $content = $res->content;
213             if ( $content ) {
214             #diag( Dumper $content );
215 37 100       431 is_valid_json( $content, "$method $resource" . ( $json ? " with $json" : "" ) . " 2" );
216 37         12703 my $status = status_from_json( $content );
217 37 50       464 if ( my $location_header = $res->header( 'Location' ) ) {
218             $status->{'location_header'} = $location_header;
219 37 100       284 }
220 37         13390 return $status;
221 37 100       162 }
222 1         41 return;
223             }
224 37         1841  
225              
226 0         0 =head2 llreq
227              
228             Low-level request generator
229              
230             =cut
231              
232             my ( $method, $uri, @args ) = @_;
233             my ( $headers, $content );
234             if ( @args ) {
235             $headers = shift @args;
236             $log->debug( "llreq: headers set to " . Dumper( $headers ) );
237 67     67 1 44738 } else {
238 67         148 $headers = [
239 67 100       180 'accept' => 'application/json',
240 50         90 'content-type' => 'application/json',
241 50         210 ];
242             }
243 17         46 if ( @args and defined( $args[0] ) ) {
244             $log->debug( "llreq: args is " . Dumper( \@args ) );
245             $content = join( ' ', @args );
246             }
247             return HTTP::Request->new( $method, $uri, $headers, $content );
248 67 100 100     12496 }
249 12         55  
250 12         2644  
251             =head2 docu_check
252 67         466  
253             Check that the resource has on-line documentation (takes Plack::Test object
254             and resource name without quotes)
255              
256             =cut
257              
258             my ( $test, $resource ) = @_;
259              
260             #diag( "Entering " . __PACKAGE__ . "::docu_check with argument $resource" );
261              
262             if ( ref( $test ) ne 'Plack::Test::MockHTTP' ) {
263             diag( "Plack::Test::MockHTTP object not passed to 'req' from " . (caller)[1] . " line " . (caller)[2] );
264 0     0 1   BAIL_OUT(0);
265             }
266              
267             my $tn = "docu_check $resource ";
268 0 0         my $t = 0;
269 0           my ( $docustr, $docustr_len );
270 0           #
271             # - straight 'docu' resource
272             my $status = req( $test, 200, 'demo', 'POST', '/docu', <<"EOH" );
273 0           { "resource" : "$resource" }
274 0           EOH
275 0           is( $status->level, 'OK', $tn . ++$t );
276             is( $status->code, 'DISPATCH_ONLINE_DOCUMENTATION', $tn . ++$t );
277             if ( exists $status->{'payload'} ) {
278 0           ok( exists $status->payload->{'resource'}, $tn . ++$t );
279             is( $status->payload->{'resource'}, $resource, $tn . ++$t );
280             ok( exists $status->payload->{'documentation'}, $tn . ++$t );
281 0           $docustr = $status->payload->{'documentation'};
282 0           $docustr_len = length( $docustr );
283 0 0         ok( $docustr_len > 10, $tn . ++$t );
284 0           isnt( $docustr, 'NOT WRITTEN YET', $tn . ++$t );
285 0           }
286 0           #
287 0           # - not a very thorough examination of the 'docu/html' version
288 0           $status = req( $test, 200, 'demo', 'POST', '/docu/html', <<"EOH" );
289 0           { "resource" : "$resource" }
290 0           EOH
291             is( $status->level, 'OK', $tn . ++$t );
292             is( $status->code, 'DISPATCH_ONLINE_DOCUMENTATION', $tn . ++$t );
293             if ( exists $status->{'payload'} ) {
294 0           ok( exists $status->payload->{'resource'}, $tn . ++$t );
295             is( $status->payload->{'resource'}, $resource, $tn . ++$t );
296             ok( exists $status->payload->{'documentation'}, $tn . ++$t );
297 0           $docustr = $status->payload->{'documentation'};
298 0           $docustr_len = length( $docustr );
299 0 0         ok( $docustr_len > 10, $tn . ++$t );
300 0           isnt( $docustr, 'NOT WRITTEN YET', $tn . ++$t );
301 0           }
302 0           }
303 0            
304 0           1;