File Coverage

blib/lib/App/Dochazka/REST/Test.pm
Criterion Covered Total %
statement 78 512 15.2
branch 2 122 1.6
condition 0 9 0.0
subroutine 26 62 41.9
pod 20 32 62.5
total 126 737 17.1


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-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              
38             use strict;
39 41     41   736379 use warnings;
  41         114  
  41         1024  
40 41     41   187  
  41         79  
  41         1101  
41             use App::CELL qw( $CELL $log $meta $site );
42 41     41   178 use App::Dochazka::Common;
  41         94  
  41         3548  
43 41     41   9796 use App::Dochazka::REST;
  41         167484  
  41         1579  
44 41     41   15620 use App::Dochazka::REST::Dispatch;
  41         138  
  41         1899  
45 41     41   27001 use App::Dochazka::REST::ConnBank qw( $dbix_conn conn_up );
  41         149  
  41         1764  
46 41     41   352 use App::Dochazka::REST::Util qw( hash_the_password );
  41         94  
  41         4342  
47 41     41   284 use App::Dochazka::REST::Model::Activity;
  41         99  
  41         1513  
48 41     41   248 use App::Dochazka::REST::Model::Component;
  41         87  
  41         1094  
49 41     41   217 use App::Dochazka::REST::Model::Privhistory qw( get_privhistory );
  41         4593  
  41         1639  
50 41     41   262 use App::Dochazka::REST::Model::Schedhistory qw( get_schedhistory );
  41         89  
  41         1464  
51 41     41   223 use App::Dochazka::REST::Model::Shared qw( cud_generic noof select_single );
  41         74  
  41         1465  
52 41     41   223 use Authen::Passphrase::SaltedDigest;
  41         82  
  41         1717  
53 41     41   204 use Data::Dumper;
  41         75  
  41         827  
54 41     41   206 use HTTP::Request::Common qw( GET PUT POST DELETE );
  41         100  
  41         1620  
55 41     41   257 use JSON;
  41         72  
  41         2116  
56 41     41   2837 use Params::Validate qw( :all );
  41         171  
  41         290  
57 41     41   3806 use Test::JSON;
  41         80  
  41         5195  
58 41     41   18152 use Test::More;
  41         2668478  
  41         327  
59 41     41   26798 use Try::Tiny;
  41         182917  
  41         386  
60 41     41   9599 use Web::MREST;
  41         90  
  41         1855  
61 41     41   281  
  41         124  
  41         1556  
62              
63              
64             =head1 NAME
65              
66             App::Dochazka::REST::Test - Test helper functions
67              
68              
69              
70              
71              
72             =head1 DESCRIPTION
73              
74             This module provides helper code for unit tests.
75              
76             =cut
77              
78              
79              
80              
81             =head1 EXPORTS
82              
83             =cut
84              
85             use Exporter qw( import );
86 41     41   249 our @EXPORT = qw(
  41         106  
  41         224565  
87             initialize_regression_test $faux_context
88             req dbi_err docu_check
89             create_bare_employee create_active_employee create_inactive_employee
90             delete_bare_employee delete_employee_by_nick
91             create_testing_activity delete_testing_activity
92             create_testing_interval delete_testing_interval
93             create_testing_component delete_testing_component
94             create_testing_schedule delete_testing_schedule delete_all_attendance_data
95             gen_activity gen_employee gen_interval gen_lock
96             gen_privhistory gen_schedhistory gen_schedule
97             test_sql_success test_sql_failure do_select_single
98             test_employee_list get_aid_by_code test_schedule_model
99             );
100              
101              
102              
103              
104             =head1 PACKAGE VARIABLES
105              
106             =cut
107              
108             # faux context
109             our $faux_context;
110              
111             # dispatch table with references to HTTP::Request::Common functions
112             my %methods = (
113             GET => \&GET,
114             PUT => \&PUT,
115             POST => \&POST,
116             DELETE => \&DELETE,
117             );
118              
119              
120              
121              
122             =head1 FUNCTIONS
123              
124             =cut
125              
126              
127             =head2 initialize_regression_test
128              
129             Perform the boilerplate tasks that have to be done at the beginning of every
130             test file that communicates with the Web::MREST server and/or the PostgreSQL
131             database. Since both Web::MREST and PostgreSQL are external resources,
132             tests that make use of them are more than mere unit tests
133              
134             While some test files do not need *all* of these initialization steps,
135             there is no harm in running them.
136              
137             The t/unit/ subdirectory is reserved for test files that need *none* of
138             these initialization steps. Having them in a separate subdirectory enables
139             them to be run separately.
140              
141             =cut
142              
143              
144             my $status = Web::MREST::init(
145             distro => 'App-Dochazka-REST',
146 39     39 1 23523 sitedir => '/etc/dochazka-rest',
147             );
148             plan skip_all => "Web::MREST::init failed: " . $status->text unless $status->ok;
149              
150 39 50       110731 #diag( "DOCHAZKA_STATE_DIR is set to " . $site->DOCHAZKA_STATE_DIR );
151              
152             note( "Set log level" );
153             $log->init(
154 39         750 ident => $site->MREST_APPNAME,
155 39         15430 debug_mode => 1,
156             );
157              
158             note( "Initialize" );
159             try {
160 39         8616 App::Dochazka::REST::Dispatch::init();
161             } catch {
162 39     39   1978 $status = $CELL->status_not_ok;
163             };
164 39     39   761 plan skip_all => 'Integration testing environment not detected' unless $status->ok;
165 39         10263  
166 39 50       2499 note( "Check status of database server connection" );
167             plan skip_all => "PostgreSQL server is unreachable" unless conn_up();
168 0            
169 0 0         my $eids = App::Dochazka::REST::get_eid_of( $dbix_conn, "root", "demo" );
170             $site->set( 'DOCHAZKA_EID_OF_ROOT', $eids->{'root'} );
171 0           $site->set( 'DOCHAZKA_EID_OF_DEMO', $eids->{'demo'} );
172 0            
173 0           is( $status->level, 'OK' );
174             ok( $site->DOCHAZKA_EID_OF_ROOT );
175 0           ok( $site->DOCHAZKA_EID_OF_DEMO );
176 0           ok( $site->DOCHAZKA_TIMEZONE );
177 0            
178 0           $faux_context = { 'dbix_conn' => $dbix_conn, 'current' => { 'eid' => 1 } };
179             $meta->set( 'META_DOCHAZKA_UNIT_TESTING' => 1 );
180 0            
181 0           note( "instantiate Web::Machine object for this application" );
182             my $app = Web::Machine->new( resource => 'App::Dochazka::REST::Dispatch', )->to_app;
183 0            
184 0           note( "A PSGI application is a Perl code reference. It takes exactly " .
185             "one argument, the environment and returns an array reference of exactly " .
186 0           "three values." );
187             is( ref($app), 'CODE' );
188              
189 0           note( 'initialize App::Dochazka::Common package variables $t, $today, etc.' );
190             App::Dochazka::Common::init_timepiece();
191 0            
192 0           return $app;
193             }
194 0            
195              
196             =head2 status_from_json
197              
198             L<App::Dochazka::REST> is designed to return status objects in the HTTP
199             response body. These, of course, are sent in JSON format. This simple routine
200             takes a JSON string and blesses it, thereby converting it back into a status
201             object.
202              
203             FIXME: There may be some encoding issues here!
204              
205             =cut
206              
207             my ( $json ) = @_;
208             bless from_json( $json ), 'App::CELL::Status';
209             }
210 0     0 1    
211 0            
212             =head2 req
213              
214             Assemble and process a HTTP request. Takes the following positional arguments:
215              
216             * Plack::Test object
217             * expected HTTP result code
218             * user to authenticate with (can be 'root', 'demo', or 'active')
219             * HTTP method
220             * resource string
221             * optional JSON string
222              
223             If the HTTP result code is 200, the return value will be a status object, undef
224             otherwise.
225              
226             =cut
227              
228             my ( $test, $code, $user, $method, $resource, $json ) = validate_pos( @_, 1, 1, 1, 1, 1, 0 );
229              
230             if ( ref( $test ) ne 'Plack::Test::MockHTTP' ) {
231             diag( "Plack::Test::MockHTTP object not passed to 'req' from " . (caller)[1] . " line " . (caller)[2] );
232 0     0 1   BAIL_OUT(0);
233             }
234 0 0          
235 0           # assemble request
236 0           my %pl = (
237             Accept => 'application/json',
238             Content_Type => 'application/json',
239             );
240 0           if ( $json ) {
241             $pl{'Content'} = $json;
242             }
243             my $r = $methods{$method}->( $resource, %pl );
244 0 0          
245 0           my $pass;
246             if ( $user eq 'root' ) {
247 0           $pass = 'immutable';
248             } elsif ( $user eq 'inactive' ) {
249 0           $pass = 'inactive';
250 0 0         } elsif ( $user eq 'active' ) {
    0          
    0          
    0          
251 0           $pass = 'active';
252             } elsif ( $user eq 'demo' ) {
253 0           $pass = 'demo';
254             } else {
255 0           #diag( "Unusual user $user - trying password $user" );
256             $pass = $user;
257 0           }
258              
259             $r->authorization_basic( $user, $pass );
260 0           note( "About to send request $method $resource as $user " . ( $json ? "with $json" : "" ) );
261             my $res = $test->request( $r );
262             $code += 0;
263 0           if ( $code != $res->code ) {
264 0 0         diag( Dumper $res );
265 0           BAIL_OUT(0);
266 0           }
267 0 0         is( $res->code, $code, "Response code is $code" );
268 0           my $content = $res->content;
269 0           if ( $content ) {
270             #diag( Dumper $content );
271 0           is_valid_json( $res->content, "Response entity is valid JSON" );
272 0           my $status = status_from_json( $content );
273 0 0         if ( my $location_header = $res->header( 'Location' ) ) {
274             $status->{'location_header'} = $location_header;
275 0           }
276 0           return $status;
277 0 0         }
278 0           return;
279             }
280 0            
281              
282 0           =head2 dbi_err
283              
284             Wrapper for 'req' intended to eliminate duplicated code on tests that are
285             expected to return DOCHAZKA_DBI_ERR. In addition to the arguments expected
286             by 'req', takes one additional argument, which should be:
287              
288             qr/error message subtext/
289              
290             (i.e. a regex quote by which to test the $status->text)
291              
292             =cut
293              
294             my ( $test, $code, $user, $method, $resource, $json, $qr ) = validate_pos( @_, 1, 1, 1, 1, 1, 1, 1 );
295             my $status = req( $test, $code, $user, $method, $resource, $json );
296             is( $status->level, 'ERR' );
297             ok( $status->text );
298             if ( ! ( $status->text =~ $qr ) ) {
299 0     0 1   diag( "$user $method $resource\n$json" );
300 0           diag( $status->text . " does not match $qr" );
301 0           BAIL_OUT(0);
302 0           }
303 0 0         like( $status->text, $qr );
304 0           }
305 0            
306 0            
307             =head2 docu_check
308 0            
309             Check that the resource has on-line documentation (takes Plack::Test object
310             and resource name without quotes)
311              
312             =cut
313              
314             my ( $test, $resource ) = @_;
315             #diag( "Entering " . __PACKAGE__ . "::docu_check with argument $resource" );
316              
317             if ( ref( $test ) ne 'Plack::Test::MockHTTP' ) {
318             diag( "Plack::Test::MockHTTP object not passed to 'req' from " . (caller)[1] . " line " . (caller)[2] );
319             BAIL_OUT(0);
320 0     0 1   }
321              
322             my $tn = "docu_check $resource ";
323 0 0         my $t = 0;
324 0           my ( $docustr, $docustr_len );
325 0           #
326             # - straight 'docu/pod' resource
327             my $status = req( $test, 200, 'demo', 'POST', '/docu/pod', "\"$resource\"" );
328 0           is( $status->level, 'OK', $tn . ++$t );
329 0           is( $status->code, 'DISPATCH_ONLINE_DOCUMENTATION', $tn . ++$t );
330 0           if ( exists $status->{'payload'} ) {
331             ok( exists $status->payload->{'resource'}, $tn . ++$t );
332             is( $status->payload->{'resource'}, $resource, $tn . ++$t );
333 0           ok( exists $status->payload->{'documentation'}, $tn . ++$t );
334 0           $docustr = $status->payload->{'documentation'};
335 0           $docustr_len = length( $docustr );
336 0 0         ok( $docustr_len > 10, $tn . ++$t );
337 0           isnt( $docustr, 'NOT WRITTEN YET', $tn . ++$t );
338 0           }
339 0           #
340 0           # - not a very thorough examination of the 'docu/html' version
341 0           $status = req( $test, 200, 'demo', 'POST', '/docu/html', "\"$resource\"" );
342 0           is( $status->level, 'OK', $tn . ++$t );
343 0           is( $status->code, 'DISPATCH_ONLINE_DOCUMENTATION', $tn . ++$t );
344             if ( exists $status->{'payload'} ) {
345             ok( exists $status->payload->{'resource'}, $tn . ++$t );
346             is( $status->payload->{'resource'}, $resource, $tn . ++$t );
347 0           ok( exists $status->payload->{'documentation'}, $tn . ++$t );
348 0           $docustr = $status->payload->{'documentation'};
349 0           $docustr_len = length( $docustr );
350 0 0         ok( $docustr_len > 10, $tn . ++$t );
351 0           isnt( $docustr, 'NOT WRITTEN YET', $tn . ++$t );
352 0           }
353 0           }
354 0            
355 0            
356 0           =head2 create_bare_employee
357 0            
358             For use in tests only. Spawns an employee object and inserts it into the
359             database.
360              
361             Takes PROPLIST which is passed through unmunged to the employee spawn method.
362              
363             Returns the new Employee object.
364              
365             =cut
366              
367             my ( $PROPS ) = validate_pos( @_,
368             { type => HASHREF },
369             );
370              
371             hash_the_password( $PROPS );
372              
373             my $emp = App::Dochazka::REST::Model::Employee->spawn( $PROPS );
374 0     0 1   is( ref($emp), 'App::Dochazka::REST::Model::Employee', 'create_bare_employee 1' );
375              
376             my $status = $emp->insert( $faux_context );
377             if ( $status->not_ok ) {
378 0           diag( "Employee insert method returned NOT_OK status in create_bare_employee" );
379             diag( "test automation function, which was called from " . (caller)[1] . " line " . (caller)[2] );
380 0           diag( "with arguments: " . Dumper( $PROPS ) );
381 0           diag( "Full status returned by employee insert method:" );
382             diag( Dumper $status );
383 0           BAIL_OUT(0);
384 0 0         }
385 0           is( $status->level, "OK", 'create_bare_employee 2' );
386 0           my $employee_object = $status->payload;
387 0           is( ref( $employee_object ), 'App::Dochazka::REST::Model::Employee' );
388 0            
389 0           return $employee_object;
390 0           }
391              
392 0            
393 0           =head2 delete_bare_employee
394 0            
395             Takes a single argument: the EID.
396 0            
397             Loads the EID into a new Employee object and calls that object's delete method.
398              
399             =cut
400              
401             my $eid = shift;
402             note( "delete testing employee with EID $eid" );
403             my $status = App::Dochazka::REST::Model::Employee->load_by_eid( $dbix_conn, $eid );
404             if ( $status->not_ok ) {
405             diag( "Employee load_by_eid method returned NOT_OK status in delete_bare_employee" );
406             diag( "test automation function, which was called from " . (caller)[1] . " line " . (caller)[2] );
407             diag( "with EID $eid" );
408             diag( "Full status returned by Employee load_by_eid method:" );
409 0     0 1   diag( Dumper $status );
410 0           BAIL_OUT(0);
411 0           }
412 0 0         is( $status->level, 'OK', 'delete_bare_employee 1' );
413 0           my $emp = $status->payload;
414 0           $status = $emp->delete( $faux_context );
415 0           if ( $status->not_ok ) {
416 0           diag( Dumper $status );
417 0           BAIL_OUT(0);
418 0           }
419             is( $status->level, 'OK', 'delete_bare_employee 2' );
420 0           return;
421 0           }
422 0            
423 0 0          
424 0           my ( $test, $privspec ) = @_;
425 0            
426             note("create $privspec employee");
427 0           my $eid = create_bare_employee( { nick => $privspec, password => $privspec } )->eid;
428 0           my $status = req( $test, 201, 'root', 'POST', "priv/history/eid/$eid",
429             "{ \"effective\":\"1892-01-01\", \"priv\":\"$privspec\" }" );
430             ok( $status->ok, "Create $privspec employee 2" );
431             is( $status->code, 'DOCHAZKA_CUD_OK', "Create $privspec employee 3" );
432             return $eid;
433 0     0      
434             }
435 0            
436 0           =head2 create_active_employee
437 0            
438             Create a testing employee with 'active' privilege. The employee will get an
439 0           'active' privhistory record with date 1892-01-01.
440 0            
441 0           =cut
442              
443             my ( $test ) = @_;
444             return _create_employee( $test, "active" );
445             }
446              
447              
448             =head2 create_inactive_employee
449              
450             Create a testing employee with 'inactive' privilege. The employee will get an
451             'inactive' privhistory record with date 1892-01-01.
452              
453 0     0 1   =cut
454 0            
455             my ( $test ) = @_;
456             return _create_employee( $test, "inactive" );
457             }
458              
459              
460             =head2 delete_employee_by_nick
461              
462             Delete testing employee (takes Plack::Test object and nick)
463              
464             =cut
465              
466 0     0 1   my ( $test, $nick ) = @_;
467 0           my ( $res, $status );
468              
469             # get and delete privhistory
470             $status = get_privhistory( $faux_context, nick => $nick );
471             if ( $status->level eq 'OK' and $status->code eq 'DISPATCH_RECORDS_FOUND' ) {
472             my $ph = $status->payload->{'history'};
473             # delete the privhistory records one by one
474             foreach my $phrec ( @$ph ) {
475             my $phid = $phrec->{phid};
476             $status = req( $test, 200, 'root', 'DELETE', "priv/history/phid/$phid" );
477             ok( $status->ok, "Delete employee by nick 2" );
478 0     0 1   is( $status->code, 'DOCHAZKA_CUD_OK', "Delete employee by nick 3" );
479 0           }
480             } else {
481             diag( "Unexpected return value from get_privhistory: " . Dumper( $status ) );
482 0           BAIL_OUT(0);
483 0 0 0       }
484 0            
485             # get and delete schedhistory
486 0           $status = get_schedhistory( $faux_context, nick => $nick );
487 0           if ( $status->level eq 'OK' and $status->code eq 'DISPATCH_RECORDS_FOUND' ) {
488 0           my $sh = $status->payload->{'history'};
489 0           # delete the schedhistory records one by one
490 0           foreach my $shrec ( @$sh ) {
491             my $shid = $shrec->{shid};
492             $status = req( $test, 200, 'root', 'DELETE', "schedule/history/shid/$shid" );
493 0           ok( $status->ok, "Delete employee by nick 5" );
494 0           is( $status->code, 'DOCHAZKA_CUD_OK', "Delete employee by nick 5" );
495             }
496             } elsif ( $status->level eq 'NOTICE' and $status->code eq 'DISPATCH_NO_RECORDS_FOUND' ) {
497             ok( 1, "$nick has no schedule history" );
498 0           } else {
499 0 0 0       diag( "Unexpected return value from get_schedhistory: " . Dumper( $status ) );
    0 0        
500 0           BAIL_OUT(0);
501             }
502 0            
503 0           # delete the employee record
504 0           $status = req( $test, 200, 'root', 'DELETE', "employee/nick/$nick" );
505 0           BAIL_OUT($status->text) unless $status->ok;
506 0           is( $status->level, 'OK', "Delete employee by nick 6" );
507             is( $status->code, 'DOCHAZKA_CUD_OK', "Delete employee by nick 7" );
508              
509 0           return;
510             }
511 0            
512 0            
513             =head2 create_testing_activity
514              
515             Tests will need to set up and tear down testing activities
516 0            
517 0 0         =cut
518 0            
519 0           my %PROPS = @_; # must be at least code
520              
521 0           my $act = App::Dochazka::REST::Model::Activity->spawn( \%PROPS );
522             is( ref($act), 'App::Dochazka::REST::Model::Activity', 'create_testing_activity 1' );
523             my $status = $act->insert( $faux_context );
524             if ( $status->not_ok ) {
525             BAIL_OUT( $status->code . " " . $status->text );
526             }
527             is( $status->level, "OK", 'create_testing_activity 2' );
528             return $status->payload;
529             }
530              
531              
532 0     0 1   =head2 delete_testing_activity
533              
534 0           Tests will need to set up and tear down testing activities
535 0            
536 0           =cut
537 0 0          
538 0           my $aid = shift;
539              
540 0           my $status = App::Dochazka::REST::Model::Activity->load_by_aid( $dbix_conn, $aid );
541 0           is( $status->level, 'OK', 'delete_testing_activity 1' );
542             my $act = $status->payload;
543             $status = $act->delete( $faux_context );
544             is( $status->level, 'OK', 'delete_testing_activity 2' );
545             return;
546             }
547              
548              
549             =head2 create_testing_interval
550              
551             Tests will need to set up and tear down testing intervals
552 0     0 1    
553             =cut
554 0            
555 0           my %PROPS = @_; # must be at least code
556 0            
557 0           my $act = App::Dochazka::REST::Model::Interval->spawn( \%PROPS );
558 0           is( ref($act), 'App::Dochazka::REST::Model::Interval', 'create_testing_interval 1' );
559 0           my $status = $act->insert( $faux_context );
560             if ( $status->not_ok ) {
561             BAIL_OUT( $status->code . " " . $status->text );
562             }
563             is( $status->level, "OK", 'create_testing_interval 2' );
564             return $status->payload;
565             }
566              
567              
568             =head2 delete_testing_interval
569              
570 0     0 1   Tests will need to set up and tear down testing intervals
571              
572 0           =cut
573 0            
574 0           my $iid = shift;
575 0 0          
576 0           my $status = App::Dochazka::REST::Model::Interval->load_by_iid( $dbix_conn, $iid );
577             is( $status->level, 'OK', 'delete_testing_interval 1' );
578 0           my $int = $status->payload;
579 0           $status = $int->delete( $faux_context );
580             is( $status->level, 'OK', 'delete_testing_interval 2' );
581             return;
582             }
583              
584              
585             =head2 create_testing_component
586              
587             Tests will need to set up and tear down testing components
588              
589             =cut
590 0     0 1    
591             my %PROPS = @_; # must be at least path
592 0            
593 0           my $comp = App::Dochazka::REST::Model::Component->spawn( \%PROPS );
594 0           is( ref($comp), 'App::Dochazka::REST::Model::Component', 'create_testing_component 1' );
595 0           my $status = $comp->insert( $faux_context );
596 0           if ( $status->not_ok ) {
597 0           BAIL_OUT( $status->code . " " . $status->text );
598             }
599             is( $status->level, "OK", 'create_testing_component 2' );
600             return $status->payload;
601             }
602              
603              
604             =head2 delete_testing_component
605              
606             Tests will need to set up and tear down testing components
607              
608 0     0 1   =cut
609              
610 0           my $cid = shift;
611 0            
612 0           my $status = App::Dochazka::REST::Model::Component->load_by_cid( $dbix_conn, $cid );
613 0 0         is( $status->level, 'OK', 'delete_testing_component 1' );
614 0           my $act = $status->payload;
615             $status = $act->delete( $faux_context );
616 0           is( $status->level, 'OK', 'delete_testing_component 2' );
617 0           return;
618             }
619              
620              
621             =head2 test_schedule_model
622              
623             Creates and returns a testing schedule without needing a L<Plack::Test> object.
624              
625             =cut
626              
627             my $intvls = shift;
628 0     0 1    
629             note('create an arbitrary schedule' );
630 0           note('at the beginning, count of schedintvls should be 0');
631 0           is( noof( $dbix_conn, 'schedintvls' ), 0 );
632 0            
633 0           note('spawn a schedintvls ("scratch schedule") object');
634 0           my $schedintvls = App::Dochazka::REST::Model::Schedintvls->spawn;
635 0           ok( ref($schedintvls), "object is a reference" );
636             isa_ok( $schedintvls, 'App::Dochazka::REST::Model::Schedintvls' );
637             ok( defined( $schedintvls->{ssid} ), "Scratch SID is defined" );
638             ok( $schedintvls->{ssid} > 0, "Scratch SID is > 0" );
639              
640             note('insert a schedule (i.e. a list of schedintvls)');
641             $schedintvls->{intvls} = $intvls;
642              
643             note('insert all the schedintvls in one go');
644             my $status = $schedintvls->insert( $dbix_conn );
645             diag( $status->text ) unless $status->ok;
646 0     0 1   ok( $status->ok, "OK scratch intervals inserted OK" );
647             ok( $schedintvls->ssid, "OK there is a scratch SID" );
648 0           my $count = scalar @{ $schedintvls->{intvls} };
649 0           ok( $count );
650 0            
651             note("after insert, count of schedintvls should be $count");
652 0           is( noof( $dbix_conn, 'schedintvls' ), $count );
653 0            
654 0           note('load the schedintvls, translating them as we go');
655 0           $status = $schedintvls->load( $dbix_conn );
656 0           ok( $status->ok, "OK scratch intervals translated OK" );
657 0           is( scalar @{ $schedintvls->{intvls} }, $count, "Still have $count intervals" );
658             is( scalar @{ $schedintvls->{schedule} }, $count, "And now have $count translated intervals as well" );
659 0           like( $status->code, qr/$count rows/, "status code says $count rows" );
660 0           like( $status->text, qr/$count rows/, "status code says $count rows" );
661             ok( exists $schedintvls->{schedule}->[0]->{high_time}, "Conversion to hash OK" );
662 0           is_valid_json( $schedintvls->json );
663 0            
664 0 0         note('insert the JSON into the schedules table');
665 0           my $schedule = App::Dochazka::REST::Model::Schedule->spawn(
666 0           schedule => $schedintvls->json,
667 0           scode => 'test1',
  0            
668 0           remark => 'TESTING',
669             );
670 0           $status = $schedule->insert( $faux_context );
671 0           ok( $status->ok, "Schedule insert OK" );
672             ok( $schedule->sid > 0, "There is an SID" );
673 0           is( $schedule->scode, 'test1', "scode accessor returns correct value" );
674 0           is_valid_json( $schedule->schedule );
675 0           is( $schedule->remark, 'TESTING' );
676 0            
  0            
677 0           note( 'delete the schedintvls' );
  0            
678 0           $status = $schedintvls->delete( $dbix_conn );
679 0           ok( $status->ok, "scratch intervals deleted" );
680 0           like( $status->text, qr/$count record/, "$count records deleted" );
681 0           is( noof( $dbix_conn, 'schedintvls' ), 0 );
682              
683 0           return $schedule;
684 0           }
685              
686              
687             =head2 create_testing_schedule
688              
689 0           Tests will need to set up and tear down testing schedules. Takes a Plack::Test
690 0           object as its only argument.
691 0            
692 0           =cut
693 0            
694 0           my ( $test ) = @_;
695              
696 0           note( "Create a testing schedule" );
697 0            
698 0           my $intvls = { "schedule" => [
699 0           "[2000-01-02 12:30, 2000-01-02 16:30)",
700 0           "[2000-01-02 08:00, 2000-01-02 12:00)",
701             "[2000-01-01 12:30, 2000-01-01 16:30)",
702 0           "[2000-01-01 08:00, 2000-01-01 12:00)",
703             "[1999-12-31 12:30, 1999-12-31 16:30)",
704             "[1999-12-31 08:00, 1999-12-31 12:00)",
705             ], "scode" => 'KOBOLD' };
706             my $intvls_json = JSON->new->utf8->canonical(1)->encode( $intvls );
707             #
708             # - request as root
709             my $status = req( $test, 201, 'root', 'POST', "schedule/new", $intvls_json );
710             is( $status->level, 'OK', 'POST schedule/new returned OK status' );
711             is( $status->code, 'DISPATCH_SCHEDULE_INSERT_OK', "POST schedule/new code " . $status->code );
712             ok( exists $status->{'payload'} );
713             ok( exists $status->payload->{'sid'}, 'there is a SID' );
714 0     0 1   ok( exists $status->payload->{'scode'}, 'there is an scode' );
715              
716 0           return $status->payload->{'sid'};
717             }
718 0            
719              
720             =head2 delete_testing_schedule
721              
722             Tests will need to set up and tear down testing schedule. Takes a SID as its
723             only argument.
724              
725             =cut
726 0            
727             my ( $sid ) = @_;
728              
729 0           note( "delete testing schedule (SID $sid)" );
730 0            
731 0           my $status = App::Dochazka::REST::Model::Schedule->load_by_sid( $dbix_conn, $sid );
732 0           is( $status->level, 'OK', 'delete_testing_schedule: load OK' );
733 0           if ( $status->not_ok ) {
734 0           diag( Dumper $status );
735             BAIL_OUT(0);
736 0           }
737              
738             my $sched = $status->payload;
739             $status = $sched->delete( $faux_context );
740             is( $status->level, 'OK', 'delete_testing_schedule: delete OK' );
741             if ( $status->not_ok ) {
742             diag( Dumper $status );
743             BAIL_OUT(0);
744             }
745             return;
746             }
747              
748 0     0 1    
749             =head2 delete_all_attendance_data
750 0            
751             Wipe out all attendance data by deleting all rows from all tables (in the correct
752 0           order).
753 0            
754 0 0         To be called like this:
755 0            
756 0           $status = delete_all_attendance_data();
757             BAIL_OUT(0) unless $status->ok;
758              
759 0           =cut
760 0            
761 0            
762 0 0         note( 'delete locks' );
763 0           my $status = cud_generic(
764 0           conn => $dbix_conn,
765             eid => $site->DOCHAZKA_EID_OF_ROOT,
766 0           sql => 'DELETE FROM locks',
767             );
768             is( $status->level, 'OK' );
769             is( $status->code, 'DOCHAZKA_CUD_OK' );
770             return $status unless $status->ok;
771              
772             note( 'delete intervals' );
773             $status = cud_generic(
774             conn => $dbix_conn,
775             eid => $site->DOCHAZKA_EID_OF_ROOT,
776             sql => 'DELETE FROM intervals',
777             );
778             is( $status->level, 'OK' );
779             is( $status->code, 'DOCHAZKA_CUD_OK' );
780             return $status unless $status->ok;
781              
782             note( 'delete activities' );
783             $status = cud_generic(
784 0     0 1   conn => $dbix_conn,
785 0           eid => $site->DOCHAZKA_EID_OF_ROOT,
786             sql => 'DELETE FROM activities',
787             );
788             is( $status->level, 'OK' );
789             is( $status->code, 'DOCHAZKA_CUD_OK' );
790 0           return $status unless $status->ok;
791 0            
792 0 0         note( 're-initialize activities table' );
793             $status = App::Dochazka::REST::initialize_activities_table( $dbix_conn );
794 0           return $status unless $status->ok;
795 0            
796             note( 'delete schedhistory' );
797             $status = cud_generic(
798             conn => $dbix_conn,
799             eid => $site->DOCHAZKA_EID_OF_ROOT,
800 0           sql => 'DELETE FROM schedhistory',
801 0           );
802 0 0         is( $status->level, 'OK' );
803             is( $status->code, 'DOCHAZKA_CUD_OK' );
804 0           return $status unless $status->ok;
805 0            
806             note( 'delete privhistory' );
807             $status = cud_generic(
808             conn => $dbix_conn,
809             eid => $site->DOCHAZKA_EID_OF_ROOT,
810 0           sql => 'DELETE FROM privhistory WHERE eid != ?',
811 0           bind_params => [ $site->DOCHAZKA_EID_OF_ROOT ],
812 0 0         );
813             is( $status->level, 'OK' );
814 0           is( $status->code, 'DOCHAZKA_CUD_OK' );
815 0           return $status unless $status->ok;
816 0 0          
817             note( 'delete schedules' );
818 0           $status = cud_generic(
819 0           conn => $dbix_conn,
820             eid => $site->DOCHAZKA_EID_OF_ROOT,
821             sql => 'DELETE FROM schedules WHERE scode != \'DEFAULT\'',
822             );
823             is( $status->level, 'OK' );
824 0           is( $status->code, 'DOCHAZKA_CUD_OK' );
825 0           return $status unless $status->ok;
826 0 0          
827             note( 'delete tempintvls' );
828 0           $status = cud_generic(
829 0           conn => $dbix_conn,
830             eid => $site->DOCHAZKA_EID_OF_ROOT,
831             sql => 'DELETE FROM tempintvls',
832             );
833             is( $status->level, 'OK' );
834             is( $status->code, 'DOCHAZKA_CUD_OK' );
835 0           return $status unless $status->ok;
836 0            
837 0 0         note( 'delete employees' );
838             $status = cud_generic(
839 0           conn => $dbix_conn,
840 0           eid => $site->DOCHAZKA_EID_OF_ROOT,
841             sql => 'DELETE FROM employees WHERE eid != ? AND eid != ?',
842             bind_params => [ $site->DOCHAZKA_EID_OF_ROOT, $site->DOCHAZKA_EID_OF_DEMO ],
843             );
844             is( $status->level, 'OK' );
845 0           is( $status->code, 'DOCHAZKA_CUD_OK' );
846 0            
847 0 0         return $status;
848             }
849 0            
850 0            
851             #
852             # functions to perform class-specific 'create', 'retrieve', 'delete', etc. actions
853             #
854              
855 0           my $dis = shift;
856 0           my $code = 'FOOBAR';
857 0 0          
858             if ( $dis eq 'create' ) {
859 0            
860 0           # create 'FOOBAR' activity
861             my $act = App::Dochazka::REST::Model::Activity->spawn( code => $code );
862             my $status = $act->insert( $faux_context );
863             if( $status->level ne 'OK' ) {
864             diag( Dumper $status );
865             BAIL_OUT(0);
866 0           }
867 0           is( $status->level, 'OK' );
868             $act = $status->payload;
869 0           is( $act->code, $code );
870             ok( $act->aid > 5 );
871             return $act;
872              
873             } elsif ( $dis eq 'retrieve' ) {
874              
875             my $status = App::Dochazka::REST::Model::Activity->load_by_code( $dbix_conn, $code );
876             return $status;
877              
878 0     0 0   } elsif ( $dis eq 'delete' ) {
879 0            
880             my $status = App::Dochazka::REST::Model::Activity->load_by_code( $dbix_conn, $code );
881 0 0         is( $status->level, 'OK' );
    0          
    0          
882             my $act = $status->payload;
883             $status = $act->delete( $faux_context );
884 0           is( $status->level, 'OK' );
885 0           return;
886 0 0        
887 0           }
888 0           diag( "gen_activity: AAAAAAHHHHH@@@!! \$dis " . Dumper( $dis ) );
889             BAIL_OUT(0);
890 0           }
891 0            
892 0            
893 0           my $dis = shift;
894 0           my $nick = 'bubbaTheCat';
895              
896             if ( $dis eq 'create' ) {
897              
898 0           # create bubbaTheCat employee
899 0           my $emp = App::Dochazka::REST::Model::Employee->spawn( nick => $nick );
900             my $status = $emp->insert( $faux_context );
901             is( $status->level, 'OK' );
902             $emp = $status->payload;
903 0           is( $emp->nick, $nick );
904 0           ok( $emp->eid > 2 ); # root is 1, demo is 2
905 0           return $emp;
906 0            
907 0           } elsif ( $dis eq 'retrieve' ) {
908 0            
909             my $status = App::Dochazka::REST::Model::Employee->load_by_nick( $dbix_conn, $nick );
910             return $status;
911 0            
912 0           } elsif ( $dis eq 'delete' ) {
913              
914             my $status = App::Dochazka::REST::Model::Employee->load_by_nick( $dbix_conn, $nick );
915             is( $status->level, 'OK' );
916             my $emp = $status->payload;
917 0     0 0   $status = $emp->delete( $faux_context );
918 0           is( $status->level, 'OK' );
919             return;
920 0 0        
    0          
    0          
921             }
922             diag( "gen_employee: AAAAAAHHHHH@@@!! \$dis " . Dumper( $dis ) );
923 0           BAIL_OUT(0);
924 0           }
925 0            
926 0            
927 0           my $dis = shift;
928 0           my $eid = $site->DOCHAZKA_EID_OF_ROOT;
929 0           my $aid = App::Dochazka::REST::Model::Activity->load_by_code( $dbix_conn, 'WORK' )->aid;
930             if ( $dis eq 'create' ) {
931              
932             # create an interval
933 0           my $int = App::Dochazka::REST::Model::Interval->spawn(
934 0           eid => $eid,
935             aid => $aid,
936             intvl => "['1950-06-30 09:00', '1950-06-30 10:00')",
937             );
938 0           my $status = $int->insert( $faux_context );
939 0           is( $status->level, 'OK' );
940 0           $int = $status->payload;
941 0           is( $int->eid, $eid );
942 0           is( $int->aid, $aid );
943 0           ok( $int->iid > 0 );
944             # FIXME: use "state" variable to store iid for use in retrieve
945             return $int;
946 0            
947 0           } elsif ( $dis eq 'retrieve' ) {
948              
949             # my $status = App::Dochazka::REST::Model::Interval->load_by_iid( $dbix_conn, $iid );
950             # return $status;
951              
952 0     0 0   } elsif ( $dis eq 'delete' ) {
953 0            
954 0           }
955 0 0         diag( "gen_interval: AAAAAAHHHHH@@@!! \$dis " . Dumper( $dis ) );
    0          
    0          
956             BAIL_OUT(0);
957             }
958 0            
959              
960             my $dis = shift;
961             if ( $dis eq 'create' ) {
962              
963 0           } elsif ( $dis eq 'retrieve' ) {
964 0            
965 0           } elsif ( $dis eq 'delete' ) {
966 0          
967 0           }
968 0           diag( "gen_lock: AAAAAAHHHHH@@@!! \$dis " . Dumper( $dis ) );
969             BAIL_OUT(0);
970 0           }
971              
972              
973             my $dis = shift;
974             if ( $dis eq 'create' ) {
975              
976             } elsif ( $dis eq 'retrieve' ) {
977              
978             } elsif ( $dis eq 'delete' ) {
979            
980 0           }
981 0           diag( "gen_privhistory: AAAAAAHHHHH@@@!! \$dis " . Dumper( $dis ) );
982             BAIL_OUT(0);
983             }
984              
985             my $dis = shift;
986 0     0 0   if ( $dis eq 'create' ) {
987 0 0          
    0          
    0          
988             } elsif ( $dis eq 'retrieve' ) {
989            
990             } elsif ( $dis eq 'delete' ) {
991            
992             }
993             diag( "gen_schedhistory: AAAAAAHHHHH@@@!! \$dis " . Dumper( $dis ) );
994 0           BAIL_OUT(0);
995 0           }
996              
997             my $dis = shift;
998             if ( $dis eq 'create' ) {
999              
1000 0     0 0   } elsif ( $dis eq 'retrieve' ) {
1001 0 0          
    0          
    0          
1002             } elsif ( $dis eq 'delete' ) {
1003            
1004             }
1005             diag( "gen_schedule: AAAAAAHHHHH@@@!! \$dis " . Dumper( $dis ) );
1006             BAIL_OUT(0);
1007             }
1008 0            
1009 0           my ( $conn, $expected_rv, $sql ) = @_;
1010             my ( $rv, $errstr );
1011             try {
1012             $conn->run( fixup => sub {
1013 0     0 0   $rv = $_->do($sql);
1014 0 0         });
    0          
    0          
1015             } catch {
1016             $errstr = $_;
1017             };
1018             if ( $errstr ) {
1019             diag( "Unexpected error in test_sql_success: $errstr" );
1020             diag( "Called from " . (caller)[1] . " line " . (caller)[2] );
1021 0           BAIL_OUT(0);
1022 0           }
1023             is( $rv, $expected_rv, "successfully executed $sql" );
1024             }
1025              
1026 0     0 0   my ( $conn, $expected_err, $sql ) = @_;
1027 0 0         my ( $rv, $errstr );
    0          
    0          
1028             try {
1029             $conn->run( fixup => sub {
1030             $rv = $_->do($sql);
1031             });
1032             } catch {
1033             $errstr = $_;
1034 0           };
1035 0           is( $rv, undef, "DBI returned undef" );
1036             like( $errstr, $expected_err, "DBI errstr is as expected" );
1037             }
1038              
1039 0     0 0   my ( $conn, $sql, @keys ) = @_;
1040 0           #diag( "do_select_single: connection OK" ) if ref( $conn ) eq 'DBIx::Connector';
1041             #diag( "do_select_single: SQL statement is $sql" ) if $sql;
1042             #diag( "do_select_single: keys are ", join(', ', @keys) ) if @keys;
1043 0           my $status = select_single( conn => $conn, sql => $sql, keys => \@keys );
1044 0     0     #diag( Dumper $status );
1045             is( $status->level, 'OK' );
1046 0     0     is( $status->code, 'DISPATCH_RECORDS_FOUND' );
1047 0           ok( $status->payload );
1048 0 0         is( ref( $status->payload ), 'ARRAY' );
1049 0           return @{ $status->payload };
1050 0           }
1051 0          
1052             my ( $status, $nicks ) = @_;
1053 0           is( $status->level, 'OK' );
1054             is( $status->code, 'DISPATCH_LIST_EMPLOYEE_NICKS' );
1055             is_deeply( $status->payload, $nicks );
1056             }
1057 0     0 0    
1058 0           my ( $test, $code ) = @_;
1059             my $status = req( $test, 200, 'root', 'GET', "activity/code/$code" );
1060             is( $status->level, 'OK' );
1061 0           is( $status->code, 'DISPATCH_ACTIVITY_FOUND' );
1062 0     0     ok( $status->{'payload'} );
1063             ok( $status->{'payload'}->{'aid'} );
1064 0     0     is( $status->{'payload'}->{'code'}, uc( $code ) );
1065 0           return $status->{'payload'}->{'aid'};
1066 0           }
1067 0            
1068             1;