Group
Extension

App-Dochazka-REST/t/dispatch/employee.t

# *************************************************************************
# Copyright (c) 2014-2017, SUSE LLC
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of SUSE LLC nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# *************************************************************************
#
# test employee resources
#

#!perl
use 5.012;
use strict;
use warnings;
use utf8;

#use App::CELL::Test::LogToFile;
use App::CELL qw( $log $meta $site );
use App::Dochazka::REST::ConnBank qw( $dbix_conn );
use App::Dochazka::REST::Test;
use Data::Dumper;
use JSON;
use Plack::Test;
use Test::JSON;
use Test::More;
use Test::Warnings;

note( 'initialize, connect to database, and set up a testing plan' );
my $app = initialize_regression_test();

note( 'check a random site param' );
ok( $site->DOCHAZKA_DBUSER );

note( 'instantiate Plack::Test object' );
my $test = Plack::Test->create( $app );
isa_ok( $test, 'Plack::Test::MockHTTP' );

my $res;


note( '=============================' );
note( '"employee/count/?:priv" resource' );
note( '=============================' );
my $base = 'employee/count';
docu_check( $test, "$base/?:priv" );

note( "GET $base/?:priv" );
note( '- fail 403 as demo' );
my $status = req( $test, 403, 'demo', 'GET', $base );

note( 'succeed as root' );
$status = req( $test, 200, 'root', 'GET', $base );
is( $status->level, 'OK', "GET $base 2" );
is( $status->code, 'DISPATCH_COUNT_EMPLOYEES', "GET $base 3" );

note( 'demonstrate that :priv parameter is case-insensitive' );
foreach my $priv ( qw( passerby PASSERBY paSsERby inactive
    INACTIVE inAcTive active ACTIVE actIVe admin ADMIN AdmiN
) ) {
    #diag( "$base/$priv" );
    $status = req( $test, 200, 'root', 'GET', "$base/$priv" );
    is( $status->level, "OK", "GET $base/:priv 2" );
    if( $status->code ne 'DISPATCH_COUNT_EMPLOYEES' ) {
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->code, 'DISPATCH_COUNT_EMPLOYEES', "GET $base/:priv 3" );
    ok( defined $status->payload, "GET $base/:priv 4" );
    ok( exists $status->payload->{'priv'}, "GET $base/:priv 5" );
    is( $status->payload->{'priv'}, lc $priv, "GET $base/:priv 6" );
    ok( exists $status->payload->{'count'}, "GET $base/:priv 7" );
    #
    req( $test, 403, 'demo', 'GET', "$base/$priv" );
}

note( 'demonstrate that invalid values of :priv are not accepted' );
foreach my $priv (
    '*pimpled teenager*',
    'nanaan',
    '%^%#$#',
#    'Žluťoucký kǔň',
    '      dfdf fifty-five sixty-five',
    'passerbies',
    '///adfd/asdf/asdf',
) {
    req( $test, 400, 'root', 'GET', "$base/$priv" );
    req( $test, 400, 'demo', 'GET', "$base/$priv" );
}

note( "PUT, POST, DELETE $base" );

note( 'fail 405 as demo, active, root, WOMBAT' );
$status = req( $test, 405, 'demo', 'PUT', $base );
$status = req( $test, 405, 'active', 'PUT', $base );
$status = req( $test, 405, 'WOMBAT', 'PUT', $base );
$status = req( $test, 405, 'root', 'PUT', $base );
$status = req( $test, 405, 'demo', 'POST', $base );
$status = req( $test, 405, 'active', 'POST', $base );
$status = req( $test, 405, 'root', 'POST', $base );
$status = req( $test, 405, 'demo', 'DELETE', $base );
$status = req( $test, 405, 'active', 'DELETE', $base );
$status = req( $test, 405, 'root', 'DELETE', $base );

$base .= '/admin';

note( "PUT, POST, DELETE $base" );

note( 'fail 405 for demo, active, root' );
$status = req( $test, 405, 'demo', 'PUT', $base );
$status = req( $test, 405, 'active', 'PUT', $base );
$status = req( $test, 405, 'root', 'PUT', $base );
$status = req( $test, 405, 'demo', 'POST', $base );
$status = req( $test, 405, 'active', 'POST', $base );
$status = req( $test, 405, 'root', 'POST', $base );
$status = req( $test, 405, 'demo', 'DELETE', $base );
$status = req( $test, 405, 'active', 'DELETE', $base );
$status = req( $test, 405, 'root', 'DELETE', $base );


note( '=============================' );
note( '"employee/self" resource' );
note( '=============================' );

my $ts_eid_inactive = create_inactive_employee( $test );
my $ts_eid_active = create_active_employee( $test );

foreach my $base ( "employee/self" ) {
    docu_check($test, $base);
    
    note( "looping GET $base" );
    $status = req( $test, 200, 'demo', 'GET', $base );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_SELF', "GET $base 3" );
    ok( defined $status->payload, "GET $base 4" );
    is_deeply( $status->payload, {
        'eid' => 2,
        'sec_id' => undef,
        'nick' => 'demo',
        'fullname' => 'Demo Employee',
        'email' => 'demo@dochazka.site',
        'supervisor' => undef,
        'sync' => 0,
    }, "GET $base 5");
    #
    $status = req( $test, 200, 'root', 'GET', $base );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_SELF', "GET $base 8" );
    ok( defined $status->payload, "GET $base 9" );
    is_deeply( $status->payload, {
        'eid' => 1,
        'sec_id' => undef,
        'nick' => 'root',
        'fullname' => 'Root Immutable',
        'email' => 'root@site.org',
        'supervisor' => undef,
        'remark' => 'dbinit',
        'sync' => 0,
    }, "GET $base 10" );
    
    note( "looping: PUT $base" );
    $status = req( $test, 405, 'demo', 'PUT', $base );
    $status = req( $test, 405, 'active', 'PUT', $base );
    $status = req( $test, 405, 'root', 'PUT', $base );
    
    note( "looping: POST $base" );
    note( "- default configuration is that 'active' and 'inactive' can modify" );
    note( '  their own passhash and salt fields; demo should *not* be ' );
    note( ' authorized to do this' );

    req( $test, 403, 'demo', 'POST', $base, '{ "password":"saltine" }' );
    foreach my $user ( "active", "inactive" ) {
        #
        #diag( "$user $base " . '{ "password" : "saltine" }' );
        $status = req( $test, 200, $user, 'POST', $base, '{ "password" : "saltine" }' );
        if ( $status->not_ok ) {
            diag( Dumper $status );
            BAIL_OUT(0);
        }
        is( $status->level, 'OK' );
        is( $status->code, 'DOCHAZKA_CUD_OK' ); 
        
        note( '- use root to change it back, otherwise the user won\'t be able' );
        note( '  to log in and next tests will fail' );
        $status = req( $test, 200, 'root', 'PUT', "employee/nick/$user", "{ \"password\" : \"$user\" }" );
        is( $status->level, 'OK' );
        is( $status->code, 'DOCHAZKA_CUD_OK' ); 
        
        note( '- legal but bogus JSON in body' );
        $status = req( $test, 200, $user, 'POST', $base, 0 );
        is( $status->level, 'OK' );
        is( $status->code, 'DISPATCH_UPDATE_NO_CHANGE_OK' ); 
        
        note( "- 'salt' is a permitted field, but 'inactive'/$user employees" );
        note( "  should not, for example, be allowed to change 'nick'" );
        req( $test, 403, $user, 'POST', $base, '{ "nick": "wanger" }' );
    }
    
    note( 'root can theoretically update any field, but certain fields of its own' );
    note( 'profile are immutable' );
    $status = req( $test, 200, 'root', 'POST', $base, '{ "email": "root@rotoroot.com" }' );
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    
    $status = req( $test, 200, 'root', 'POST', $base, '{ "email": "root@site.org" }' );
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
   
    dbi_err( $test, 500, 'root', 'POST', $base, '{ "nick": "aaaaazz" }', qr/root employee is immutable/ );
    

    note( "DELETE $base" );
    $status = req( $test, 405, 'demo', 'DELETE', $base );
    $status = req( $test, 405, 'active', 'DELETE', $base );
    $status = req( $test, 405, 'root', 'DELETE', $base );
}


note( '=============================' );
note( '"employee/self/full" resource' );
note( '=============================' );

$base = "employee/self";
my $resource = "$base/full";
docu_check( $test, $resource );

foreach my $originator ( 'demo', 'inactive', 'active', 'root' ) {

    my $uri;
    if ( $base eq 'employee/nick' ) {
        $uri = "employee/nick/$originator/full";
    } elsif ( $base eq 'employee/self' ) {
        $uri = 'employee/self/full';
    } else {
        diag( "Bad loop!" );
        BAIL_OUT(0);
    }

    $status = req( $test, 200, $originator, 'GET', $uri );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_PROFILE_FULL' );
    ok( defined $status->payload );
    ok( exists $status->payload->{'emp'} );
    ok( exists $status->payload->{'has_reports'} );
    ok( exists $status->payload->{'priv'} );
    ok( exists $status->payload->{'privhistory'} );
    ok( exists $status->payload->{'schedule'} );
    ok( exists $status->payload->{'schedhistory'} );
    is( $status->payload->{'emp'}->{'nick'}, $originator );
    if ( $originator eq 'demo' ) {
        is( $status->payload->{'priv'}, 'passerby' );
        is( $status->payload->{'privhistory'}, undef );
    } elsif ( $originator eq 'inactive' ) {
        is( $status->payload->{'priv'}, 'inactive' );
        is( ref( $status->payload->{'privhistory'} ), 'HASH' );
        ok( exists $status->payload->{'privhistory'}->{'phid'} );
    } elsif ( $originator eq 'active' ) {
        is( $status->payload->{'priv'}, 'active' );
        is( ref( $status->payload->{'privhistory'} ), 'HASH' );
        ok( exists $status->payload->{'privhistory'}->{'phid'} );
    } elsif ( $originator eq 'root' ) {
        is( $status->payload->{'priv'}, 'admin' );
        is( ref( $status->payload->{'privhistory'} ), 'HASH' );
        ok( exists $status->payload->{'privhistory'}->{'phid'} );
    } else {
        diag( "bad \$originator ($originator) in test loop" );
        BAIL_OUT(0);
    }
    is( $status->payload->{'schedule'}, undef );
    is( $status->payload->{'schedhistory'}, undef );

    note( "PUT, POST, DELETE $resource" );
    $status = req( $test, 405, $originator, 'PUT', $uri );
    $status = req( $test, 405, $originator, 'POST', $uri );
    $status = req( $test, 405, $originator, 'DELETE', $uri );

}

    
note( '=============================' );
note( '"employee/eid/:eid/full" resource' );
note( '"employee/nick/:nick/full" resource' );
note( '=============================' );

map { docu_check( $test, $_ ); } 
    ( 'employee/eid/:eid/full', 'employee/nick/:nick/full' );

my %eid_map = (
    'demo' => $site->DOCHAZKA_EID_OF_DEMO,
    'inactive' => $ts_eid_inactive,
    'active' => $ts_eid_active,
    'root' => $site->DOCHAZKA_EID_OF_ROOT,
);

foreach my $nick ( 'demo' ) {
    my $eid = $eid_map{$nick};
    foreach my $uri ( "employee/eid/$eid/full", "employee/nick/$nick/full" ) {
        note( "$nick tries and fails to use $uri resource" );
        req( $test, 403, $nick, 'GET', $uri );
        req( $test, 405, $nick, 'PUT', $uri );
        req( $test, 405, $nick, 'POST', $uri );
        req( $test, 405, $nick, 'DELETE', $uri );
    }
}

foreach my $nick ( 'demo', 'inactive', 'active' ) {
    foreach my $uri ( "employee/eid/1/full", "employee/nick/root/full" ) {
        note( "$nick tries and fails to use $uri resource" );
        req( $test, 403, $nick, 'GET', $uri );
        req( $test, 405, $nick, 'PUT', $uri );
        req( $test, 405, $nick, 'POST', $uri );
        req( $test, 405, $nick, 'DELETE', $uri );
    }
}

sub _employee_full_success {
    my ( $originator, $nick, $uri ) = @_;

    note( "$nick tries and succeeds to use $uri resource" );
    $status = req( $test, 200, $originator, 'GET', $uri );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_PROFILE_FULL' );
    ok( defined $status->payload );
    ok( exists $status->payload->{'emp'} );
    ok( exists $status->payload->{'priv'} );
    ok( exists $status->payload->{'privhistory'} );
    ok( exists $status->payload->{'schedule'} );
    ok( exists $status->payload->{'schedhistory'} );
    is( $status->payload->{'emp'}->{'nick'}, $nick );
    if ( $nick eq 'demo' ) {
        is( $status->payload->{'priv'}, 'passerby' );
        is( $status->payload->{'privhistory'}, undef );
    } elsif ( $nick eq 'inactive' ) {
        is( $status->payload->{'priv'}, 'inactive' );
        is( ref( $status->payload->{'privhistory'} ), 'HASH' );
        ok( exists $status->payload->{'privhistory'}->{'phid'} );
    } elsif ( $nick eq 'active' ) {
        is( $status->payload->{'priv'}, 'active' );
        is( ref( $status->payload->{'privhistory'} ), 'HASH' );
        ok( exists $status->payload->{'privhistory'}->{'phid'} );
    } elsif ( $nick eq 'root' ) {
        is( $status->payload->{'priv'}, 'admin' );
        is( ref( $status->payload->{'privhistory'} ), 'HASH' );
        ok( exists $status->payload->{'privhistory'}->{'phid'} );
    } else {
        diag( "bad \$nick ($nick) in test loop" );
        BAIL_OUT(0);
    }
    is( $status->payload->{'schedule'}, undef );
    is( $status->payload->{'schedhistory'}, undef );
}

foreach my $nick ( 'active', 'root' ) {
    my $eid = $eid_map{$nick};
    foreach my $uri ( "employee/eid/$eid/full", "employee/nick/$nick/full" ) {

        _employee_full_success( $nick, $nick, $uri );

        note( "PUT, POST, DELETE $resource" );
        req( $test, 405, $nick, 'PUT', $uri );
        req( $test, 405, $nick, 'POST', $uri );
        req( $test, 405, $nick, 'DELETE', $uri );
    }
}

foreach my $uri ( "employee/eid/$ts_eid_inactive/full", "employee/nick/inactive/full" ) {
    _employee_full_success( 'root', 'inactive', $uri );
}


note( '=============================' );
note( '"employee/eid" resource' );
note( '=============================' );
$base = "employee/eid";

note( "docu_check on $base" );
docu_check($test, "employee/eid");

note( "GET, PUT: $base" );
req( $test, 405, 'demo', 'GET', $base );
req( $test, 405, 'active', 'GET', $base );
req( $test, 405, 'root', 'GET', $base );
req( $test, 405, 'demo', 'PUT', $base );
req( $test, 405, 'active', 'PUT', $base );
req( $test, 405, 'root', 'PUT', $base );

note( "POST: $base" );

note( "create a 'mrfu' employee" );
my $mrfu = create_bare_employee( { nick => 'mrfu', password => 'mrfu' } );
my $eid_of_mrfu = $mrfu->eid;

# these tests break when 'email' is added to DOCHAZKA_PROFILE_EDITABLE_FIELDS
## - give Mr. Fu an email address
##req( $test, 403, 'demo', 'POST', $base, '{ "eid": ' . $mrfu->eid . ', "email" : "shake it" }' );
# 
##is( $mrfu->nick, 'mrfu' );
##req( $test, 403, 'mrfu', 'POST', $base, '{ "eid": ' . $mrfu->eid . ', "email" : "shake it" }' );
# fails because mrfu is a passerby

note( "make mrfu an inactive" );
$status = req( $test, 201, 'root', 'POST', "priv/history/eid/" . $mrfu->eid, <<"EOH" );
{ "priv" : "inactive", "effective" : "2004-01-01" }
EOH
is( $status->level, "OK", 'POST employee/eid 3' );
is( $status->code, "DOCHAZKA_CUD_OK", 'POST employee/eid 3' );
ok( exists $status->payload->{'phid'} );
my $mrfu_phid = $status->payload->{'phid'};

# these tests break when 'email' is added to DOCHAZKA_PROFILE_EDITABLE_FIELDS
## - try the operation again - it still fails because inactives can not change their email
##req( $test, 403, 'mrfu', 'POST', $base, '{ "eid": ' . $mrfu->eid . ', "email" : "shake it" }' );

note( "inactive mrfu can change his password" );
$status = req( $test, 200, 'mrfu', 'POST', $base, '{ "eid": ' . $mrfu->eid . ', "password" : "shake it" }' );
is( $status->level, "OK", 'POST employee/eid 3' );
is( $status->code, 'DOCHAZKA_CUD_OK', 'POST employee/eid 4' );

note( "but now mrfu cannot log in, because req assumes password is 'mrfu'" );
req( $test, 401, 'mrfu', 'GET', 'employee/nick/mrfu' );

note( "so, use root powers to change the password back" );
$eid_of_mrfu = $mrfu->eid;
$status = req( $test, 200, 'root', 'POST', $base, <<"EOH" );
{ "eid" : $eid_of_mrfu, "password" : "mrfu" }
EOH
is( $status->level, "OK", 'POST employee/eid 3' );
is( $status->code, "DOCHAZKA_CUD_OK", 'POST employee/eid 3' );

note( "and now mrfu can log in" );
$status = req( $test, 200, 'mrfu', 'GET', 'employee/nick/mrfu' );
is( $status->level, "OK", 'POST employee/eid 3' );
is( $status->payload->{'remark'}, undef );
is( $status->payload->{'sec_id'}, undef );
is( $status->payload->{'nick'}, 'mrfu' );
is( $status->payload->{'email'}, undef );
is( $status->payload->{'fullname'}, undef );

note( "attempt by demo to update mrfu to a different nick" );
#diag("--- POST employee/eid (update with different nick)");
req( $test, 403, 'demo', 'POST', $base, '{ "eid": ' . $mrfu->eid . ', "nick" : "mrsfu" , "fullname":"Dragoness" }' );

note( "use root power to update mrfu to a different nick" ); 
$status = req( $test, 200, 'root', 'POST', $base, '{ "eid": ' . $mrfu->eid . ', "nick" : "mrsfu" , "fullname":"Dragoness" }' );
is( $status->level, 'OK', 'POST employee/eid 8' );
is( $status->code, 'DOCHAZKA_CUD_OK', 'POST employee/eid 9' );
my $mrsfu = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
my $mrsfuprime = App::Dochazka::REST::Model::Employee->spawn( eid => $mrfu->eid,
    nick => 'mrsfu', fullname => 'Dragoness' );
is( $mrsfu->eid, $mrsfuprime->eid, 'POST employee/eid 10' );
is( $mrsfu->nick, $mrsfuprime->nick, 'POST employee/eid 10' );
is( $mrsfu->fullname, $mrsfuprime->fullname, 'POST employee/eid 10' );
is( $mrsfu->email, $mrsfuprime->email, 'POST employee/eid 10' );
is( $mrsfu->remark, $mrsfuprime->remark, 'POST employee/eid 10' );

note( "attempt as demo and root to update Mr./Mrs. Fu to a non-existent EID" );
#diag("--- POST employee/eid (non-existent EID)");
req( $test, 403, 'demo', 'POST', $base, '{ "eid" : 5442' );
req( $test, 400, 'root', 'POST', $base, '{ "eid" : 5442' );
req( $test, 403, 'demo', 'POST', $base, '{ "eid" : 5442 }' );
req( $test, 404, 'root', 'POST', $base, '{ "eid" : 5442 }' );
req( $test, 404, 'root', 'POST', $base, '{ "eid": 534, "nick": "mrfu", "fullname":"Lizard Scale" }' );

note( 'missing EID' );
req( $test, 400, 'root', 'POST', $base, '{ "long-john": "silber" }' );

note( 'incorrigibly attempt to update totally bogus and invalid EIDs' );
req( $test, 400, 'root', 'POST', $base, '{ "eid" : }' );
req( $test, 400, 'root', 'POST', $base, '{ "eid" : jj }' );
$status = req( $test, 500, 'root', 'POST', $base, '{ "eid" : "jj" }' );
like( $status->text, qr/invalid input syntax for type integer/ );

note( 'and give it a bogus parameter (on update, bogus parameters cause REST to' );
note( 'return 200 status code with DISPATCH_UPDATE_NO_CHANGE_OK; on insert, they are ignored)' );
$status = req( $test, 200, 'root', 'POST', $base, '{ "eid" : 2, "bogus" : "json" }' ); 
is( $status->level, "OK", "POST $base with bogus property in body 1" );
is( $status->code, 'DISPATCH_UPDATE_NO_CHANGE_OK', "POST $base with bogus property in body 2" );

note( 'update to existing nick' );
dbi_err( $test, 500, 'root', 'POST', $base, 
    '{ "eid": ' . $mrfu->eid . ', "nick" : "root" , "fullname":"Tom Wang" }',
    qr/Key \(nick\)=\(root\) already exists/ );

note( 'update nick to null' );
dbi_err( $test, 500, 'root', 'POST', $base, 
    '{ "eid": ' . $mrfu->eid . ', "nick" : null  }',
    qr/null value in column "nick" violates not-null constraint/ );

note( 'inactive and active users get a little piece of the action, too:' );
note( 'they can operate on themselves (certain fields), but not on, e.g., Mr. Fu' );
foreach my $user ( qw( demo inactive active ) ) {
    req( $test, 403, $user, 'POST', $base, <<"EOH" );
{ "eid" : $eid_of_mrfu, "passhash" : "HAHAHAHA" }
EOH
}
foreach my $user ( qw( demo inactive active ) ) {
    $status = req( $test, 200, 'root', 'GET', "employee/nick/$user" );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
    is( ref( $status->payload ), 'HASH' );
    my $eid = $status->payload->{'eid'};
    req( $test, 403, $user, 'POST', $base, <<"EOH" );
{ "eid" : $eid, "nick" : "tHE gREAT fABULATOR" }
EOH
}
foreach my $user ( qw( inactive active ) ) {
    $status = req( $test, 200, 'root', 'GET', "employee/nick/$user" );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
    is( ref( $status->payload ), 'HASH' );
    my $eid = $status->payload->{'eid'};
    $status = req( $test, 200, $user, 'POST', $base, <<"EOH" );
{ "eid" : $eid, "password" : "tHE gREAT fABULATOR" }
EOH
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    
    note( "$user can no longer log in because Test.pm expects password to be same as $user" );
    req( $test, 401, $user, 'GET', "employee/nick/$user" );
    
    note( "use root power to change $user\'s password back to $user" );
    $status = req( $test, 200, 'root', 'POST', $base, <<"EOH" );
{ "eid" : $eid, "password" : "$user" }
EOH
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
}



note( "teardown: delete the testing user mrfu" );

note( "first, delete his privhistory entry" );
$status = req( $test, 200, 'root', 'DELETE', "priv/history/phid/$mrfu_phid" );
ok( $status->ok );

note( "then, delete the employee" );
delete_bare_employee( $eid_of_mrfu );

note( "DELETE $base" );
req( $test, 405, 'demo', 'DELETE', $base );
req( $test, 405, 'active', 'DELETE', $base );
req( $test, 405, 'root', 'DELETE', $base );


note( '=============================' );
note( '"employee/eid/:eid" resource' );
note( '=============================' );
$base = 'employee/eid';
docu_check($test, "$base/:eid");

my @invalid_eids = (
    '342j',
    '**12',
    'fenestre',
    '1234/123/124/',
);

note( "GET $base/:eid" );

note( "normal usage: get employee with nick [0], eid [2], fullname [3] as employee" );
note( "with nick [1]" );
foreach my $params (
    [ 'root', 'root', $site->DOCHAZKA_EID_OF_ROOT, 'Root Immutable' ],
    [ 'demo', 'root', 2, 'Demo Employee' ],
    [ 'active', 'root', $ts_eid_active, undef ],
    [ 'active', 'active', $ts_eid_active, undef ],
    [ 'inactive', 'root', $ts_eid_inactive, undef ],
) {
    $status = req( $test, 200, $params->[1], 'GET', "$base/" . $params->[2] );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
    ok( defined $status->payload );
    ok( exists $status->payload->{'eid'} );
    is( $status->payload->{'eid'}, $params->[2] );
    ok( exists $status->payload->{'nick'} );
    is( $status->payload->{'nick'}, $params->[0] );
    ok( exists $status->payload->{'fullname'} );
    is( $status->payload->{'fullname'}, $params->[3] );
}

note( "GET $base/2 as demo" );
req( $test, 200, 'demo', 'GET', "$base/2" );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( "GET $base/53432 as root" );
req( $test, 404, 'root', 'GET', "$base/53432" );

note( "GET $base/53432 as demo" );
req( $test, 403, 'demo', 'GET', "$base/53432" );

note( 'invalid EIDs caught by Path::Router validations clause' );
foreach my $eid ( @invalid_eids ) {
    foreach my $user ( qw( root demo ) ) {
        req( $test, 400, $user, 'GET', "$base/$eid" );
    }
}

note( 'as demonstrated above, an active employee can see his own profile using this' );
note( 'resource -- demonstrate it again' );
$status = req( $test, 200, 'active', 'GET', "$base/$ts_eid_active" );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( 'an \'inactive\' employee can do the same' );
$status = req( $test, 200, 'inactive', 'GET', "$base/$ts_eid_inactive" );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( 'and, indeed, demo as well' );
req( $test, 200, 'demo', 'GET', "$base/2" );  # EID 2 is 'demo'
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( 'unknown user gets 401' );
req( $test, 401, 'unknown', 'GET', "$base/2" );  # EID 2 is 'demo'

note( 'non-administrators cannot use this resource to look at other employees' );
foreach my $user ( qw( active inactive demo ) ) {
    my $status = req( $test, 403, $user, 'GET', "$base/1" );
}

note( "PUT $base/:eid" );

note( "create a testing employee 'brotherchen'" );
my $emp = create_bare_employee( {
    nick => 'brotherchen',
    email => 'goodbrother@orient.cn',
    fullname => 'Good Brother Chen',
} );
my $eid_of_brchen = $emp->{eid};
is( $eid_of_brchen, $emp->eid );

note( "insufficient priv" );
req( $test, 403, 'demo', 'PUT', "$base/$eid_of_brchen",
    '{ "eid": ' . $eid_of_brchen . ', "fullname":"Chen Update Again" }' );

note( "be nice" );
req( $test, 403, 'demo', 'PUT', "$base/$eid_of_brchen",
    '{ "fullname":"Chen Update Again", "salt":"tasty" }' );
$status = req( $test, 200, 'root', 'PUT', "$base/$eid_of_brchen",
    '{ "fullname":"Chen Update Again", "salt":"tasty" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
my $brchen = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
is( $brchen->eid, $eid_of_brchen );
my $brchenprime = App::Dochazka::REST::Model::Employee->spawn( eid => $eid_of_brchen,
    nick => 'brotherchen', email => 'goodbrother@orient.cn', fullname =>
    'Chen Update Again', salt => 'tasty', sync => 0 );
is_deeply( $brchen, $brchenprime );

note( "provide invalid EID in request body -> it will be ignored" );
$status = req( $test, 200, 'root', 'PUT', "$base/$eid_of_brchen",
    '{ "eid": 99999, "fullname":"Chen Update Again 2" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$brchen = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
isnt( $brchen->eid, 99999 );
is( $brchen->eid, $eid_of_brchen );
$brchenprime = App::Dochazka::REST::Model::Employee->spawn( eid => $eid_of_brchen,
    nick => 'brotherchen', email => 'goodbrother@orient.cn', fullname =>
    'Chen Update Again 2', salt => 'tasty', sync => 0 );
is_deeply( $brchen, $brchenprime );

note( 'change the nick' );
req( $test, 403, 'demo', 'PUT', "$base/$eid_of_brchen", '{' );
req( $test, 400, 'root', 'PUT', "$base/$eid_of_brchen", '{' );
req( $test, 403, 'demo', 'PUT', "$base/$eid_of_brchen", '{ "nick": "mrfu", "fullname":"Lizard Scale" }' );
$status = req( $test, 200, 'root', 'PUT', "$base/$eid_of_brchen",
    '{ "nick": "mrfu", "fullname":"Lizard Scale", "email":"mrfu@dragon.cn" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$mrfu = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
isnt( $mrfu->nick, 'brotherchen' );
is( $mrfu->nick, 'mrfu' );
my $mrfuprime = App::Dochazka::REST::Model::Employee->spawn( eid => $eid_of_brchen,
    nick => 'mrfu', fullname => 'Lizard Scale', email => 'mrfu@dragon.cn',
    salt => 'tasty', sync => 0 );
is_deeply( $mrfu, $mrfuprime );
$eid_of_mrfu = $mrfu->eid;
is( $eid_of_mrfu, $eid_of_brchen );

note( 'provide non-existent EID' );
req( $test, 403, 'demo', 'PUT', "$base/5633", '{' );
req( $test, 404, 'root', 'PUT', "$base/5633", '{' );
req( $test, 403, 'demo', 'PUT', "$base/5633",
    '{ "nick": "mrfu", "fullname":"Lizard Scale" }' );
req( $test, 404, 'root', 'PUT', "$base/5633",
    '{ "eid": 534, "nick": "mrfu", "fullname":"Lizard Scale" }' );

note( 'with valid JSON that is not what we are expecting' );
req( $test, 400, 'root', 'PUT', "$base/2", 0 );

note( 'another kind of bogus JSON' );
$status = req( $test, 200, 'root', 'PUT', "$base/2", '{ "legal" : "json" }' );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_UPDATE_NO_CHANGE_OK' ); 

note( 'invalid EIDs caught by Path::Router validations clause' );
foreach my $eid ( @invalid_eids ) {
    foreach my $user ( qw( root demo ) ) {
        req( $test, 400, $user, 'PUT', "$base/$eid" );
    }
}

note( 'inactive and active users get a little piece of the action, too:' );
note( 'they can operate on themselves (certain fields), but not on, e.g., Mr. Fu' );
foreach my $user ( qw( demo inactive active ) ) {
    req( $test, 403, $user, 'PUT', "$base/$eid_of_mrfu", <<"EOH" );
{ "passhash" : "HAHAHAHA" }
EOH
}
foreach my $user ( qw( demo inactive active ) ) {
    $status = req( $test, 200, 'root', 'GET', "employee/nick/$user" );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
    is( ref( $status->payload ), 'HASH' );
    my $eid = $status->payload->{'eid'};
    req( $test, 403, $user, 'PUT', "$base/$eid", <<"EOH" );
{ "nick" : "tHE gREAT fABULATOR" }
EOH
}
foreach my $user ( qw( inactive active ) ) {
    $status = req( $test, 200, 'root', 'GET', "employee/nick/$user" );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
    is( ref( $status->payload ), 'HASH' );
    my $eid = $status->payload->{'eid'};
    $status = req( $test, 200, $user, 'PUT', "$base/$eid", <<"EOH" );
{ "password" : "tHE gREAT fABULATOR" }
EOH
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    
    note( "so far so good, but now we can\'t log in because Test.pm assumes password is $user" );
    req( $test, 401, $user, 'GET', "$base/$eid" );
    
    note( 'change it back' );
    $status = req( $test, 200, 'root', 'PUT', "$base/$eid", "{ \"password\" : \"$user\" }" );
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    
    note( 'working again' );
    $status = req( $test, 200, 'root', 'GET', "employee/nick/$user" );
    is( $status->level, 'OK' );
    is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
    is( ref( $status->payload ), 'HASH' );
}

note( 'delete the \'brotherchen\' testing user' );
delete_bare_employee( $eid_of_brchen );

note( "POST $base/:eid" );
req( $test, 405, 'demo', 'POST', "$base/2" );
req( $test, 405, 'active', 'POST', "$base/2" );
req( $test, 405, 'root', 'POST', "$base/2" );

note( "DELETE $base/:eid" );

note( 'create a "cannon fodder" employee' );
my $cf = create_bare_employee( { nick => 'cannonfodder' } );
my $eid_of_cf = $cf->eid;

note( 'employee/eid/:eid - delete cannonfodder' );
req( $test, 403, 'demo', 'DELETE', "$base/$eid_of_cf" );
req( $test, 403, 'active', 'DELETE', "$base/$eid_of_cf" ); 
req( $test, 401, 'unknown', 'DELETE', "$base/$eid_of_cf" ); # 401 because 'unknown' doesn't exist
$status = req( $test, 200, 'root', 'DELETE', "$base/$eid_of_cf" );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );

note( 'attempt to get cannonfodder - not there anymore' );
req( $test, 403, 'demo', 'GET', "$base/$eid_of_cf" );
req( $test, 404, 'root', 'GET', "$base/$eid_of_cf" );

note( 'create another "cannon fodder" employee' );
$cf = create_bare_employee( { nick => 'cannonfodder' } );
ok( $cf->eid > $eid_of_cf ); # EID will have incremented
$eid_of_cf = $cf->eid;

note( 'delete the sucker' );
req( $test, 403, 'demo', 'DELETE', '/employee/nick/cannonfodder' );
$status = req( $test, 200, 'root', 'DELETE', '/employee/nick/cannonfodder' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );

note( 'attempt to get cannonfodder - not there anymore' );
req( $test, 403, 'demo', 'GET',  "$base/$eid_of_cf" );
req( $test, 404, 'root', 'GET',  "$base/$eid_of_cf" );

note( 'attempt to delete "root the immutable" (won\'t work)' );
dbi_err( $test, 500, 'root', 'DELETE', "$base/1", undef, qr/immutable/i );

note( 'invalid EIDs caught by Path::Router validations clause' );
foreach my $eid ( @invalid_eids ) {
    foreach my $user ( qw( root demo ) ) {
        req( $test, 400, $user, 'GET', "$base/$eid" );
    }
}


note( '=============================' );
note( '"employee/eid/:eid/minimal" resource' );
note( '=============================' );
$base = 'employee/eid';
docu_check($test, "$base/:eid/minimal");

note( 'root attempt to get non-existent EID (minimal)' );
req( $test, 404, 'root', 'GET', "$base/53432/minimal" );

note( 'demo attempt to get non-existent EID (minimal)' );
req( $test, 403, 'demo', 'GET', "$base/53432/minimal" );

note( 'demo attempt to get existent EID (minimal)' );
note( 'DOCHAZKA_EMPLOYEE_MINIMAL_FIELDS is ' . Dumper( $site->DOCHAZKA_EMPLOYEE_MINIMAL_FIELDS ) );
req( $test, 403, 'demo', 'GET', "$base/" . $site->DOCHAZKA_EID_OF_ROOT . "/minimal" );

note( 'root get active (minimal)' );
$status = req( $test, 200, 'root', 'GET', "$base/$ts_eid_active/minimal" );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_EMPLOYEE_MINIMAL' );
ok( $status->payload );
is( ref( $status->payload ), 'HASH' );
ok( $status->payload->{'nick'} );
is( $status->payload->{'nick'}, 'active' );
is( $status->payload->{'eid'}, $ts_eid_active );
is( join( '', sort( keys( %{ $status->payload } ) ) ),
    join( '', sort( @{ $site->DOCHAZKA_EMPLOYEE_MINIMAL_FIELDS } ) ) );

note( '=============================' );
note( '"employee/eid/:eid/team" resource' );
note( '=============================' );
$base = "employee/eid";
docu_check($test, "$base/:eid/team" );

note( 'FIXME: NO TESTS!!!' );


note( '=============================' );
note( '"employee/list/?:priv" resource' );
note( '=============================' );
$base = "employee/list";
docu_check($test, "employee/list/?:priv");

note( 'GET employee/list/?:priv' );
req( $test, 403, 'demo', 'GET', $base );
$status = req( $test, 200, 'root', 'GET', $base );
test_employee_list( $status, [ 'active', 'demo', 'inactive', 'root' ] );
$status = req( $test, 200, 'root', 'GET', "$base/admin" );
test_employee_list( $status, [ 'root' ] );
$status = req( $test, 200, 'root', 'GET', "$base/active" );
test_employee_list( $status, [ 'active' ] );
$status = req( $test, 200, 'root', 'GET', "$base/inactive" );
test_employee_list( $status, [ 'inactive' ] );
$status = req( $test, 200, 'root', 'GET', "$base/passerby" );
test_employee_list( $status, [ 'demo' ] );

note( 'PUT, POST, DELETE employee/list/?:priv' );
req( $test, 405, 'demo', 'PUT', $base );
req( $test, 405, 'root', 'PUT', $base );
req( $test, 405, 'demo', 'POST', $base );
req( $test, 405, 'root', 'POST', $base );
req( $test, 405, 'demo', 'DELETE', $base );
req( $test, 405, 'root', 'DELETE', $base );


note( "=============================" );
note( '"employee/nick" resource' );
note( "=============================" );
$base = "employee/nick";
docu_check($test, "employee/nick");

note( 'GET, PUT employee/nick' );
req( $test, 405, 'demo', 'GET', $base );
req( $test, 405, 'root', 'GET', $base );
req( $test, 405, 'demo', 'PUT', $base );
req( $test, 405, 'root', 'PUT', $base );

note( 'POST employee/nick' );

note( 'create a \'mrfu\' employee' );
$mrfu = create_bare_employee( { nick => 'mrfu' } );
my $nick_of_mrfu = $mrfu->nick;
$eid_of_mrfu = $mrfu->eid;

note( 'give Mr. Fu an email address' );
my $j = '{ "nick": "' . $nick_of_mrfu . '", "email" : "mrsfu@dragon.cn" }';
req( $test, 403, 'demo', 'POST', $base, $j );

$status = req( $test, 200, 'root', 'POST', $base, $j );
is( $status->level, "OK" );
is( $status->code, 'DOCHAZKA_CUD_OK' );
is( $status->payload->{'email'}, 'mrsfu@dragon.cn' );

note( "non-existent nick (insert new employee)" );
req( $test, 403, 'demo', 'POST', $base, '{ "nick" : 5442' );
req( $test, 400, 'root', 'POST', $base, '{ "nick" : 5442' );
req( $test, 403, 'demo', 'POST', $base, '{ "nick" : 5442 }' );

note( 'attempt to insert new employee with bogus "eid" value' );
$status = req( $test, 200, 'root', 'POST', $base,
    '{ "eid": 534, "nick": "mrfutra", "fullname":"Rovnou do futer" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
is( $status->payload->{'nick'}, 'mrfutra' );
is( $status->payload->{'fullname'}, 'Rovnou do futer' );
isnt( $status->payload->{'eid'}, 534 );
my $eid_of_mrfutra = $status->payload->{'eid'};

note( 'bogus property' );
$status = req( $test, 400, 'root', 'POST', $base, '{ "Nick" : "foobar" }' );

note( 'cleanup: delete the testing users' );
delete_bare_employee( $eid_of_mrfu );
delete_bare_employee( $eid_of_mrfutra );

note( 'add a new employee with nick in request body' );
req( $test, 403, 'demo', 'POST', $base, '{' );
req( $test, 400, 'root', 'POST', $base, '{' );
req( $test, 403, 'demo', 'POST', $base, 
    '{ "nick":"mrfu", "fullname":"Dragon Scale" }' );
$status = req( $test, 200, 'root', 'POST', $base, 
    '{ "nick":"mrfu", "fullname":"Dragon Scale", "email":"mrfu@dragon.cn" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$mrfu = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
$mrfuprime = App::Dochazka::REST::Model::Employee->spawn( eid => $mrfu->eid, 
    nick => 'mrfu', fullname => 'Dragon Scale', email => 'mrfu@dragon.cn', 
    sync => 0 );
is_deeply( $mrfu, $mrfuprime );
$eid_of_mrfu = $mrfu->eid;

note( "POST valid, yet bogus JSON (bogus property will be ignored and wombat employee inserted)" );
$status = req( $test, 200, 'root', 'POST', $base, 
    '{ "nick" : "wombat", "bogus" : "json" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
my $eid_of_wombat = $status->payload->{'eid'};

note( 'GET employee/nick/wombat' );
$status = req( $test, 200, 'root', 'GET', '/employee/nick/wombat' );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
my $wombat_emp = App::Dochazka::REST::Model::Employee->spawn( $status->payload );

note( 'POST with valid, yet bogus JSON -- wombat exists, so this is an update' );
note( 'operation, but after the bogus property is filtered out there is no work to do' );
$status = req( $test, 200, 'root', 'POST', $base, 
    '{ "nick" : "wombat", "bogus" : "json" }' );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_UPDATE_NO_CHANGE_OK' );

note( 'cleanup: delete wombat employee' ); 
delete_bare_employee( $eid_of_wombat );

note( "update existing employee" );
req( $test, 403, 'demo', 'POST', $base, 
    '{ "nick":"mrfu", "fullname":"Dragon Scale Update", "email" : "scale@dragon.org" }' );
$status = req( $test, 200, 'root', 'POST', $base, 
    '{ "nick":"mrfu", "fullname":"Dragon Scale Update", "email" : "scale@dragon.org" }' );
is( $status->level, "OK" );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$mrfu = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
$mrfuprime = App::Dochazka::REST::Model::Employee->spawn( eid => $eid_of_mrfu,
    nick => 'mrfu', fullname => 'Dragon Scale Update', email => 'scale@dragon.org',
    sync => 0 );
is_deeply( $mrfu, $mrfuprime );

note( 'create a bogus user with a bogus property' );
$status = req( $test, 200, 'root', 'POST', $base, 
    '{ "nick":"bogus", "wago":"svorka", "fullname":"bogus user" }' );
is( $status->level, "OK" );
is( $status->code, 'DOCHAZKA_CUD_OK' );
my $eid_of_bogus = $status->payload->{'eid'};

note( 'delete testing employees' );
map { delete_bare_employee( $_ ); } ( $eid_of_mrfu, $eid_of_bogus );

note( "DELETE employee/nick" );
req( $test, 405, 'demo', 'DELETE', $base );
req( $test, 405, 'root', 'DELETE', $base );


note( '=============================' );
note( '"employee/nick/:nick" resource' );
note( '=============================' );
$base = "employee/nick";
docu_check($test, "employee/nick/:nick");

note( 'GET employee/nick/:nick' );

note( 'nick: root' );
$status = req( $test, 200, 'root', 'GET', "$base/root" );
is( $status->level, "OK" );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
ok( defined $status->payload );
ok( exists $status->payload->{'eid'} );
is( $status->payload->{'eid'}, $site->DOCHAZKA_EID_OF_ROOT );
ok( exists $status->payload->{'nick'} );
is( $status->payload->{'nick'}, 'root' );
ok( exists $status->payload->{'fullname'} );
is( $status->payload->{'fullname'}, 'Root Immutable' );

note( 'nick: demo' );
$status = req( $test, 200, 'root', 'GET', "$base/demo" );
is( $status->level, "OK" );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
ok( defined $status->payload );
ok( exists $status->payload->{'eid'} );
is( $status->payload->{'eid'}, 2 );
ok( exists $status->payload->{'nick'} );
is( $status->payload->{'nick'}, 'demo' );
ok( exists $status->payload->{'fullname'} );
is( $status->payload->{'fullname'}, 'Demo Employee' );

note( "GET $base/demo" );
req( $test, 200, 'demo', 'GET', "$base/demo" );
is( $status->level, "OK" );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( "GET $base/nick/{various bogus nicks}" );
req( $test, 404, 'root', 'GET', "$base/53432" );
req( $test, 403, 'demo', 'GET', "$base/53432" );
req( $test, 404, 'root', 'GET', "$base/heathledger" );

# this one triggers "wide character in print" warnings
#req( $test, 404, 'root', 'GET', "$base/" . uri_escape_utf8('/employee/nick//////áěěoěščqwšáščšýš..-...-...-..-.00') );

note( 'single-character nicks' );
$status = req( $test, 404, 'root', 'GET', "$base/4" );


note( "PUT employee/nick/:nick" );

note( 'demo cannot PUT no matter what' );
req( $test, 403, 'demo', 'PUT', "$base/mrsfu", '{' );

note( 'root can PUT, but JSON entity is invalid' );
req( $test, 400, 'root', 'PUT', "$base/mrsfu", '{' );

note( 'demo cannot PUT no matter what' );
req( $test, 403, 'demo', 'PUT', "$base/mrsfu", 
    '{ "fullname":"Dragonness" }' );

note( 'insert happy path' );
$status = req( $test, 200, 'root', 'PUT', "$base/mrsfu", 
    '{ "fullname":"Dragonness" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$mrsfu = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
$mrsfuprime = App::Dochazka::REST::Model::Employee->spawn( eid => $mrsfu->eid, 
    nick => 'mrsfu', fullname => 'Dragonness', sync => 0 );
is_deeply( $mrsfu, $mrsfuprime );
my $eid_of_mrsfu = $mrsfu->eid;

note( 'insert pathological paths' );

note( 'provide conflicting \'nick\' property in the content body' );
req( $test, 403, 'demo', 'PUT', "$base/hapless", '{' );
req( $test, 400, 'root', 'PUT', "$base/hapless", '{' );
req( $test, 403, 'demo', 'PUT', "$base/hapless", 
    '{ "nick":"INVALID", "fullname":"Anders Chen" }' );
$status = req( $test, 200, 'root', 'PUT', "$base/hapless", 
    '{ "nick":"INVALID", "fullname":"Anders Chen" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
my $hapless = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
isnt( $hapless->nick, 'INVALID' );
is( $hapless->nick, 'hapless' );
my $haplessprime = App::Dochazka::REST::Model::Employee->spawn( eid => $hapless->eid, 
    nick => 'hapless', fullname => 'Anders Chen', sync => 0 );
is_deeply( $hapless, $haplessprime );
my $eid_of_hapless = $hapless->eid;

note( "update happy path" );
$status = req( $test, 200, 'root', 'PUT', "$base/hapless", 
    '{ "fullname":"Chen Update", "salt":"none, please" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$hapless = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
is( $hapless->nick, "hapless" );
is( $hapless->fullname, "Chen Update" );
is( $hapless->salt, "none, please" );
$haplessprime = App::Dochazka::REST::Model::Employee->spawn( eid => $eid_of_hapless,
    nick => 'hapless', fullname => 'Chen Update', salt => "none, please",
    sync => 0 );
is_deeply( $hapless, $haplessprime );

note( "update: change salt to null" );
$status = req( $test, 200, 'root', 'PUT', "$base/hapless", 
    '{ "fullname":"Chen Update", "salt":null }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$hapless = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
is( $hapless->nick, "hapless" );
is( $hapless->fullname, "Chen Update" );
is( $hapless->salt, undef );
$haplessprime = App::Dochazka::REST::Model::Employee->spawn( eid => $eid_of_hapless,
    nick => 'hapless', fullname => 'Chen Update', sync => 0 );
is_deeply( $hapless, $haplessprime );

note( "update: pathological paths" );

note( 'attempt to set a bogus EID' );
$status = req( $test, 200, 'root', 'PUT', "$base/hapless",
    '{ "eid": 534, "fullname":"Good Brother Chen", "salt":"" }' );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
$hapless = App::Dochazka::REST::Model::Employee->spawn( %{ $status->payload } );
is( $hapless->fullname, "Good Brother Chen" );
is( $hapless->eid, $eid_of_hapless );
isnt( $hapless->eid, 534 );
$haplessprime = App::Dochazka::REST::Model::Employee->spawn( eid => $eid_of_hapless,
    nick => 'hapless', fullname => 'Good Brother Chen', salt => '', sync => 0 );
is_deeply( $hapless, $haplessprime );

note( 'attempt to change nick to null' );
dbi_err( $test, 500, 'root', 'PUT', "$base/hapless",
    '{ "nick":null }', qr/violates not-null constraint/ );

note( 'feed it some random bogusness' );
$status = req( $test, 200, 'root', 'PUT', "$base/hapless", '{ "legal" : "json" }' );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_UPDATE_NO_CHANGE_OK' ); 

note( 'inactive and active users can not change passwords of other users' );
foreach my $user ( qw( demo inactive active ) ) {
    foreach my $target ( qw( mrsfu hapless ) ) {
        req( $test, 403, $user, 'PUT', "$base/$target", <<"EOH" );
{ "passhash" : "HAHAHAHA" }
EOH
    }
}

note( 'clean up testing employees' );
delete_bare_employee( $eid_of_mrsfu );
delete_bare_employee( $eid_of_hapless );

note( 'POST employee/nick:nick' );
req( $test, 405, 'demo', 'POST', "$base/root" );
req( $test, 405, 'root', 'POST', "$base/root" );

note( 'DELETE employee/nick/:nick' );

note( 'create a "cannon fodder" employee' );
$cf = create_bare_employee( { nick => 'cannonfodder' } );
ok( $cf->eid > 1 );
$eid_of_cf = $cf->eid;

note( 'get cannonfodder - no problem' );
$status = req( $test, 200, 'root', 'GET', "$base/cannonfodder" );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( 'DELETE "employee/nick/:nick" with nick cannonfodder' );
req( $test, 403, 'demo', 'DELETE', $base . "/" . $cf->nick );
$status = req( $test, 200, 'root', 'DELETE', $base . "/" . $cf->nick );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );

note( 'attempt to get cannonfodder - not there anymore' );
req( $test, 404, 'root', 'GET', "$base/cannonfodder" );

note( 'attempt to get in a different way' );
$status = App::Dochazka::REST::Model::Employee->load_by_nick( $dbix_conn, 'cannonfodder' );
is( $status->level, 'NOTICE' );
is( $status->code, 'DISPATCH_NO_RECORDS_FOUND' );

note( 'create another "cannon fodder" employee' );
$cf = create_bare_employee( { nick => 'cannonfodder' } );
ok( $cf->eid > $eid_of_cf ); # EID will have incremented
$eid_of_cf = $cf->eid;

note( 'get cannonfodder - again, no problem' );
$status = req( $test, 200, 'root', 'GET', "$base/cannonfodder" );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( 'delete with a typo (non-existent nick)' );
req( $test, 403, 'demo', 'DELETE', "$base/cannonfoddertypo" );
req( $test, 404, 'root', 'DELETE', "$base/cannonfoddertypo" );

note( 'attempt to get cannonfodder - still there' );
$status = req( $test, 200, 'root', 'GET', "$base/cannonfodder" );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );

note( 'tear down testing employee' );
delete_bare_employee( $eid_of_cf );

note( 'attempt to delete \'root the immutable\' (won\'t work)' );
dbi_err( $test, 500, 'root', 'DELETE', "$base/root", undef, qr/immutable/i );


note( '=============================' );
note( '"employee/nick/:nick/minimal" resource' );
note( '=============================' );
$base = 'employee/nick';
docu_check($test, "$base/:nick/minimal");

note( 'root attempt to get non-existent nick (minimal)' );
req( $test, 404, 'root', 'GET', "$base/53432/minimal" );

note( 'demo attempt to get non-existent nick (minimal)' );
req( $test, 403, 'demo', 'GET', "$base/53432/minimal" );

note( 'demo attempt to get existent nick (minimal)' );
req( $test, 403, 'demo', 'GET', "$base/root/minimal" );

note( 'root get active (minimal)' );
$status = req( $test, 200, 'root', 'GET', "$base/active/minimal" );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_EMPLOYEE_MINIMAL' );
ok( $status->payload );
is( ref( $status->payload ), 'HASH' );
ok( $status->payload->{'nick'} );
is( $status->payload->{'nick'}, 'active' );
is( $status->payload->{'eid'}, $ts_eid_active );
is( join( '', sort( keys( %{ $status->payload } ) ) ),
    join( '', sort( @{ $site->DOCHAZKA_EMPLOYEE_MINIMAL_FIELDS } ) ) );


note( '=============================' );
note( '"employee/nick/:nick/team" resource' );
note( '=============================' );
$base = "employee/nick";
docu_check($test, "$base/:nick/team" );

note( 'FIXME: NO TESTS!!!' );


note( '=============================' );
note( '"employee/search/nick/:key" resource' );
note( '=============================' );
$base = "employee/search/nick";
docu_check($test, "$base/:key");
# 
# - with wildcard == 'ro%'
$status = req( $test, 200, 'root', 'GET', "$base/ro%" );
is( $status->level, "OK" );
is( $status->code, 'DISPATCH_RECORDS_FOUND' );
ok( defined $status->payload );
is( ref( $status->payload ), 'ARRAY' );
is( scalar( @{ $status->payload } ), 1 );
is( $status->payload->[0]->{'nick'}, 'root' );
#ok( exists $status->payload->{'count'} );
#ok( exists $status->payload->{'search_key'} );
#ok( exists $status->payload->{'result_set'} );
#ok( ref( $status->payload->{'result_set'} ) eq 'ARRAY' );
#is( $status->payload->{'result_set'}->[0]->{'nick'}, 'root' );

note( '=============================' );
note( '"employee/sec_id/:sec_id" resource' );
note( '=============================' );
$base = "employee/sec_id";
docu_check($test, "$base/:sec_id");

note( "give 'inactive' employee a sec_id" );
$status = req( $test, 200, 'root', 'PUT', "employee/nick/inactive",
    '{ "sec_id" : 1024 }' );
is( $status->level, "OK" );
is( $status->code, 'DOCHAZKA_CUD_OK' );

note( 'read it back' );
$status = req( $test, 200, 'root', 'GET', "employee/nick/inactive" ); 
is( $status->level, "OK" );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
is( $status->payload->{'sec_id'}, 1024 );
my $mustr = $status->payload;

note( 'GET employee/sec_id/1024' );
$status = req( $test, 200, 'root', 'GET', "employee/sec_id/1024" ); 
is( $status->level, "OK" );
is( $status->code, 'DISPATCH_EMPLOYEE_FOUND' );
is_deeply( $status->payload, $mustr );


note( '=============================' );
note( '"employee/sec_id/:sec_id/minimal" resource' );
note( '=============================' );
$base = 'employee/sec_id';
docu_check($test, "$base/:sec_id/minimal");

note( 'root attempt to get non-existent sec_id (minimal)' );
req( $test, 404, 'root', 'GET', "$base/53432/minimal" );

note( 'demo attempt to get non-existent sec_id (minimal)' );
req( $test, 403, 'demo', 'GET', "$base/53432/minimal" );

note( 'set root\'s sec_id to be foobar' );
my $eid_of_root = $site->DOCHAZKA_EID_OF_ROOT;
$status = req( $test, 200, 'root', 'POST', 'employee/eid', <<"EOS" );
{ "eid" : $eid_of_root, "sec_id" : "foobar" }
EOS
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );

note( 'demo attempt to get existent sec_id (minimal)' );
req( $test, 403, 'demo', 'GET', "$base/foobar/minimal" );

note( 'root get itself (minimal)' );
$status = req( $test, 200, 'root', 'GET', "$base/foobar/minimal" );
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_EMPLOYEE_MINIMAL' );
ok( $status->payload );
is( ref( $status->payload ), 'HASH' );
ok( $status->payload->{'nick'} );
is( $status->payload->{'nick'}, 'root' );
is( $status->payload->{'eid'}, $eid_of_root );
is( join( '', sort( keys( %{ $status->payload } ) ) ),
    join( '', sort( @{ $site->DOCHAZKA_EMPLOYEE_MINIMAL_FIELDS } ) ) );

note( 'set root\'s sec_id back to undef' );
$status = req( $test, 200, 'root', 'POST', 'employee/eid', <<"EOS" );
{ "eid" : $eid_of_root, "sec_id" : null }
EOS
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );



note( '=============================' );
note( '"employee/team" resource' );
note( '=============================' );
$base = "employee/team";
docu_check($test, "$base");

note( 'FIXME: NO TESTS!!!' );

note( 'tear down' );
$status = delete_all_attendance_data();
BAIL_OUT(0) unless $status->ok;

done_testing;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.