App-Dochazka-REST

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

- work on development-checklist
- t/: standardize method order (GET, PUT, POST, DELETE); adapt units

0.198  2014-10-21 12:10 CEST
- Dispatch.pm: document all target subroutines; put into alphabetical order
  by resource name/path

0.199  2014-10-21 16:31 CEST
- Hackweek Day 2
- dispatch_Message_en.conf: add DISPATCH_RESOURCE_NOT_IMPLEMENTED
- dispatch_Top_Config.pm: implement 'not_implemented' resource
- Dispatch.pm: implement 'not_implemented' resource, alphabetical order
  tweak
- t/: add missing top-level dispatch tests
- new ../development-checklist chart to clarify and streamline
  resource-implementation workflow

0.200  2014-10-21 22:29 CEST
- ../development-checklist: finish implementing top-level resources
- Dispatch.pm: when getting site and meta params, show file and line
  number along with other metadata; tweak resource documentation
- t/: some tests failing due to bug in App::CELL::Config->get_param_meta

Changes  view on Meta::CPAN

- Dispatch/Privhistory.pm: repurpose _current_priv so it works for the new
  resources added in this commit; rename _eid and _nick to _history_eid and
  _history_nick, respectively

0.214  2014-10-27 13:12 CET
- config/dispatch/top_Config.pm: add 'cli' property to all top-level
  resource definitions; put into alphabetical order; rename 'privhistory'
  to 'priv'
- Dispatch.pm: make 'docu' resource return "resource => ''" when called
  without an argument, instead of "resource => undef"; make
  "not_implemented" resource include HTTP method in payload

0.215  2014-10-27 13:39 CET
- config/dispatch/employee_Config.pm, config/dispatch/priv_Config.pm: add
  'cli' properties to all resource definitions
- ../development-checklist: complete manual CLI testing of all top-level
  and employee resources

0.216  2014-10-27 14:29 CET
- config/dispatch/priv_Config.pm: fix 'priv/eid/:eid/?:ts' and
  'priv/nick/:nick/?:ts' resource definitions

Changes  view on Meta::CPAN

- config/dispatch/priv_Config.pm: alphabetical order tweak
- Test.pm: export all the test functions (EXPORT instead of EXPORT_OK); bring
  in create_testing_employee and delete_testing_employee
- STATUS: all top-level, employee, and priv resources implemented and tested,
  ready to start work on activity, interval, etc. resources

0.220  2014-10-28 09:35 CET
- MANIFEST: add Dispatch/Activity.pm
- config/dispatch/activity_Config.pm: add 'activity/all' resource definition
- config/dispatch/top_Config.pm: add 'activity' resource definition; rename
  '_not_implemented' target to 'not_implemented' because we are now exporting
  it from Dispatch/Shared.pm
- config/dispatch_Config.pm: uncomment DISPATCH_RESOURCES_ACTIVITY
- Dispatch.pm: import not_implemented target from Dispatch/Shared.pm
- Dispatch/Employee.pm: fix debug messages
- Dispatch/Privhistory.pm: import priv_by_eid from the right module
- Dispatch/Shared.pm: export 'not_implemented' routine
- Resource.pm: add 'use App::Dochazka::REST::Dispatch::Activity' so activity
  targets will work

0.221  2014-10-28 12:08 CET
- MANIFEST: add t/dispatch/activity.t 
- config/sql/: add 'SQL_ACTIVITY_SELECT_ALL'; add 'disabled'
  field to 'activities' table; adapt SQL statements where appropriate
- t/dispatch/activity.t: new unit for activity dispatch tests

0.222  2014-10-28 21:51 CET

Changes  view on Meta::CPAN

- Model/Shared.pm: return decoded schedule (hashref) from schedule_by_eid
- t/dispatch/schedule.t: uncomment/adapt some tests
- t/: get model units to run cleanly again

0.242  2014-11-04 08:30 CET
- config/dispatch/schedule_Config.pm: put resource definitions in alphabetical order;
  add missing POD; add missing 'schedule/intervals' resource definition

0.243  2014-11-04 09:56 CET
- schedule_Config.pm, Dispatch/Schedule.pm: make 'schedule/history/...'
  resources use "not_implemented" dispatch target so they don't vomit 500

0.244  2014-11-04 11:32 CET
- schedule_Config.pm: split 'schedule/intervals/?:shid' into two separate
  resources for parsing clarity

0.245  2014-11-04 17:50 CET
- Build.PL: require App::Dochazka 0.175 for 'ssid' (instead of 'scratch_sid')
- schedule_Config.pm: 'schedule/intervals' is POST and DELETE only; activate
  DELETE on 'schedule/intervals/:sid'; rename ':shid' parameter to ':sid'
- rename 'scratch_sid' field/property of intervals to 'ssid'

Changes  view on Meta::CPAN

0.288  2014-11-19 10:05 CET
- Dispatch/Lock.pm: lay groundwork for locks
- t/dispatch/interval.t: new tests 
- interval_Config.pm, dispatch_Message_en.conf: minor fixes
- Dispatch/Interval.pm: add special ACL handling
- Model/Interval.pm: fix bug (wrong order of properties in call to 'cud')
- Test.pm: let 'req' handle any user as long as "passhash" property is
  set the same as "nick"

0.289  2014-11-19 11:08 CET
- interval_Config.pm: make not-implemented resources use 'not_implemented'
  dispatch target
- t/dispatch/interval.pm: add some missing boilerplate tests

0.290  2014-11-19 11:23 CET
- interval_Config.pm: 'interval/nick/:nick/:tsrange' resource was broken due to
  missing EOH - fixed
- t/dispatch/interval.t: $base for 'interval/nick/:nick/:tsrange' resource was
  set to wrong value - fixed

0.291  2014-11-19 15:19 CET

Changes  view on Meta::CPAN

- t/model/: add '+01' to some timestamp literals because they are now timestamptz

0.305  2014-11-24 18:11 CET
- dbinit_Config.pm: notice that we already have a 'valid_intvl' trigger; revamp
  'intervals' and 'locks' triggers to use it
- t/dispatch/interval_lock.t: add a group of tests to keep track of which
  pathological tsranges ('intervals' in Dochazka terms) we are successfully
  checking for

0.306  2014-11-26 09:31 CET
- dbinit_Config.pm: add 'intvl_not_locked' trigger before update or insert on
  'intervals' table - checks to make sure the interval would not overlap with
  any existing lock
- Model/Shared.pm: fix bug where 'load' routine was needlessly adding 
  'count => 1' to the payload, which is supposed to be just an object
- t/: some tests started to fail because we were creating intervals and locks
  with the same intervals - fixed by changing the lock interval
- t/dispatch/interval_lock.t: now that we have a functioning trigger, add tests
  that attempt to insert intervals that conflict with a lock
- t/model/triggers: add new subdirectory for trigger tests
- t/model/triggers/immutable_id.t: new unit to test triggers that make ID

Changes  view on Meta::CPAN

  insert and update; confidently add 'NOT NULL' constraint to 'disabled' fields
- t/sql/disabled_to_zero.t: adapt to current state
- t/model/schedule.t: add notes the way it should have been from the beginning

0.419  2015-04-03 18:06 CEST
- ResourceDefs.pm: change ACL of 'GET schedule/all' so inactives and actives
  can do it, too
- t/dispatch/schedule.t: add appropriate test cases

0.420  2015-04-04 21:18 CEST
- SQL: implement 'not_before_1892' function to enforce sane timestamp policy
  (no dates before 1892 in the database)
- t/: adapt tests; add test cases

0.421  2015-04-05 13:34 CEST
- REST/Shared.pm: in 'shared_get_privsched', return 404 if no priv/schedule
  assigned (instead of returning 200 OK with a null priv/schedule) 
- t/dispatch/schedule.t: adapt tests

0.422  2015-04-05 20:45 CEST
- dbinit_Config.pm: make schedule_at_timestamp return SID instead of the

Changes  view on Meta::CPAN

- Dispatch.pm: add handler_get_employee_ldap() for GET employee/nick/:nick/ldap
- Auth.pm: populate certain employee attributes from LDAP upon successful LDAP autocreate
- REST_Config.pm: default value for DOCHAZKA_LDAP_POPULATE_MATRIX
- Build.PL: require App::Dochazka::Common 0.191 for "set()" method
- Dispatch.pm: increase granularity of employee/.../minimal ACL check
- No longer auto-generate Makefile.PL

0.471  2015-07-23 16:55 CEST
- t/201-LDAP.t: add simple test cases for GET employee/nick/:nick/ldap
- ResourceDefs.pm: enable PUT on employee/nick/:nick/ldap
- LDAP.pm: fix populate_employee() so it returns not_ok if nick is not in LDAP
- Dispatch.pm: implement handler_get_employee_ldap and handler_put_employee_ldap
- Auth.pm: fix DOCHAZKA_LDAP_AUTOCREATE_AS code path

0.472  2015-07-24 17:06 CEST
- doc: config/REST_Config.pm: tell users to not add a nick property to
  DOCHAZKA_LDAP_POPULATE_MATRIX
- t/: test POST employee/nick without required nick property
- Dispatch.pm: return 400 if POST employee/nick without nick property
  (fixes github issue #2) 
- Dispatch.pm: handler_put_employee_ldap() return 404 if nick not found in LDAP

WISHLIST  view on Meta::CPAN



------------------------------------------------------------------------
Implement site policy table
https://github.com/smithfarm/dochazka-rest/issues/25

    The use case for a policy table is as follows: we have a site
    configuration mechanism, but the SQL code cannot access it.

    SQL-relevant configuration values, such as what constitutes "too far in
    the future" for the not_too_future() stored procedure, could be stored
    in a "policy" table and accessed via a Policy class.

    The idea is that each policy would define a scalar value used in a
    function or trigger. This would give site admins some degree of control
    over the functions/triggers.

    So, site admins would not be able to add or delete policies, but they
    could update them. Each policy object would contain a validation
    attribute defining the range of permissible values.

bin/dochazka-dbinit  view on Meta::CPAN

$log->info( $message );
if ( ! $site->MREST_APPNAME ) {
    die "Site parameter MREST_APPNAME is undefined - please investigate!";
}
$log->init(
    ident => $site->MREST_APPNAME,
    debug_mode => ( $site->MREST_DEBUG_MODE || 0 ),
);

$status = App::Dochazka::REST::reset_db();
if ( $status->not_ok ) {
    die "PostgreSQL server is unreachable";
}

print "Dochazka database initialized\n";

bin/dochazka-ldap-sync-all  view on Meta::CPAN

}
my $emp_list = $status->payload;

foreach ( @$emp_list ) {
    if ( ! ldap_exists( $_->nick ) ) {
        print "WARNING: " . $_->nick . " not found in LDAP database!\n" unless $quiet;
        next;
    }
    my $old = $_->clone();
    $status = $_->ldap_sync();
    if ( $status->not_ok ) {
        print "WARNING: could not sync " . $_->nick . " from LDAP\n" unless $quiet; 
        next;
    }
    if ( $old->compare( $_ ) ) {
        print $_->nick . " existing profile data match LDAP: no action needed\n" unless $quiet;
        next;
    }
    $status = $_->update( $faux_context );
    if ( $status->ok ) {
        print $_->nick . " profile updated from LDAP\n" unless $quiet;

config/sql/dbinit_Config.pm  view on Meta::CPAN

                RETURN 't'::boolean;
            ELSE
                RETURN 'f'::boolean;
            END IF;
        END;
      $$ LANGUAGE plpgsql/,

    q/COMMENT ON FUNCTION overlaps(tstzrange, tstzrange) IS
      'Tests two tstzranges whether they overlap'/,

    q/CREATE OR REPLACE FUNCTION not_before_1892(timestamptz) 
      RETURNS TIMESTAMPTZ AS $IMM$
      BEGIN
          IF $1 < '1892-01-01'::timestamptz THEN
              RAISE EXCEPTION 'No dates earlier than 1892-01-01 please'; 
          END IF;
          RETURN $1;
      END;
    $IMM$ LANGUAGE plpgsql/,

    q/COMMENT ON FUNCTION not_before_1892(timestamptz) IS 'We enforce dates 1892-01-01 or later'/,

    q#CREATE OR REPLACE FUNCTION valid_intvl() RETURNS trigger AS $$
        BEGIN
            IF ( NEW.intvl IS NULL ) OR
               ( isempty(NEW.intvl) ) OR
               ( lower(NEW.intvl) = '-infinity' ) OR
               ( lower(NEW.intvl) = 'infinity' ) OR
               ( upper(NEW.intvl) = '-infinity' ) OR
               ( upper(NEW.intvl) = 'infinity' ) OR
               ( NOT lower_inc(NEW.intvl) ) OR
               ( upper_inc(NEW.intvl) ) OR
               ( lower_inf(NEW.intvl) ) OR
               ( upper_inf(NEW.intvl) ) THEN
                RAISE EXCEPTION 'illegal attendance interval %s', NEW.intvl;
            END IF;
            PERFORM not_before_1892(upper(NEW.intvl));
            PERFORM not_before_1892(lower(NEW.intvl));
            IF ( upper(NEW.intvl) != round_time(upper(NEW.intvl)) ) OR
               ( lower(NEW.intvl) != round_time(lower(NEW.intvl)) ) THEN
                RAISE EXCEPTION 'upper and lower bounds of interval must be evenly divisible by 5 minutes';
            END IF;
            RETURN NEW;
        END;
    $$ LANGUAGE plpgsql IMMUTABLE
    #,

    q#COMMENT ON FUNCTION valid_intvl() IS $body$

config/sql/dbinit_Config.pm  view on Meta::CPAN


    q/CREATE OR REPLACE FUNCTION round_effective() RETURNS trigger AS $$
        BEGIN
            NEW.effective = round_time(NEW.effective);
            RETURN NEW;
        END;
    $$ LANGUAGE plpgsql IMMUTABLE/,

    q/CREATE OR REPLACE FUNCTION sane_timestamp() RETURNS trigger AS $$
        BEGIN
            PERFORM not_before_1892(NEW.effective);
            RETURN NEW;
        END;
    $$ LANGUAGE plpgsql IMMUTABLE/,

    q/CREATE TRIGGER round_effective BEFORE INSERT OR UPDATE ON schedhistory
        FOR EACH ROW EXECUTE PROCEDURE round_effective()/,

    q/CREATE TRIGGER round_effective BEFORE INSERT OR UPDATE ON privhistory
        FOR EACH ROW EXECUTE PROCEDURE round_effective()/,

config/sql/dbinit_Config.pm  view on Meta::CPAN

            WHERE eid=NEW.eid AND effective >= lower(NEW.intvl) AND effective <= upper(NEW.intvl);
            IF sh_count > 0 THEN
                RAISE EXCEPTION 'employee schedule for this interval cannot be determined';
            END IF;
            RETURN NEW;
        END;
    $$ LANGUAGE plpgsql IMMUTABLE/,

    q/-- trigger function for use in sanity checks on attendance and lock intervals
      -- vets an interval to ensure it does not extend too far into the future
    CREATE OR REPLACE FUNCTION not_too_future() RETURNS trigger AS $$
        DECLARE
            limit_ts timestamptz;
        BEGIN
            --
            -- does the interval extend too far into the future?
            --
            SELECT date_trunc('MONTH', (now() + interval '4 months'))::TIMESTAMPTZ INTO limit_ts;
            IF upper(NEW.intvl) >= limit_ts THEN 
                RAISE EXCEPTION 'interval extends too far into the future';
            END IF;

config/sql/dbinit_Config.pm  view on Meta::CPAN

    
    q/CREATE TRIGGER one_and_only_one_schedule BEFORE INSERT OR UPDATE ON intervals
      FOR EACH ROW EXECUTE PROCEDURE schedule_policy()/,

    q/CREATE TRIGGER enforce_priv_policy BEFORE INSERT OR UPDATE ON intervals
      FOR EACH ROW EXECUTE PROCEDURE priv_policy()/,

    q/CREATE TRIGGER a1_interval_valid_intvl BEFORE INSERT OR UPDATE ON intervals
      FOR EACH ROW EXECUTE PROCEDURE valid_intvl()/,

    q/CREATE TRIGGER a2_interval_not_too_future BEFORE INSERT OR UPDATE ON intervals
      FOR EACH ROW EXECUTE PROCEDURE not_too_future()/,

    q/CREATE TRIGGER a3_no_iid_update BEFORE UPDATE ON intervals
      FOR EACH ROW EXECUTE PROCEDURE iid_immutable()/,
    
    # the 'locks' table

    q/-- locks
      CREATE TABLE locks (
          lid     serial PRIMARY KEY,
          eid     integer REFERENCES Employees (EID),

config/sql/dbinit_Config.pm  view on Meta::CPAN

          IF OLD.lid <> NEW.lid THEN
              RAISE EXCEPTION 'locks.lid field is immutable'; 
          END IF;
          RETURN NEW;
      END;
    $IMM$ LANGUAGE plpgsql/,
    
    q/CREATE TRIGGER a1_lock_valid_intvl BEFORE INSERT OR UPDATE ON locks
      FOR EACH ROW EXECUTE PROCEDURE valid_intvl()/,

    q/CREATE TRIGGER a2_lock_not_too_future BEFORE INSERT OR UPDATE ON locks
      FOR EACH ROW EXECUTE PROCEDURE not_too_future()/,

    q/-- trigger the trigger
    CREATE TRIGGER a3_no_lid_update BEFORE UPDATE ON locks
      FOR EACH ROW EXECUTE PROCEDURE lid_immutable()/,

    q/-- lock lookup trigger for intervals table
      CREATE OR REPLACE FUNCTION no_lock_conflict() RETURNS trigger AS $IMM$
      DECLARE
          this_eid integer;
          this_intvl tstzrange;

config/sql/dbinit_Config.pm  view on Meta::CPAN


          IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
              RETURN NEW;
          ELSE
              RETURN OLD;
          END IF;

      END;
      $IMM$ LANGUAGE plpgsql/,
          
    q/CREATE TRIGGER intvl_not_locked BEFORE INSERT OR UPDATE OR DELETE ON intervals
      FOR EACH ROW EXECUTE PROCEDURE no_lock_conflict()/,

    # the 'tempintvls' table and associated plumbing

    q/CREATE SEQUENCE temp_intvl_seq/,

    q/COMMENT ON SEQUENCE temp_intvl_seq IS 'sequence guaranteeing that each set of temporary intervals will have a unique identifier'/,

    q/-- tempintvls
      -- for staging fillup intervals 
      CREATE TABLE IF NOT EXISTS tempintvls (
          int_id     serial PRIMARY KEY,
          tiid       integer NOT NULL,
          intvl      tstzrange NOT NULL
      )/,

    q/CREATE TRIGGER a2_interval_not_too_future BEFORE INSERT OR UPDATE ON tempintvls
      FOR EACH ROW EXECUTE PROCEDURE not_too_future()/,

    # create 'root' and 'demo' employees

    q/-- insert root employee into employees table and grant admin
      -- privilege to the resulting EID
      WITH cte AS (
        INSERT INTO employees (nick, fullname, email, passhash, salt, remark) 
        VALUES ('root', 'Root Immutable', 'root@site.org', '82100e9bd4757883b4627b3bafc9389663e7be7f76a1273508a7a617c9dcd917428a7c44c6089477c8e1d13e924343051563d2d426617b695f3a3bff74e7c003', '341755e03e1f163f829785d1d19eab9dee5135c0', 'dbinit') 
        RETURNING eid
      ) 

lib/App/Dochazka/REST.pm  view on Meta::CPAN

    my $conn = App::Dochazka::REST::ConnBank::get_arbitrary_dbix_conn(
        $dbname, $dbuser, $dbpass
    );
    print "done\n";

    print "Initializing audit schema...";
    $status = run_sql(
        $conn,
        @{ $site->DBINIT_AUDIT },
    );
    if ( $status->not_ok ) {
        print Dumper( $status ), "\n";
        return $status;
    }
    print "done\n";

    print "Initializing public schema...";
    $status = run_sql(
        $conn,
        @{ $site->DBINIT_CREATE },
    );
    if ( $status->not_ok ) {
        print Dumper( $status ), "\n";
        return $status;
    }
    print "done\n";

    # get EID of root employee that was just created, since
    # we will need it in the second round of SQL statements
    my $eids = get_eid_of( $conn, "root", "demo" );
    $site->set( 'DOCHAZKA_EID_OF_ROOT', $eids->{'root'} );
    $site->set( 'DOCHAZKA_EID_OF_DEMO', $eids->{'demo'} );

lib/App/Dochazka/REST/ACL.pm  view on Meta::CPAN

    } elsif ( my $nick = $pl{'nick'} ) {
        $log->debug( "acl_check_is_my_report: given nick $nick" );
        $status = $emp->load_by_nick( $conn, $nick );
    } elsif ( my $sec_id = $pl{'sec_id'} ) {
        $log->debug( "acl_check_is_my_report: given sec_id $sec_id" );
        $status = $emp->load_by_sec_id( $conn, $sec_id );
    } else {
        die "AAAGAAHHAHAHAAJJAJAJAHAHA! " . Dumper( \%pl );
    }

    if ( $status->not_ok ) {
        $log->error( "acl_check_is_my_report: employee lookup failed (" . $status->text . ")" );
        return 0;
    }

    $emp = $status->payload;
    
    if ( defined($emp->supervisor) and defined($ce->eid) and $emp->supervisor eq $ce->eid ) {
        $log->debug( "acl_check_is_my_report: I am the supervisor of ->" . $emp->nick . "<-" );
        return 1;
    }

lib/App/Dochazka/REST/Auth.pm  view on Meta::CPAN

        $log->info( "Detected authentication attempt from $nick, a known LDAP user" );
        #$log->debug( "Password provided: $password" );

        # - authenticate by LDAP bind
        if ( ldap_auth( $nick, $password ) ) {
            # successful LDAP auth: if the employee doesn't already exist in
            # the database, possibly autocreate
            $status = autocreate_employee( $dbix_conn, $nick );
            return $status unless $status->ok;
        } else {
            return $CELL->status_not_ok( 'DOCHAZKA_EMPLOYEE_AUTH' );
        }

        # load the employee object
        my $emp = App::Dochazka::REST::Model::Employee->load_by_nick( $dbix_conn, $nick )->payload;
        die "missing employee object in _authenticate" unless ref($emp) eq "App::Dochazka::REST::Model::Employee";
        return $CELL->status_ok( 'DOCHAZKA_EMPLOYEE_AUTH', payload => $emp );
    }

    # if not, authenticate against the password stored in the employee object.
    else {

        $log->notice( "Employee $nick not found in LDAP; reverting to internal auth" );

        # - check if this employee exists in database
        my $emp = nick_exists( $dbix_conn, $nick );

        if ( ! defined( $emp ) or ! $emp->isa( 'App::Dochazka::REST::Model::Employee' ) ) {
            $log->notice( "Rejecting login attempt from unknown user $nick" );
            $self->mrest_declare_status( explanation => "Authentication failed for user $nick", permanent => 1 );
            return $CELL->status_not_ok;
        }

        # - the password might be empty
        $password = '' unless defined( $password );
        my $passhash = $emp->passhash;
        $passhash = '' unless defined( $passhash );

        # - check password against passhash 
        my ( $ppr, $status );
        try {

lib/App/Dochazka/REST/Auth.pm  view on Meta::CPAN

                algorithm => "SHA-512",
                salt_hex => $emp->salt,
                hash_hex => $emp->passhash,
            );
        } catch {
            $status = $CELL->status_err( 'DOCHAZKA_PASSPHRASE_EXCEPTION', args => [ $_ ] );
        };

        if ( ref( $ppr ) ne 'Authen::Passphrase::SaltedDigest' ) {
            $log->crit( "employee $nick has invalid passhash and/or salt" );
            return $CELL->status_not_ok( 'DOCHAZKA_EMPLOYEE_AUTH' );
        }
        if ( $ppr->match( $password ) ) {
            $log->notice( "Internal auth successful for employee $nick" );
            return $CELL->status_ok( 'DOCHAZKA_EMPLOYEE_AUTH', payload => $emp );
        } else {
            $self->mrest_declare_status( explanation => 
                "Internal auth failed for known employee $nick (mistyped password?)" 
            );
            return $CELL->status_not_ok;
        }
    }
}            


=head2 forbidden

This overrides the L<Web::Machine> method of the same name.

Authorization (ACL check) method.

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN




=head1 PACKAGE VARIABLES

This module uses some package variables, which are essentially constants, to do
its work.

=cut

my $fail = $CELL->status_not_ok;
my %iue_dispatch = (
    'insert_employee' => \&shared_insert_employee,
    'update_employee' => \&shared_update_employee,
);



=head1 FUNCTIONS

=cut

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

sub handler_holiday_tsrange {
    my ( $self, $pass ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::handler_holiday_tsrange, pass number $pass" );
    
    # first pass
    if ( $pass == 1 ) {
        my $status = split_tsrange( 
            $self->context->{'dbix_conn'},
            $self->context->{'mapping'}->{'tsrange'},
        );
        if ( $status->not_ok ) {
            $status->{'http_code'} = ( $status->code eq 'DOCHAZKA_DBI_ERR' )
                ? 500 
                : 400;
            $self->mrest_declare_status( $status );
            return 0;
        }
        my $datereg = qr/(\d+-\d+-\d+)/;
        my ( $begin ) = $status->payload->[0] =~ $datereg;
        my ( $end ) = $status->payload->[1] =~ $datereg;
        if ( ! defined( $begin ) or ! defined( $end ) ) {

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

=head3 _ldap_sync_pass1

=cut

sub _ldap_sync_pass1 {
    my ( $self, $emp ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::_ldap_sync_pass1" ); 

    my $status = $emp->ldap_sync();
    $log->debug( "ldap_sync status: " . Dumper( $status ) );
    if ( $status->not_ok ) {
        if ( $status->code eq 'DOCHAZKA_LDAP_SYSTEM_USER_NOSYNC' ) {
            # system user - 403
            $status->{'http_code'} = 403;
        } else {
            $status->{'http_code'} = 404;
        }
        $self->mrest_declare_status( $status );
        return 0;
    }
    $self->context->{'stashed_employee_object'} = $emp;

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

        keys => [ $key ],
    );
    # check for 404
    if ( $status->level eq 'NOTICE' and $status->code eq 'DISPATCH_NO_RECORDS_FOUND' ) {
        $self->mrest_declare_status( code => 404,
            explanation => "DISPATCH_SEARCH_EMPTY",
            args => [ 'employee', "nick LIKE $key" ],
        );
        return $fail;
    }
    return $status if $status->not_ok;

    # found some employee objects
    foreach my $emp ( @{ $status->payload } ) {
        $emp = $emp->TO_JSON;
    }
    return $status;
}


=head2 Genreport handlers

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

        die "BGUDFUUFF! Improper prop ->$prop<- seen!";
    }
    # - process return value
    if ( $status->level eq 'NOTICE' and $status->code eq 'DISPATCH_NO_RECORDS_FOUND' ) {
        my $tsmsg = ( $ts ) ? $ts : 'now';
        $self->mrest_declare_status(
            code => 404,
            explanation => "No $prop history for $key $value as of $tsmsg",
        );
        return $fail;
    } elsif ( $status->not_ok ) {
        $self->mrest_declare_status(
            code => 500,
            explanation => $status->text,
        );
        return $fail;
    }
    return $status;
}


lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

        $prop,
        $context->{'dbix_conn'},
        eid => $emp->eid,
        nick => $emp->nick, 
        tsrange => $tsrange, 
    );
    # - process return value
    if ( $status->level eq 'NOTICE' and $status->code eq 'DISPATCH_NO_RECORDS_FOUND' ) {
        $self->mrest_declare_status( code => 404, explanation => "No history for $key $value $tsrange" );
        return $fail;
    } elsif ( $status->not_ok ) {
        $self->mrest_declare_status( code => 500, explanation => $status->text );
        return $fail;
    }
    return $status;
}


=head3 handler_history_post

Handler method for POST requests on the '/{priv,schedule}/history/eid/..' and

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

            eid => $emp->eid, 
            effective => $entity->{'effective'},
            $prop => $entity->{$prop},
            remark => $entity->{'remark'},
        );
    } catch {
        $log->crit($_);
        return $CELL->status_crit("DISPATCH_HISTORY_COULD_NOT_SPAWN", args => [ $_ ] );
    };
    $status = $ho->insert( $context );
    if ( $status->not_ok ) {
        $self->context->{'create_path'} = $status->level;
        if ( $status->code eq 'DOCHAZKA_MALFORMED_400' ) {
            return $self->mrest_declare_status(
                code => 400,
                explanation => "Check syntax of your request entity"
            );
        }
        return $self->mrest_declare_status(
            code => 500,
            explanation => $status->code,

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

    my $context = $self->context;

    # first pass
    if ( $pass == 1 ) {
        $context->{'post_is_create'} = 1;
        return 1;
    }
        
    # second pass
    my $status = shared_entity_check( $self, 'aid', 'intvl' );
    return $fail if $status->not_ok;

    if ( check_acl_context( $context )->not_ok ) {
        $self->mrest_declare_status( code => 403, explanation => 'DISPATCH_KEEP_TO_YOURSELF' );
        return $fail;
    }

    return shared_insert_interval( $self );
}


=head3 handler_post_interval_iid

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

    my $context = $self->context;

    # first pass
    if ( $pass == 1 ) {
        $context->{'post_is_create'} = 1;
        return 1;
    }
        
    # second pass
    my $status = shared_entity_check( $self, 'intvl' );
    return $fail if $status->not_ok;

    if ( check_acl_context( $context )->not_ok ) {
        $self->mrest_declare_status( code => 403, explanation => 'DISPATCH_KEEP_TO_YOURSELF' );
        return $fail;
    }

    return shared_insert_lock( $self );
}


=head3 handler_post_lock_lid

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

    if ( ! $fillup->constructor_status or
         ! $fillup->constructor_status->isa( 'App::CELL::Status' ) )
    {
        $self->mrest_declare_status( 
            code => 500, 
            explanation => "No constructor_status in Fillup object" 
        );
        return $fail;
    }
    $log->debug( "Fillup object created; constructor status is " . Dumper( $fillup->constructor_status ) );
    if ( $fillup->constructor_status->not_ok ) {
        my $status = $fillup->constructor_status;
        $status->{'http_code'} = ( $status->code eq 'DOCHAZKA_DBI_ERR' )
            ? 500 
            : 400;
        $self->mrest_declare_status( $status );
        return $fail;
    }
    
    my $status = $fillup->commit;
    if ( $status->not_ok ) {
        $self->mrest_declare_status( code => 500, explanation => $status->text );
        return $fail;
    }
    return $status;
}

# helper function to extract employee spec from request entity
# takes request entity hash and returns either undef on failure
# or Employee object on success
sub _extract_employee_spec {

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

    # first pass
    if ( $pass == 1 ) {
        $context->{'post_is_create'} = 1;
        return 1;
    }

    # second pass
    my ( $status, $code );

    $status = shared_entity_check( $self, 'schedule' );
    return $fail if $status->not_ok;
    if ( ref( $context->{'request_entity'}->{'schedule'} ) ne "ARRAY" ) {
        $self->mrest_declare_status( code => 400, explanation => 'Check schedule syntax' );
        return $fail;
    }

    # first, spawn a Schedintvls object
    my $intvls = App::Dochazka::REST::Model::Schedintvls->spawn;
    $log->debug( "Spawned Schedintvls object " . Dumper( $intvls ) );

    # note that a SSID has been assigned
    my $ssid = $intvls->ssid;
    $log->debug("Spawned Schedintvls object with SSID $ssid");

    # assume that these are the intervals
    $intvls->{'intvls'} = $context->{'request_entity'}->{'schedule'};
    #
    # insert the intervals
    $status = $intvls->insert( $context->{'dbix_conn'} ); # schedintvls is not audited
    if ( $status->not_ok ) {
        $self->mrest_declare_status( code => 500, explanation => $status->text );
        return $fail;
    }
    $log->info( "schedule/new: Scratch intervals inserted" );

    #
    # convert the intervals to get the 'schedule' property
    $status = $intvls->load( $context->{'dbix_conn'} );
    if ( $status->not_ok ) {
        $intvls->delete( $context->{'dbix_conn'} );
        $self->mrest_declare_status( code => 400, explanation => $status->text );
        return $fail;
    }
    $log->info( "schedule/new: Scratch intervals converted" );

    #
    # spawn Schedule object
    my @ARGS = ( 'schedule' => $intvls->json );
    if ( my $scode = $context->{'request_entity'}->{'scode'} ) {

lib/App/Dochazka/REST/Dispatch.pm  view on Meta::CPAN

        }
    } else {
        $self->mrest_declare_status( code => 500, explanation => 
            "schedule/new: Model/Schedule.pm->insert failed: " . $status->text );
        $intvls->delete( $context->{'dbix_conn'} );
        return $fail;
    }
    #
    # delete the schedintvls object
    $status = $intvls->delete( $context->{'dbix_conn'} ); # schedintvls is not audited
    if ( $status->not_ok ) {
        $self->mrest_declare_status( code => 500, explanation => "Could not delete schedintvls: " . $status->text );
        return $fail;
    }
    $log->info( "schedule/new: scratch intervals deleted" );
    #
    # success
    return $CELL->status_ok( $code, payload => $sched->TO_JSON );
}


lib/App/Dochazka/REST/Fillup.pm  view on Meta::CPAN


Performs various tests on the C<context> attribute. If the value of that
attribute is not what we're expecting, returns a non-OK status. Otherwise,
returns an OK status.

=cut

sub _vet_context {
    my $self = shift;
    my %ARGS = @_;
    return $CELL->status_not_ok unless $ARGS{context};
    return $CELL->status_not_ok unless $ARGS{context}->{dbix_conn};
    return $CELL->status_not_ok unless $ARGS{context}->{dbix_conn}->isa('DBIx::Connector');
    $self->context( $ARGS{context} );
    $self->{'vetted'}->{'context'} = 1;
    return $CELL->status_ok;
}


=head2 _vet_date_spec

The user can specify fillup dates either as a tsrange or as a list of
individual dates.

lib/App/Dochazka/REST/Fillup.pm  view on Meta::CPAN


=cut

sub _vet_date_spec {
    my $self = shift;
    my %ARGS = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::_vet_date_spec to enforce date specification policy" );

    if ( defined( $ARGS{date_list} ) and defined( $ARGS{tsrange} ) ) {
        $log->debug( "date_spec is NOT OK" );
        return $CELL->status_not_ok;
    }
    if ( ! defined( $ARGS{date_list} ) and ! defined( $ARGS{tsrange} ) ) {
        $log->debug( "date_spec is NOT OK" );
        return $CELL->status_not_ok;
    }
    $self->{'vetted'}->{'date_spec'} = 1;
    $log->debug( "date_spec is OK" );
    return $CELL->status_ok;
}


=head2 _vet_date_list

This function takes one named argument: date_list, the value of which must

lib/App/Dochazka/REST/Model/Employee.pm  view on Meta::CPAN

        $count += 1;
    }

    $ldap->unbind;

    return $CELL->status_ok( 
        'DOCHAZKA_LDAP_SYNC_SUCCESS',
        args => [ $count ],
    ) unless $count < 1;

    return $CELL->status_not_ok( 'DOCHAZKA_LDAP_SYNC_FAILURE' );
}


=head2 load_by_eid

Analogous method to L<App::Dochazka::REST::Model::Activity/"load_by_aid">.

=cut

sub load_by_eid {

lib/App/Dochazka/REST/Model/Employee.pm  view on Meta::CPAN

create the employee. Returns a status object.

=cut

sub autocreate_employee {
    my ( $dbix_conn, $nick ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::autocreate_employee()" );
    my $status;

    return $CELL->status_ok() if nick_exists( $dbix_conn, $nick );
    return $CELL->status_not_ok( 'DOCHAZKA_NO_AUTOCREATE' ) unless $site->DOCHAZKA_LDAP_AUTOCREATE;

    my $emp = App::Dochazka::REST::Model::Employee->spawn(
        nick => $nick,
        sync => 1,
        remark => 'LDAP autocreate',
    );
    $status = $emp->ldap_sync();
    return $status unless $status->ok;

    my $faux_context = { 'dbix_conn' => $dbix_conn, 'current' => { 'eid' => 1 } };
    $status = $emp->insert( $faux_context );
    if ( $status->not_ok ) {
        my $reason = $status->text;
        return $CELL->status_err(
            'DOCHAZKA_EMPLOYEE_CREATE_FAIL',
            args => [ $nick, $reason ],
        );
    }
    $log->notice( "Auto-created employee $nick, who was authenticated via LDAP" );

    my $priv = $site->DOCHAZKA_LDAP_AUTOCREATE_AS;
    if ( $priv !~ m/^(inactive)|(active)$/ ) {

lib/App/Dochazka/REST/Model/Employee.pm  view on Meta::CPAN


    # create a privhistory record (inactive/active only)
    init_timepiece();
    my $ph_obj = App::Dochazka::REST::Model::Privhistory->spawn(
        eid => $emp->eid,
        priv => $priv,
        effective => ( $today . ' 00:00' ),
        remark => 'LDAP autocreate',
    );
    $status = $ph_obj->insert( $faux_context );
    if ( $status->not_ok ) {
        my $reason = $status->text;
        $status = $CELL->status_err(
            'DOCHAZKA_AUTOCREATE_PRIV_PROBLEM',
            args => [ $nick, $reason ],
        );
    }

    return $status;
}

lib/App/Dochazka/REST/Shared.pm  view on Meta::CPAN

use App::Dochazka::REST::Model::Lock;
use App::Dochazka::REST::Model::Privhistory;
use App::Dochazka::REST::Model::Schedhistory;
use App::Dochazka::REST::Model::Schedule;
use App::Dochazka::REST::Model::Shared qw( priv_by_eid schedule_by_eid );
use App::Dochazka::REST::Util qw( hash_the_password pre_update_comparison );
use Data::Dumper;
use Params::Validate qw( :all );
use Try::Tiny;

my $fail = $CELL->status_not_ok;


=head1 NAME

App::Dochazka::REST::Dispatch::Shared - Shared dispatch functions




=head1 DESCRIPTION

lib/App/Dochazka/REST/Shared.pm  view on Meta::CPAN

        die "shared_first_pass_lookup could not do anything with key $key!";
    }

    if ( $status->level eq 'NOTICE' and $status->code eq 'DISPATCH_NO_RECORDS_FOUND' ) {
        $d_obj->mrest_declare_status( code => 404,
            explanation => 'DISPATCH_SEARCH_EMPTY',
            args => [ $thing, "$key equals $value" ],
        );
        return;
    }
    if ( $status->not_ok ) {
        $d_obj->mrest_declare_status( code => 500, explanation => $status->code,
            args => $status->args 
        );
        return;
    }
    return $status->payload;
}


=head2 shared_entity_check

lib/App/Dochazka/REST/Shared.pm  view on Meta::CPAN

            $return_value = $status->payload;
        }

        my @privsched = ( $t, $return_value );
        if ( $ts ) {
            if ( ! $return_value ) {
                $d_obj->mrest_declare_status( 
                    code => 404, 
                    explanation => "Employee $nick (EID $eid) has no $t assigned as of $ts" 
                );
                return $CELL->status_not_ok;
            }
            my $code;
            if ( 'PRIV' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_PRIV_AS_AT';
            } elsif ( 'SCHEDULE' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_SCHEDULE_AS_AT';
            } else {
                die "AGHNEVERNEVERNEVERPRIVSCHED1";
            }
            return $CELL->status_ok( $code,

lib/App/Dochazka/REST/Shared.pm  view on Meta::CPAN

                    timestamp => $ts,
                    @privsched,
                },
            );
        } else {
            if ( ! $return_value ) {
                $d_obj->mrest_declare_status( 
                    code => 404, 
                    explanation => "Employee $nick (EID $eid) has no $t assigned"
                );
                return $CELL->status_not_ok;
            }
            my $code;
            if ( 'PRIV' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_PRIV';
            } elsif ( 'SCHEDULE' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_SCHEDULE';
            } else {
                die "AGHNEVERNEVERNEVERPRIVSCHED2";
            }
            return $CELL->status_ok( $code,

lib/App/Dochazka/REST/Test.pm  view on Meta::CPAN

    note( "Set log level" );
    $log->init( 
        ident => $site->MREST_APPNAME, 
        debug_mode => 1,
    );

    note( "Initialize" );
    try {
        App::Dochazka::REST::Dispatch::init();
    } catch {
        $status = $CELL->status_not_ok;
    };
    plan skip_all => 'Integration testing environment not detected' unless $status->ok;

    note( "Check status of database server connection" );
    plan skip_all => "PostgreSQL server is unreachable" unless conn_up();

    my $eids = App::Dochazka::REST::get_eid_of( $dbix_conn, "root", "demo" );
    $site->set( 'DOCHAZKA_EID_OF_ROOT', $eids->{'root'} );
    $site->set( 'DOCHAZKA_EID_OF_DEMO', $eids->{'demo'} );

lib/App/Dochazka/REST/Test.pm  view on Meta::CPAN

    my ( $PROPS ) = validate_pos( @_,
        { type => HASHREF },
    );

    hash_the_password( $PROPS );

    my $emp = App::Dochazka::REST::Model::Employee->spawn( $PROPS );
    is( ref($emp), 'App::Dochazka::REST::Model::Employee', 'create_bare_employee 1' );

    my $status = $emp->insert( $faux_context );
    if ( $status->not_ok ) {
        diag( "Employee insert method returned NOT_OK status in create_bare_employee" );
        diag( "test automation function, which was called from " . (caller)[1] . " line " . (caller)[2] );
        diag( "with arguments: " . Dumper( $PROPS ) );
        diag( "Full status returned by employee insert method:" );
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->level, "OK", 'create_bare_employee 2' );
    my $employee_object = $status->payload;
    is( ref( $employee_object ), 'App::Dochazka::REST::Model::Employee' );

lib/App/Dochazka/REST/Test.pm  view on Meta::CPAN

Takes a single argument: the EID.

Loads the EID into a new Employee object and calls that object's delete method.

=cut

sub delete_bare_employee {
    my $eid = shift;  
    note( "delete testing employee with EID $eid" );
    my $status = App::Dochazka::REST::Model::Employee->load_by_eid( $dbix_conn, $eid );
    if ( $status->not_ok ) {
        diag( "Employee load_by_eid method returned NOT_OK status in delete_bare_employee" );
        diag( "test automation function, which was called from " . (caller)[1] . " line " . (caller)[2] );
        diag( "with EID $eid" );
        diag( "Full status returned by Employee load_by_eid method:" );
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->level, 'OK', 'delete_bare_employee 1' );
    my $emp = $status->payload;
    $status = $emp->delete( $faux_context );
    if ( $status->not_ok ) {
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->level, 'OK', 'delete_bare_employee 2' );
    return;
}


sub _create_employee {
    my ( $test, $privspec ) = @_;

lib/App/Dochazka/REST/Test.pm  view on Meta::CPAN

Tests will need to set up and tear down testing activities

=cut

sub create_testing_activity {
    my %PROPS = @_;  # must be at least code

    my $act = App::Dochazka::REST::Model::Activity->spawn( \%PROPS );
    is( ref($act), 'App::Dochazka::REST::Model::Activity', 'create_testing_activity 1' );
    my $status = $act->insert( $faux_context );
    if ( $status->not_ok ) {
        BAIL_OUT( $status->code . " " . $status->text );
    }
    is( $status->level, "OK", 'create_testing_activity 2' );
    return $status->payload;
}


=head2 delete_testing_activity

Tests will need to set up and tear down testing activities

lib/App/Dochazka/REST/Test.pm  view on Meta::CPAN

Tests will need to set up and tear down testing intervals

=cut

sub create_testing_interval {
    my %PROPS = @_;  # must be at least code

    my $act = App::Dochazka::REST::Model::Interval->spawn( \%PROPS );
    is( ref($act), 'App::Dochazka::REST::Model::Interval', 'create_testing_interval 1' );
    my $status = $act->insert( $faux_context );
    if ( $status->not_ok ) {
        BAIL_OUT( $status->code . " " . $status->text );
    }
    is( $status->level, "OK", 'create_testing_interval 2' );
    return $status->payload;
}


=head2 delete_testing_interval

Tests will need to set up and tear down testing intervals

lib/App/Dochazka/REST/Test.pm  view on Meta::CPAN

Tests will need to set up and tear down testing components

=cut

sub create_testing_component {
    my %PROPS = @_;  # must be at least path

    my $comp = App::Dochazka::REST::Model::Component->spawn( \%PROPS );
    is( ref($comp), 'App::Dochazka::REST::Model::Component', 'create_testing_component 1' );
    my $status = $comp->insert( $faux_context );
    if ( $status->not_ok ) {
        BAIL_OUT( $status->code . " " . $status->text );
    }
    is( $status->level, "OK", 'create_testing_component 2' );
    return $status->payload;
}


=head2 delete_testing_component

Tests will need to set up and tear down testing components

lib/App/Dochazka/REST/Test.pm  view on Meta::CPAN


=cut

sub delete_testing_schedule {
    my ( $sid ) = @_;

    note( "delete testing schedule (SID $sid)" );

    my $status = App::Dochazka::REST::Model::Schedule->load_by_sid( $dbix_conn, $sid );
    is( $status->level, 'OK', 'delete_testing_schedule: load OK' );
    if ( $status->not_ok ) {
        diag( Dumper $status );
        BAIL_OUT(0);
    }

    my $sched = $status->payload;
    $status = $sched->delete( $faux_context );
    is( $status->level, 'OK', 'delete_testing_schedule: delete OK' );
    if ( $status->not_ok ) {
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    return;
}


=head2 delete_all_attendance_data

Wipe out all attendance data by deleting all rows from all tables (in the correct

t/001-noauto.t  view on Meta::CPAN

use strict;
use warnings;

#use App::CELL::Test::LogToFile;
use App::CELL qw( $site );
use Test::More;
use Web::MREST;

note( 'initialize the REST server' );
my $status = Web::MREST::init( distro => 'App-Dochazka-REST' );
if ( $status->not_ok ) { 
    diag( $status->text );
    plan skip_all => "Not configured. Please run the test suite manually after initial site configuration";
}

note( 'DOCHAZKA_TIMEZONE must be set' );
BAIL_OUT(-1) unless $site->DOCHAZKA_TIMEZONE;

ok( 1 );

done_testing;

t/dispatch/activity.t  view on Meta::CPAN

note( "root: fail invalid code" );
req( $test, 400, 'root', 'DELETE', "$base/!!!" );

note( "delete FOOBARPUS, too" );
$status = req( $test, 200, 'root', 'DELETE', "$base/foobarpus" );
is( $status->level, 'OK', "DELETE $base/foobarpus 2" );
is( $status->code, 'DOCHAZKA_CUD_OK', "DELETE $base/foobarpus 3" );

note( "teardown" );
$status = delete_all_attendance_data();
if ( $status->not_ok ) {
    diag( Dumper $status );
    BAIL_OUT(0);
}

done_testing;

t/dispatch/employee.t  view on Meta::CPAN

    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' );

t/dispatch/history.t  view on Meta::CPAN

    note( 'we will be inserting a bunch of records so push them onto an array' );
    note( 'for easy deletion later' );
    my @history_recs_to_delete;
    # - be nice
    $j = ( $base =~ m/^priv/ )
        ? '{ "effective":"1969-04-28 19:15", "priv":"inactive" }'
        : '{ "effective":"1969-04-28 19:15", "sid":' . $ts_sid . ' }';

    req( $test, 403, 'demo', 'POST', "$base/2", $j );
    $status = req( $test, 201, 'root', 'POST', "$base/2", $j );
    if ( $status->not_ok ) {
        diag( $status->code . ' ' . $status->text );
    }
    is( $status->level, 'OK' );
    my $pho = $status->payload;
    my $prop = ( $base =~ m/^priv/ ) ? 'phid' : 'shid';
    ok( exists $pho->{$prop}, "$prop exists in payload after POST $base/2" );
    ok( defined $pho->{$prop}, "$prop defined in payload after POST $base/2" );
    push @history_recs_to_delete, { eid => $pho->{eid}, $prop => $pho->{$prop} };

    note( 'be pathological' );

t/dispatch/history.t  view on Meta::CPAN

    note( 'PUT' );
    req( $test, 405, 'demo', 'PUT', "$base/asdf" );
    req( $test, 405, 'root', 'PUT', "$base/asdf" );
    
    note( "POST" );
    $j = ( $base =~ m/^priv/ ) 
        ? '{ "effective":"1969-04-27 9:45", "priv":"inactive" }'
        : '{ "effective":"1969-04-27 9:45", "sid":' . $ts_sid . ' }';
    req( $test, 403, 'demo', 'POST', "$base/demo", $j );
    $status = req( $test, 201, 'root', 'POST', "$base/demo", $j );
    if ( $status->not_ok ) {
        diag( $status->code . ' ' . $status->text );
    }
    is( $status->level, 'OK' );
    my $pho = $status->payload;
    my $prop = ( $base =~ m/^priv/ ) ? 'phid' : 'shid';
    push my @history_recs_to_delete, { nick => 'demo', $prop => $pho->{$prop} };
    
    note( 'DELETE' );
    req( $test, 405, 'demo', 'DELETE', "$base/madagascar" );
    req( $test, 405, 'active', 'DELETE', "$base/madagascar" );

t/dispatch/interval_lock.t  view on Meta::CPAN

    #    req( $test, 403, $user, 'DELETE', "$base/2/[,)" );
    #}
}

note( 'create an interval as active employee' );
my $aid_of_work = get_aid_by_code( $test, 'WORK' );
my $iae_interval_long_desc = 'iae interval';
$status = req( $test, 201, 'active', 'POST', 'interval/new', <<"EOH" );
{ "aid" : $aid_of_work, "intvl" : "[1958-01-02 08:00, 1958-01-03 08:00)", "long_desc" : "$iae_interval_long_desc" }
EOH
if ( $status->not_ok ) {
    diag( "MARK iae active" );
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
ok( $status->{'payload'} );
ok( $status->{'payload'}->{'iid'} );
my $iae_iid = $status->payload->{'iid'}; # store for later deletion

t/dispatch/interval_lock.t  view on Meta::CPAN

    # 
    note( 'test if expected behavior behaves as expected (update)' );
    my $int_obj = <<"EOH";
{ "$idmap{$il}" : $test_id, "remark" : "Sharpening pencils" }
EOH
    req( $test, 403, 'demo', 'POST', $base, $int_obj );
    req( $test, 403, 'inactive', 'POST', $base, $int_obj );

    if ( $il eq 'interval' ) {
         $status = req( $test, 200, 'active', 'POST', $base, $int_obj );
         if ( $status->not_ok ) {
             diag( "MARK foo1" );
             diag( Dumper $status );
             BAIL_OUT(0);
         }
         is( $status->level, 'OK', "POST $base 3" );
         is( $status->code, 'DOCHAZKA_CUD_OK', "POST $base 4" );
         is( $status->payload->{'iid'}, $test_iid, "POST $base 5" );
         is( $status->payload->{'remark'}, 'Sharpening pencils', "POST $base 7" );
    } else {
         req( $test, 403, 'active', 'POST', $base, $int_obj );

t/dispatch/interval_lock.t  view on Meta::CPAN

    req( $test, 403, $user, 'POST', $base, <<"EOH" );
{ "aid" : $aid_of_work, "intvl" : "[1957-01-02 08:00, 1957-01-03 08:00)" }
EOH
}

note( '- let active and root create themselves an interval and promptly delete it' );
foreach my $user ( qw( active root ) ) {
    $status = req( $test, 201, $user, 'POST', $base, <<"EOH" );
{ "aid" : $aid_of_work, "intvl" : "[1957-01-02 08:00, 1957-01-03 08:00)" }
EOH
    if ( $status->not_ok ) {
        diag( "MARK foo3 $user" );
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    ok( $status->{'payload'} );
    ok( $status->{'payload'}->{'iid'} );
    my $iid = $status->payload->{'iid'};

t/dispatch/interval_lock.t  view on Meta::CPAN

}

note( '- as long as all required properties are present, JSON with bogus properties' );
note( '  will be accepted for insert operation (bogus properties will be silently ignored)' );
foreach my $rb ( 
    "{ \"aid\" : $aid_of_work, \"intvl\" : \"[1957-01-02 08:00, 1957-01-02 08:05)\", \"whinger\" : \"me\" }",
    "{ \"aid\" : $aid_of_work, \"intvl\" : \"[1957-01-03 08:00, 1957-01-03 08:05)\", \"horse\" : \"E-Or\" }",
    "{ \"aid\" : $aid_of_work, \"intvl\" : \"[1957-01-04 08:00, 1957-01-04 08:05)\", \"nine dogs\" : [ 1, 9 ] }",
) {
    $status = req( $test, 201, 'root', 'POST', $base, $rb );
    if ( $status->not_ok ) {
        diag( "MARK foo4: $rb");
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    ok( $status->{'payload'} );
    ok( $status->{'payload'}->{'iid'} );
    my $iid = $status->payload->{'iid'};

t/dispatch/interval_lock.t  view on Meta::CPAN

{ "intvl" : "[1957-01-02 00:00, 1957-01-03 24:00)" }
EOH
}

note( 'let active and root create themselves a lock' );
foreach my $user ( qw( active root ) ) {
    note( 'user == "active"' );
    $status = req( $test, 201, $user, 'POST', $base, <<"EOH" );
{ "intvl" : "[1957-01-02 00:00, 1957-01-03 24:00)" }
EOH
    if ( $status->not_ok ) {
        diag( "MARK foo5" );
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    ok( $status->{'payload'} );
    ok( $status->{'payload'}->{'lid'} );
    my $lid = $status->payload->{'lid'};
    note( "$user successfully created a lock" );

t/dispatch/interval_lock.t  view on Meta::CPAN

}

note( 'as long as all required properties are present, JSON with bogus properties' );
note( 'will be accepted for insert operation (bogus properties will be silently ignored)' );
foreach my $rb ( 
    "{ \"intvl\" : \"[1957-01-02 00:00, 1957-01-02 24:00)\", \"whinger\" : \"me\" }",
    "{ \"intvl\" : \"[1957-01-03 00:00, 1957-01-03 24:00)\", \"horse\" : \"E-Or\" }",
    "{ \"intvl\" : \"[1957-01-04 00:00, 1957-01-04 24:00)\", \"nine dogs\" : [ 1, 9 ] }"
) {
    $status = req( $test, 201, 'root', 'POST', $base, $rb );
    if ( $status->not_ok ) {
        diag( "MARK foo6" );
        diag( Dumper $status );
        BAIL_OUT(0);
    }
    is( $status->level, 'OK' );
    is( $status->code, 'DOCHAZKA_CUD_OK' );
    ok( $status->{'payload'} );
    ok( $status->{'payload'}->{'lid'} );
    my $lid = $status->payload->{'lid'};

t/dispatch/interval_lock.t  view on Meta::CPAN

#note( 'DELETE' );
#foreach my $user ( 'demo', 'root', 'WAMBLE owdkmdf 5**' ) {
#    req( $test, 403, $user, 'DELETE', "$base/demo/[,)" );
#}

note( 'create an interval as active employee' );
my $ian_interval_long_desc = 'ian interval';
$status = req( $test, 201, 'active', 'POST', 'interval/new', <<"EOH" );
{ "aid" : $aid_of_work, "intvl" : "[1958-01-02 08:00, 1958-01-03 08:00)", "long_desc" : "$ian_interval_long_desc" }
EOH
if ( $status->not_ok ) {
    diag( "MARK ian active" );
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK' );
is( $status->code, 'DOCHAZKA_CUD_OK' );
ok( $status->{'payload'} );
ok( $status->{'payload'}->{'iid'} );
my $ian_iid = $status->payload->{'iid'}; # store for later deletion

t/fillup.t  view on Meta::CPAN

    qr/was not a.*it is a/
);
like( 
    exception { $fo->emp_obj( $CELL->status_ok ) }, 
    qr/was not a.*it is a/
);

note( $note = "vet empty context" );
$log->info( "=== $note" );
$status = $fo->_vet_context();
ok( $status->not_ok );

note( $note = "populate context attribute" );
$log->info( "=== $note" );
$status = $fo->_vet_context( context => $faux_context );
ok( $status->ok );

note( $note = "context should now be OK" );
$log->info( "=== $note" );
ok( $fo->context );
is( ref( $fo->context ), 'HASH' );

t/fillup.t  view on Meta::CPAN

);
ok( $status->ok );
$status = $fo->_vet_date_spec(
    tsrange => 'bubba', # can be any scalar, not necessarily a valid tsrange
);
ok( $status->ok );
$status = $fo->_vet_date_spec(
    date_list => [ qw( 2016-01-01 2016-01-02 2016-01-03 ) ],
    tsrange => 'bubba', # can be any scalar, not necessarily a valid tsrange
);
ok( $status->not_ok );
$status = $fo->_vet_date_spec();
ok( $status->not_ok );
$status = $fo->_vet_date_spec(
    date_list => undef,
    tsrange => undef,
);
ok( $status->not_ok );
isnt( $fo->context, undef );

note( $note = 'vet some valid date lists' );
$log->info( "=== $note" );

note( $note = 'valid date list #1' );
$log->info( "=== $note" );
reset_obj( $fo );
is( $fo->date_list, undef );
is( $fo->tsrange, undef );

t/fillup.t  view on Meta::CPAN

my $ins_effective = "1892-01-01";
my $ins_remark = 'TESTING';
my $priv = App::Dochazka::REST::Model::Privhistory->spawn(
              eid => $ins_eid,
              priv => $ins_priv,
              effective => $ins_effective,
              remark => $ins_remark,
          );
is( $priv->phid, undef, "phid undefined before INSERT" );
$status = $priv->insert( $faux_context );
diag( Dumper $status->text ) if $status->not_ok;
ok( $status->ok, "Post-insert status ok" );
ok( $priv->phid > 0, "INSERT assigned an phid" );
is( $priv->remark, $ins_remark, "remark survived INSERT" );
push my @phids_to_delete, $priv->phid;

note( $note = 'vet active - no schedule' );
$log->info( "=== $note" );
$status = $fo->_vet_employee( emp_obj => $active );
is( $status->level, 'ERR' );
is( $status->code, 'DISPATCH_EMPLOYEE_NO_SCHEDULE' );

t/ldap.t  view on Meta::CPAN

        'sync' => 1,
    );
    my $root_obj = App::Dochazka::REST::Model::Employee->spawn(
        'nick' => 'root',
        'sync' => 1,
    );

    note( "System users cannot be synced from LDAP" );
    $emp = $root_obj->clone();
    $status = $emp->ldap_sync();
    ok( $status->not_ok, "Employee sync operation failed" );
    is( $status->code, 'DOCHAZKA_LDAP_SYSTEM_USER_NOSYNC', "and for the right reason" );

    note( "Test that existing LDAP user can be synced" );
    note( "------------------------------------------" );

    note( "1. assert that $ex employee object has non-nick properties unpopulated" );
    $emp = $ex_obj->clone();
    my @props = grep( !/^nick/, keys( %{ $site->DOCHAZKA_LDAP_MAPPING } ) );
    foreach my $prop ( @props ) {
        is( $emp->{$prop}, undef, "$prop property is undef" );

t/ldap.t  view on Meta::CPAN

    foreach my $prop ( @props ) {
        ok( $emp->{$prop}, "$prop property has value " . $emp->{$prop} );
    }

    note( "Test that non-existing LDAP user can *not* be synced" );
    note( "----------------------------------------------------" );

    note( "1. populate $noex employee object from LDAP: fail" );
    $emp = $noex_obj->clone();
    $status = $emp->ldap_sync();
    ok( $status->not_ok );


    note( '####################################################' );
    note( 'DISPATCH TESTS' );
    note( '####################################################' );

    note( "GET employee/nick/$noex/ldap returns 404" );
    req( $test, 404, 'root', 'GET', "employee/nick/$noex/ldap" );

    note( "PUT employee/nick/$noex/ldap returns 404" );

t/model/activity.t  view on Meta::CPAN

is( aid_by_code( $dbix_conn, 'orneryFooBarred' ), undef, 'aid_by_code returns undef if code does not exist' );

note( 'insert an activity (success)' );
my $bogus_act = App::Dochazka::REST::Model::Activity->spawn(
    code => 'boguS',
    long_desc => 'An activity',
    remark => 'ACTIVITY',
);
note( "About to insert bogus_act" );
$status = $bogus_act->insert( $faux_context );
if ( $status->not_ok ) {
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK', "Insert activity with code 'bogus'" );
ok( defined( $bogus_act->aid ) );
ok( $bogus_act->aid > 0 );
# test code accessor method and code_to_upper trigger
is( $bogus_act->code, 'BOGUS' );
is( $bogus_act->long_desc, "An activity" );
is( $bogus_act->remark, 'ACTIVITY' );

note( 'try to insert the same activity again (fail with DOCHAZKA_DBI_ERR)' );
$status = $bogus_act->insert( $faux_context );
ok( $status->not_ok );
is( $status->level, 'ERR' );
is( $status->code, 'DOCHAZKA_DBI_ERR' );
like( $status->text, qr/Key \(code\)\=\(BOGUS\) already exists/ );

note( 'get_all_activities -> now there is one more' );
$status = get_all_activities( $dbix_conn );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_RECORDS_FOUND' );
is( scalar( @{ $status->payload } ), ( $initial_noof_act + 1 ) );

note( 'update the activity (success)' );
$bogus_act->{code} = "bogosITYVille";
$bogus_act->{long_desc} = "A bogus activity that doesn't belong here";
$bogus_act->{remark} = "BOGUS ACTIVITY";
$bogus_act->{disabled} = 1;
#diag( "About to update bogus_act" );
$status = $bogus_act->update( $faux_context );
if ( $status->not_ok ) {
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK' );

note( 'test accessors' );
is( $bogus_act->code, 'BOGOSITYVILLE' );
is( $bogus_act->long_desc, "A bogus activity that doesn't belong here" );
is( $bogus_act->remark, 'BOGUS ACTIVITY' );
ok( $bogus_act->disabled );

t/model/activity.t  view on Meta::CPAN

$status = get_all_activities( $dbix_conn, disabled => 1 );
is( $status->level, 'OK' );
is( $status->code, 'DISPATCH_RECORDS_FOUND' );
is( $status->{'count'}, ( $initial_noof_act + 1 ) );
# - and BOGOSITYVILLE is there
ok( scalar( grep { $_->{'code'} eq 'BOGOSITYVILLE'; } @{ $status->payload } ) );

note( 'CLEANUP: delete the bogus activity' );
#diag( "About to delete bogus_act" );
$status = $bogus_act->delete( $faux_context );
if ( $status->not_ok ) {
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK' );

ok( ! aid_exists( $dbix_conn, $aid_of_bogus_act ) );
ok( ! code_exists( $dbix_conn, $code_of_bogus_act ) );

note( 'attempt to load the bogus activity - no longer there' );
$status = App::Dochazka::REST::Model::Activity->load_by_code( $dbix_conn, 'BOGUS' );

t/model/component.t  view on Meta::CPAN


note( 'insert a component (success)' );
my $non_bogus_component = App::Dochazka::REST::Model::Component->spawn(
    path => 'non/bogus',
    source => 'An componnennt',
    acl => 'passerby',
);

note( "About to insert non_bogus_component" );
$status = $non_bogus_component->insert( $faux_context );
if ( $status->not_ok ) {
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK' );
ok( defined( $non_bogus_component->cid ) );
ok( $non_bogus_component->cid > 0 );
is( $non_bogus_component->path, 'non/bogus' );
is( $non_bogus_component->source, "An componnennt" );
is( $non_bogus_component->acl, 'passerby' );

note( 'try to insert the same component again (fail with DOCHAZKA_DBI_ERR)' );
$status = $non_bogus_component->insert( $faux_context );
ok( $status->not_ok );
is( $status->level, 'ERR' );
is( $status->code, 'DOCHAZKA_DBI_ERR' );
like( $status->text, qr#Key \(path\)\=\(non/bogus\) already exists# );

note( 'update the component (success)' );
$non_bogus_component->{path} = "bogosITYVille";
$non_bogus_component->{source} = "A bogus component that doesn't belong here";
$non_bogus_component->{acl} = 'inactive';
#diag( "About to update non_bogus_component" );
$status = $non_bogus_component->update( $faux_context );
if ( $status->not_ok ) {
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK' );

note( 'test accessors' );
is( $non_bogus_component->path, 'bogosITYVille' );
is( $non_bogus_component->source, "A bogus component that doesn't belong here" );
is( $non_bogus_component->acl, 'inactive' );

t/model/component.t  view on Meta::CPAN


my $cid_of_non_bogus_component = $non_bogus_component->cid; 
my $path_of_non_bogus_component = $non_bogus_component->path; 

ok( cid_exists( $dbix_conn, $cid_of_non_bogus_component ) );
ok( path_exists( $dbix_conn, $path_of_non_bogus_component ) );

note( 'CLEANUP: delete the bogus component' );
#diag( "About to delete non_bogus_component" );
$status = $non_bogus_component->delete( $faux_context );
if ( $status->not_ok ) {
    diag( Dumper $status );
    BAIL_OUT(0);
}
is( $status->level, 'OK' );

ok( ! cid_exists( $dbix_conn, $cid_of_non_bogus_component ) );
ok( ! path_exists( $dbix_conn, $path_of_non_bogus_component ) );

note( 'attempt to load the bogus component - no longer there' );
$status = App::Dochazka::REST::Model::Component->load_by_cid( $dbix_conn, $cid_of_non_bogus_component );

t/model/employee.t  view on Meta::CPAN

ok( $status->ok, "UPDATE status is OK" );
$status = App::Dochazka::REST::Model::Employee->load_by_nick( $dbix_conn, 'mrsfu' );
is( $status->code, 'DISPATCH_RECORDS_FOUND', "Nick mrsfu exists" );
my $emp2 = $status->payload;
is_deeply( $emp, $emp2 );

note( "pathologically change Mrs. Fu's nick to null" );
my $saved_nick = $emp->nick;
$emp->{'nick'} = undef;
$status = $emp->update( $faux_context );
ok( $status->not_ok );
is( $status->level, 'ERR' );
is( $status->code, 'DOCHAZKA_DBI_ERR' );
$emp->nick( $saved_nick );

note( "attempt to change Mr. Fu's supervisor to Mr. Fu - i.e. he would supervise himself" );
$mrfu->supervisor( $eid_of_mrfu );
$status = $mrfu->update( $faux_context );
is( $status->level, 'ERR' );
is( $status->code, 'DOCHAZKA_DBI_ERR' );

t/model/interval_lock.t  view on Meta::CPAN

( $by, $bm, $bd ) = $d =~ m/(\d+)-(\d+)-(\d+)/;
( $ey, $em, $ed ) = Add_Delta_Days( $by, $bm, $bd, 65 );
$end_date = sprintf( "%04d-%02d-%02d", $ey, $em, $ed );
$count = 0;
while ( $d ne $end_date ) {
    foreach my $intvl ( "[ $d 08:00, $d 10:00 )", "[ $d 10:00, $d 12:00 )", "[ $d 12:30, $d 14:30 )", "[ $d 14:30, $d 16:30 )" ) {
        $count += 1;
        $int->iid( undef );
        $int->intvl( $intvl );
        $status = $int->insert( $faux_context );
        if( $status->not_ok ) {
            diag( "Count: $count" );
            diag( Dumper $status );
            BAIL_OUT(0);
        }
        is( $status->level, 'OK' );
        is( $status->code, 'DOCHAZKA_CUD_OK' );
        ok( $int->iid > 0 );
    }
    $d = get_tomorrow( $d );
}

t/model/privhistory.t  view on Meta::CPAN

my $ins_effective = $today_ts;
my $ins_remark = 'TESTING';
my $priv = App::Dochazka::REST::Model::Privhistory->spawn(
              eid => $ins_eid,
              priv => $ins_priv,
              effective => $ins_effective,
              remark => $ins_remark,
          );
is( $priv->phid, undef, "phid undefined before INSERT" );
$status = $priv->insert( $faux_context );
diag( $status->text ) if $status->not_ok;
ok( $status->ok, "Post-insert status ok" );
ok( $priv->phid > 0, "INSERT assigned an phid" );
is( $priv->remark, $ins_remark, "remark survived INSERT" );

note( 'do a dastardly deed (insert the same privhistory row a second time)');
my $dastardly_sh = App::Dochazka::REST::Model::Privhistory->spawn(
    eid => $ins_eid,
    priv => $ins_priv,
    effective => $ins_effective,
    remark => 'Dastardly',

t/model/privhistory.t  view on Meta::CPAN


note( 'add another record within the range' );
my $priv3 = App::Dochazka::REST::Model::Privhistory->spawn(
              eid => $ins_eid,
              priv => 'passerby',
              effective => "$today 02:00",
              remark => $ins_remark,
          );
is( $priv3->phid, undef, "phid undefined before INSERT" );
$status = $priv3->insert( $faux_context );
diag( $status->text ) if $status->not_ok;
ok( $status->ok, "Post-insert status ok" );
ok( $priv3->phid > 0, "INSERT assigned an phid" );

note( 'test get_privhistory again -- do we get two records?' );
$status = get_privhistory( $faux_context, eid => $emp->eid, tsrange => "[$today_ts, $tomorrow_ts)" );
ok( $status->ok, "Privhistory record found" );
$ph = $status->payload->{'history'};
is( scalar @$ph, 2, "Two records" );
#diag( Dumper( $ph ) );



( run in 0.942 second using v1.01-cache-2.11-cpan-0a987023a57 )