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