tests against stock test data and live Evergreen
authorJason Etheridge <jason@esilibrary.com>
Wed, 17 Jul 2013 21:55:07 +0000 (17:55 -0400)
committerBill Erickson <berick@esilibrary.com>
Thu, 10 Oct 2013 18:49:29 +0000 (14:49 -0400)
I'm running these on a machine where Evergreen has been installed with the stock
test data.  Repeat invocations of the tests will cause test failures, as they
don't completely cleanup after themselves (the goal long-term is to rely on
complete database wipes between invocations).

Manual cleanup right now could include doing the following in psql:
DELETE FROM actor.workstation WHERE name ~ '.t$';

and paying the bills on the admin user (otherwise, once a Max Fines penalty is
generated, checkout tests will fail).

opensrf@dev141:~/git/Evergreen/Open-ILS/src/perlmods (livetests)$ make livecheck 2> /dev/null
perl Build.PL --destdir  || make -s build-perl-fail
Creating new 'MYMETA.yml' with configuration results
Creating new 'Build' script for 'OpenILS' version '2.4'
./Build test --test_files live_t || make -s build-perl-fail
live_t/00-simple.t ..................... ok
live_t/01-auth.t ....................... ok
live_t/02-simple_circ.t ................ ok
live_t/03-overdue_circ.t ............... ok
live_t/04-overdue_with_closed_dates.t .. ok
All tests successful.
Files=5, Tests=62, 11 wallclock secs ( 0.25 usr  0.16 sys +  3.52 cusr  0.63 csys =  4.56 CPU)
Result: PASS

and

opensrf@dev141:~/git/Evergreen/Open-ILS/src/perlmods (livetests)$ prove -v live_t/
live_t/00-simple.t .....................
1..2
 # Simple tests against the open-ils.storage service and the stock test data.
ok 1 - open-ils.storage.direct.actor.user.retrieve returned aou object
ok 2 - User with id = 1 is admin user
ok
live_t/01-auth.t .......................
1..4
 # Simple tests against the open-ils.auth service, memcached, and the stock test data.
 # authtime is 7200, authtoken is a3f2d06775fb670284450ad1f1d6ea00
ok 1 - Have an authtoken
ok 2 - Default authtime for staff login is 7200 seconds
ok 3 - Can retrieve authtoken from memcached
ok 4 - Authtoken is removed from memcached after logout
ok
live_t/02-simple_circ.t ................
1..14
 # Test circulation of item CONC70000345 against the admin user.
ok 1 - open-ils.storage.direct.actor.user.retrieve returned aou object
ok 2 - User with id = 1 is admin user
ok 3 - open-ils.storage.direct.asset.copy.retrieve returned acp object
ok 4 - Item with id = 310 has barcode CONC70000345
ok 5 - Item with id = 310 has status of Reshelving or Available
 # authtime is 7200, authtoken is f7c9ae35165ec74ab5e8ce5b84673da8
ok 6 - Have an authtoken
ok 7 - Registered a new workstation
 # authtime is 7200, authtoken is f7c9ae35165ec74ab5e8ce5b84673da8
ok 8 - Have an authtoken associated with the workstation
ok 9 - Checkout request returned a HASH
ok 10 - Checkout returned a SUCCESS event
ok 11 - Item with id = 310 has status of Checked Out after fresh Storage request
ok 12 - Checkin request returned a HASH
ok 13 - Checkin returned a SUCCESS event
ok 14 - Item with id = 310 has status of Reshelving or Available after fresh Storage request
ok
live_t/03-overdue_circ.t ...............
1..20
 # Test fine generation on checkin against the admin user.
ok 1 - open-ils.storage.direct.actor.user.retrieve returned aou object
ok 2 - User with id = 1 is admin user
ok 3 - open-ils.storage.direct.asset.copy.retrieve returned acp object
ok 4 - Item with id = 810 has barcode CONC71000345
ok 5 - Item with id = 810 has status of Reshelving or Available
 # authtime is 7200, authtoken is 5ab4ae5b9a09c7fd04b4eb0dabf19da5
ok 6 - Have an authtoken
ok 7 - Registered a new workstation
 # authtime is 7200, authtoken is 5ab4ae5b9a09c7fd04b4eb0dabf19da5
ok 8 - Have an authtoken associated with the workstation
ok 9 - Checkout request returned a HASH
ok 10 - Checkout returned a SUCCESS event
ok 11 - Checkout response object has payload object
ok 12 - Payload object has circ object
ok 13 - Circ objection has loan duration of "7 days"
ok 14 - Item with id = 810 has status of Checked Out after fresh Storage request
ok 15 - Zero bills associated with circulation
ok 16 - rewrote circ to have happened 20 days ago
ok 17 - Checkin request returned a HASH
ok 18 - Checkin returned a SUCCESS event
ok 19 - Item with id = 810 has status of Reshelving or Available after fresh Storage request
ok 20 - Thirteen bills associated with circulation
ok
live_t/04-overdue_with_closed_dates.t ..
1..22
 # Test fine generation with closed date on checkin against the admin user.
ok 1 - open-ils.storage.direct.actor.user.retrieve returned aou object
ok 2 - User with id = 1 is admin user
ok 3 - open-ils.storage.direct.asset.copy.retrieve returned acp object
ok 4 - Item with id = 1310 has barcode CONC72000345
ok 5 - Item with id = 1310 has status of Reshelving or Available
 # authtime is 7200, authtoken is dd4b533f677dd5ec6d177312eca2e86b
ok 6 - Have an authtoken
ok 7 - Registered a new workstation
 # authtime is 7200, authtoken is dd4b533f677dd5ec6d177312eca2e86b
ok 8 - Have an authtoken associated with the workstation
ok 9 - Created a closed date for 10 days ago
ok 10 - Checkout request returned a HASH
ok 11 - Checkout returned a SUCCESS event
ok 12 - Checkout response object has payload object
ok 13 - Payload object has circ object
ok 14 - Circ objection has loan duration of "7 days"
ok 15 - Item with id = 1310 has status of Checked Out after fresh Storage request
ok 16 - Zero bills associated with circulation
ok 17 - rewrote circ to have happened 20 days ago
ok 18 - Checkin request returned a HASH
ok 19 - Checkin returned a SUCCESS event
ok 20 - Item with id = 1310 has status of Reshelving or Available after fresh Storage request
ok 21 - Twelve bills associated with circulation (instead of 13, thanks to closed date)
ok 22 - Removed closed date
ok
All tests successful.
Files=5, Tests=62, 10 wallclock secs ( 0.06 usr  0.04 sys +  3.29 cusr  0.50 csys =  3.89 CPU)
Result: PASS

Signed-off-by: Jason Etheridge <jason@esilibrary.com>
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Open-ILS/src/perlmods/Makefile.am
Open-ILS/src/perlmods/live_t/00-simple.t [new file with mode: 0644]
Open-ILS/src/perlmods/live_t/01-auth.t [new file with mode: 0644]
Open-ILS/src/perlmods/live_t/02-simple_circ.t [new file with mode: 0644]
Open-ILS/src/perlmods/live_t/03-overdue_circ.t [new file with mode: 0644]
Open-ILS/src/perlmods/live_t/04-overdue_with_closed_dates.t [new file with mode: 0644]

index ac507d1..b443110 100644 (file)
@@ -25,6 +25,9 @@ all: build-perl
 check: build-perl
        ./Build test || make -s build-perl-fail
 
+livecheck: build-perl
+       ./Build test --test_files live_t || make -s build-perl-fail
+
 install: build-perl
        ./Build install
 
diff --git a/Open-ILS/src/perlmods/live_t/00-simple.t b/Open-ILS/src/perlmods/live_t/00-simple.t
new file mode 100644 (file)
index 0000000..f6f0c02
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+
+use Test::More tests => 2;
+
+diag("Simple tests against the open-ils.storage service and the stock test data.");
+
+use strict; use warnings;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::SettingsClient;
+
+my $config = `osrf_config --sysconfdir`;
+chomp $config;
+$config .= '/opensrf_core.xml';
+
+OpenSRF::System->bootstrap_client(config_file => $config);
+Fieldmapper->import(IDL =>
+    OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
+my $ses = OpenSRF::AppSession->create('open-ils.storage');
+my $req = $ses->request('open-ils.storage.direct.actor.user.retrieve', 1);
+if (my $resp = $req->recv) {
+    if (my $user = $resp->content) {
+        is(
+            ref $user,
+            'Fieldmapper::actor::user',
+            'open-ils.storage.direct.actor.user.retrieve returned aou object'
+        );
+        is(
+            $user->usrname,
+            'admin',
+            'User with id = 1 is admin user'
+        );
+    }
+}
+
diff --git a/Open-ILS/src/perlmods/live_t/01-auth.t b/Open-ILS/src/perlmods/live_t/01-auth.t
new file mode 100644 (file)
index 0000000..a522040
--- /dev/null
@@ -0,0 +1,167 @@
+#!perl
+
+use Test::More tests => 4;
+
+diag("Simple tests against the open-ils.auth service, memcached, and the stock test data.");
+
+use strict;
+use warnings;
+use Data::Dumper;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use Digest::MD5 qw(md5_hex);
+use OpenILS::Application::AppUtils;
+use OpenSRF::Utils::SettingsClient;
+
+# Some useful objects
+our $cache      = "OpenSRF::Utils::Cache";
+our $apputils   = "OpenILS::Application::AppUtils";
+our $memcache;
+our $authtoken;
+our $authtime;
+
+#----------------------------------------------------------------
+# Exit a script
+#----------------------------------------------------------------
+sub err {
+    my ($pkg, $file, $line, $sub)  = _caller();
+    no warnings;
+    die "Script halted with error ".
+        "($pkg : $file : $line : $sub):\n" . shift() . "\n";
+}
+
+#----------------------------------------------------------------
+# This is not the function you're looking for
+#----------------------------------------------------------------
+sub _caller {
+    my ($pkg, $file, $line, $sub)  = caller(2);
+    if(!$line) {
+        ($pkg, $file, $line)  = caller(1);
+        $sub = "";
+    }
+    return ($pkg, $file, $line, $sub);
+}
+
+#----------------------------------------------------------------
+# Connect to the servers
+#----------------------------------------------------------------
+sub osrf_connect {
+    my $config = `osrf_config --sysconfdir`;
+    chomp $config;
+    $config .= '/opensrf_core.xml';
+    err("Bootstrap config required") unless $config;
+    OpenSRF::System->bootstrap_client( config_file => $config );
+}
+
+#----------------------------------------------------------------
+# Get a handle for the memcache object
+#----------------------------------------------------------------
+sub osrf_cache {
+    $cache->use;
+    $memcache = $cache->new('global') unless $memcache;
+    return $memcache;
+}
+
+#----------------------------------------------------------------
+# Is the given object an OILS event?
+#----------------------------------------------------------------
+sub oils_is_event {
+    my $e = shift;
+    if( $e and ref($e) eq 'HASH' ) {
+        return 1 if defined($e->{ilsevent});
+    }
+    return 0;
+}
+
+#----------------------------------------------------------------
+# If the given object is an event, this prints the event info 
+# and exits the script
+#----------------------------------------------------------------
+sub oils_event_die {
+    my $evt = shift;
+    my ($pkg, $file, $line, $sub)  = _caller();
+    if(oils_is_event($evt)) {
+        if($evt->{ilsevent}) {
+            diag("\nReceived Event($pkg : $file : $line : $sub): \n" . Dumper($evt));
+            exit 1;
+        }
+    }
+}
+
+#----------------------------------------------------------------
+# Login to the auth server and set the global $authtoken var
+#----------------------------------------------------------------
+sub oils_login {
+    my( $username, $password, $type ) = @_;
+
+    $type |= "staff";
+
+    my $seed = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.init', $username );
+    err("No auth seed") unless $seed;
+
+    my $response = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.complete',
+        {   username => $username,
+            password => md5_hex($seed . md5_hex($password)),
+            type => $type });
+
+    err("No auth response returned on login") unless $response;
+
+    oils_event_die($response);
+
+    $authtime  = $response->{payload}->{authtime};
+    $authtoken = $response->{payload}->{authtoken};
+    diag("authtime is $authtime, authtoken is $authtoken");
+    return $authtoken;
+}
+
+#----------------------------------------------------------------
+# Destroys the login session on the server
+#----------------------------------------------------------------
+sub oils_logout {
+    $apputils->simplereq(
+        'open-ils.auth',
+        'open-ils.auth.session.delete', (@_ ? shift : $authtoken) );
+}
+
+#----------------------------------------------------------------
+# var $response = simplereq( $service, $method, @params );
+#----------------------------------------------------------------
+sub simplereq    { return $apputils->simplereq(@_); }
+sub osrf_request { return $apputils->simplereq(@_); }
+
+#----------------------------------------------------------------
+# The tests...  assumes stock sample data, full-auto install by
+# eg_wheezy_installer.sh, etc.
+#----------------------------------------------------------------
+
+osrf_connect();
+oils_login('admin','demo123','staff');
+
+ok(
+    $authtoken,
+    'Have an authtoken'
+);
+is(
+    $authtime,
+    7200,
+    'Default authtime for staff login is 7200 seconds'
+);
+
+osrf_cache();
+my $cached_obj = $memcache->get_cache("oils_auth_$authtoken");
+
+ok(
+    ref $cached_obj,
+    'Can retrieve authtoken from memcached'
+);
+
+oils_logout();
+
+$cached_obj = $memcache->get_cache("oils_auth_$authtoken");
+ok(
+    ! $cached_obj,
+    'Authtoken is removed from memcached after logout'
+);
+
diff --git a/Open-ILS/src/perlmods/live_t/02-simple_circ.t b/Open-ILS/src/perlmods/live_t/02-simple_circ.t
new file mode 100644 (file)
index 0000000..5d366e2
--- /dev/null
@@ -0,0 +1,270 @@
+#!perl
+
+use Test::More tests => 14;
+
+diag("Test circulation of item CONC70000345 against the admin user.");
+
+use constant WORKSTATION_NAME => 'BR4-test-02-simple-circ.t';
+use constant WORKSTATION_LIB => 7;
+use constant ITEM_BARCODE => 'CONC70000345';
+use constant ITEM_ID => 310;
+
+use strict;
+use warnings;
+use Data::Dumper;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use Digest::MD5 qw(md5_hex);
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use OpenSRF::Utils::SettingsClient;
+
+# Some useful objects
+our $cache      = "OpenSRF::Utils::Cache";
+our $apputils   = "OpenILS::Application::AppUtils";
+our $memcache;
+our $authtoken;
+our $authtime;
+
+#----------------------------------------------------------------
+# Exit a script
+#----------------------------------------------------------------
+sub err {
+    my ($pkg, $file, $line, $sub)  = _caller();
+    no warnings;
+    die "Script halted with error ".
+        "($pkg : $file : $line : $sub):\n" . shift() . "\n";
+}
+
+#----------------------------------------------------------------
+# This is not the function you're looking for
+#----------------------------------------------------------------
+sub _caller {
+    my ($pkg, $file, $line, $sub)  = caller(2);
+    if(!$line) {
+        ($pkg, $file, $line)  = caller(1);
+        $sub = "";
+    }
+    return ($pkg, $file, $line, $sub);
+}
+
+#----------------------------------------------------------------
+# Connect to the servers
+#----------------------------------------------------------------
+sub osrf_connect {
+    my $config = `osrf_config --sysconfdir`;
+    chomp $config;
+    $config .= '/opensrf_core.xml';
+    err("Bootstrap config required") unless $config;
+    OpenSRF::System->bootstrap_client( config_file => $config );
+    Fieldmapper->import(IDL =>
+        OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
+}
+
+#----------------------------------------------------------------
+# Is the given object an OILS event?
+#----------------------------------------------------------------
+sub oils_is_event {
+    my $e = shift;
+    if( $e and ref($e) eq 'HASH' ) {
+        return 1 if defined($e->{ilsevent});
+    }
+    return 0;
+}
+
+#----------------------------------------------------------------
+# If the given object is an event, this prints the event info 
+# and exits the script
+#----------------------------------------------------------------
+sub oils_event_die {
+    my $evt = shift;
+    my ($pkg, $file, $line, $sub)  = _caller();
+    if(oils_is_event($evt)) {
+        if($evt->{ilsevent}) {
+            diag("\nReceived Event($pkg : $file : $line : $sub): \n" . Dumper($evt));
+            exit 1;
+        }
+    }
+}
+
+#----------------------------------------------------------------
+# Login to the auth server and set the global $authtoken var
+#----------------------------------------------------------------
+sub oils_login {
+    my( $username, $password, $type, $ws ) = @_;
+
+    $type |= "staff";
+
+    my $seed = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.init', $username );
+    err("No auth seed") unless $seed;
+
+    my $response = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.complete',
+        {   username => $username,
+            password => md5_hex($seed . md5_hex($password)),
+            type => $type, workstation => $ws });
+
+    err("No auth response returned on login") unless $response;
+
+    oils_event_die($response);
+
+    $authtime  = $response->{payload}->{authtime};
+    $authtoken = $response->{payload}->{authtoken};
+    diag("authtime is $authtime, authtoken is $authtoken");
+    return $authtoken;
+}
+
+#----------------------------------------------------------------
+# Destroys the login session on the server
+#----------------------------------------------------------------
+sub oils_logout {
+    $apputils->simplereq(
+        'open-ils.auth',
+        'open-ils.auth.session.delete', (@_ ? shift : $authtoken) );
+}
+
+#----------------------------------------------------------------
+# var $response = simplereq( $service, $method, @params );
+#----------------------------------------------------------------
+sub simplereq    { return $apputils->simplereq(@_); }
+sub osrf_request { return $apputils->simplereq(@_); }
+
+#----------------------------------------------------------------
+
+sub register_workstation {
+    my $resp = osrf_request(
+        'open-ils.actor',
+        'open-ils.actor.workstation.register',
+        $authtoken, WORKSTATION_NAME, WORKSTATION_LIB);
+    return $resp;
+}
+
+sub do_checkout {
+    my( $patronid, $barcode ) = @_;
+    my $args = { patron => $patronid, barcode => $barcode };
+    my $resp = osrf_request(
+        'open-ils.circ',
+        'open-ils.circ.checkout.full', $authtoken, $args );
+    return $resp;
+}
+
+sub do_checkin {
+    my $barcode  = shift;
+    my $args = { barcode => $barcode };
+    my $resp = osrf_request(
+        'open-ils.circ',
+        'open-ils.circ.checkin', $authtoken, $args );
+    return $resp;
+}
+
+#----------------------------------------------------------------
+# The tests...  assumes stock sample data, full-auto install by
+# eg_wheezy_installer.sh, etc.
+#----------------------------------------------------------------
+
+osrf_connect();
+my $storage_ses = OpenSRF::AppSession->create('open-ils.storage');
+
+my $user_req = $storage_ses->request('open-ils.storage.direct.actor.user.retrieve', 1);
+if (my $user_resp = $user_req->recv) {
+    if (my $user = $user_resp->content) {
+        is(
+            ref $user,
+            'Fieldmapper::actor::user',
+            'open-ils.storage.direct.actor.user.retrieve returned aou object'
+        );
+        is(
+            $user->usrname,
+            'admin',
+            'User with id = 1 is admin user'
+        );
+    }
+}
+
+my $item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->barcode,
+            ITEM_BARCODE,
+            'Item with id = ' . ITEM_ID . ' has barcode ' . ITEM_BARCODE
+        );
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . ITEM_ID . ' has status of Reshelving or Available'
+        );
+    }
+}
+
+oils_login('admin','demo123','staff');
+ok(
+    $authtoken,
+    'Have an authtoken'
+);
+my $ws = register_workstation();
+ok(
+    ! ref $ws,
+    'Registered a new workstation'
+);
+
+oils_logout();
+oils_login('admin','demo123','staff',WORKSTATION_NAME);
+ok(
+    $authtoken,
+    'Have an authtoken associated with the workstation'
+);
+
+my $checkout_resp = do_checkout(1, ITEM_BARCODE);
+is(
+    ref $checkout_resp,
+    'HASH',
+    'Checkout request returned a HASH'
+);
+is(
+    $checkout_resp->{ilsevent},
+    0,
+    'Checkout returned a SUCCESS event'
+);
+   
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', 310);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            1,
+            'Item with id = ' . ITEM_ID . ' has status of Checked Out after fresh Storage request'
+        );
+    }
+}
+
+my $checkin_resp = do_checkin(ITEM_BARCODE);
+is(
+    ref $checkin_resp,
+    'HASH',
+    'Checkin request returned a HASH'
+);
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . ITEM_ID . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+oils_logout();
+
+
diff --git a/Open-ILS/src/perlmods/live_t/03-overdue_circ.t b/Open-ILS/src/perlmods/live_t/03-overdue_circ.t
new file mode 100644 (file)
index 0000000..f073e3e
--- /dev/null
@@ -0,0 +1,347 @@
+#!perl
+
+use Test::More tests => 20;
+
+diag("Test fine generation on checkin against the admin user.");
+
+use constant WORKSTATION_NAME => 'BR4-test-03-overdue-circ.t';
+use constant WORKSTATION_LIB => 7;
+use constant ITEM_BARCODE => 'CONC71000345';
+use constant ITEM_ID => 810;
+
+use strict;
+use warnings;
+use Data::Dumper;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use Digest::MD5 qw(md5_hex);
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use DateTime;
+use DateTime::Format::ISO8601;
+use OpenSRF::Utils qw/cleanse_ISO8601/;
+use OpenSRF::Utils::SettingsClient;
+
+# Some useful objects
+our $cache      = "OpenSRF::Utils::Cache";
+our $apputils   = "OpenILS::Application::AppUtils";
+our $memcache;
+our $authtoken;
+our $authtime;
+
+#----------------------------------------------------------------
+# Exit a script
+#----------------------------------------------------------------
+sub err {
+    my ($pkg, $file, $line, $sub)  = _caller();
+    no warnings;
+    die "Script halted with error ".
+        "($pkg : $file : $line : $sub):\n" . shift() . "\n";
+}
+
+#----------------------------------------------------------------
+# This is not the function you're looking for
+#----------------------------------------------------------------
+sub _caller {
+    my ($pkg, $file, $line, $sub)  = caller(2);
+    if(!$line) {
+        ($pkg, $file, $line)  = caller(1);
+        $sub = "";
+    }
+    return ($pkg, $file, $line, $sub);
+}
+
+#----------------------------------------------------------------
+# Connect to the servers
+#----------------------------------------------------------------
+sub osrf_connect {
+    my $config = `osrf_config --sysconfdir`;
+    chomp $config;
+    $config .= '/opensrf_core.xml';
+    err("Bootstrap config required") unless $config;
+    OpenSRF::System->bootstrap_client( config_file => $config );
+    Fieldmapper->import(IDL =>
+        OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
+}
+
+#----------------------------------------------------------------
+# Is the given object an OILS event?
+#----------------------------------------------------------------
+sub oils_is_event {
+    my $e = shift;
+    if( $e and ref($e) eq 'HASH' ) {
+        return 1 if defined($e->{ilsevent});
+    }
+    return 0;
+}
+
+#----------------------------------------------------------------
+# If the given object is an event, this prints the event info 
+# and exits the script
+#----------------------------------------------------------------
+sub oils_event_die {
+    my $evt = shift;
+    my ($pkg, $file, $line, $sub)  = _caller();
+    if(oils_is_event($evt)) {
+        if($evt->{ilsevent}) {
+            diag("\nReceived Event($pkg : $file : $line : $sub): \n" . Dumper($evt));
+            exit 1;
+        }
+    }
+}
+
+#----------------------------------------------------------------
+# Login to the auth server and set the global $authtoken var
+#----------------------------------------------------------------
+sub oils_login {
+    my( $username, $password, $type, $ws ) = @_;
+
+    $type |= "staff";
+
+    my $seed = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.init', $username );
+    err("No auth seed") unless $seed;
+
+    my $response = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.complete',
+        {   username => $username,
+            password => md5_hex($seed . md5_hex($password)),
+            type => $type, workstation => $ws });
+
+    err("No auth response returned on login") unless $response;
+
+    oils_event_die($response);
+
+    $authtime  = $response->{payload}->{authtime};
+    $authtoken = $response->{payload}->{authtoken};
+    diag("authtime is $authtime, authtoken is $authtoken");
+    return $authtoken;
+}
+
+#----------------------------------------------------------------
+# Destroys the login session on the server
+#----------------------------------------------------------------
+sub oils_logout {
+    $apputils->simplereq(
+        'open-ils.auth',
+        'open-ils.auth.session.delete', (@_ ? shift : $authtoken) );
+}
+
+#----------------------------------------------------------------
+# var $response = simplereq( $service, $method, @params );
+#----------------------------------------------------------------
+sub simplereq    { return $apputils->simplereq(@_); }
+sub osrf_request { return $apputils->simplereq(@_); }
+
+#----------------------------------------------------------------
+
+sub register_workstation {
+    my $resp = osrf_request(
+        'open-ils.actor',
+        'open-ils.actor.workstation.register',
+        $authtoken, WORKSTATION_NAME, WORKSTATION_LIB);
+    return $resp;
+}
+
+sub do_checkout {
+    my( $patronid, $barcode ) = @_;
+    my $args = { patron => $patronid, barcode => $barcode };
+    my $resp = osrf_request(
+        'open-ils.circ',
+        'open-ils.circ.checkout.full', $authtoken, $args );
+    return $resp;
+}
+
+sub do_checkin {
+    my $barcode  = shift;
+    my $args = { barcode => $barcode };
+    my $resp = osrf_request(
+        'open-ils.circ',
+        'open-ils.circ.checkin', $authtoken, $args );
+    return $resp;
+}
+
+#----------------------------------------------------------------
+# The tests...  assumes stock sample data, full-auto install by
+# eg_wheezy_installer.sh, etc.
+#----------------------------------------------------------------
+
+osrf_connect();
+my $storage_ses = OpenSRF::AppSession->create('open-ils.storage');
+my $circ_ses = OpenSRF::AppSession->create('open-ils.circ');
+my $cstore_ses = OpenSRF::AppSession->connect('open-ils.cstore');
+
+my $user_req = $storage_ses->request('open-ils.storage.direct.actor.user.retrieve', 1);
+if (my $user_resp = $user_req->recv) {
+    if (my $user = $user_resp->content) {
+        is(
+            ref $user,
+            'Fieldmapper::actor::user',
+            'open-ils.storage.direct.actor.user.retrieve returned aou object'
+        );
+        is(
+            $user->usrname,
+            'admin',
+            'User with id = 1 is admin user'
+        );
+    }
+}
+
+my $item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->barcode,
+            ITEM_BARCODE,
+            'Item with id = ' . ITEM_ID . ' has barcode ' . ITEM_BARCODE
+        );
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . ITEM_ID . ' has status of Reshelving or Available'
+        );
+    }
+}
+
+oils_login('admin','demo123','staff');
+ok(
+    $authtoken,
+    'Have an authtoken'
+);
+my $ws = register_workstation();
+ok(
+    ! ref $ws,
+    'Registered a new workstation'
+);
+
+oils_logout();
+oils_login('admin','demo123','staff',WORKSTATION_NAME);
+ok(
+    $authtoken,
+    'Have an authtoken associated with the workstation'
+);
+
+my $checkout_resp = do_checkout(1, ITEM_BARCODE);
+is(
+    ref $checkout_resp,
+    'HASH',
+    'Checkout request returned a HASH'
+);
+is(
+    $checkout_resp->{ilsevent},
+    0,
+    'Checkout returned a SUCCESS event'
+);
+ok(
+    ref $checkout_resp->{payload},
+    'Checkout response object has payload object'
+);
+ok(
+    ref $checkout_resp->{payload}->{circ},
+    'Payload object has circ object'
+);
+is(
+    $checkout_resp->{payload}->{circ}->duration,
+    '7 days',
+    'Circ objection has loan duration of "7 days"'
+);
+
+my $circ = $checkout_resp->{payload}->{circ};
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            1,
+            'Item with id = ' . ITEM_ID . ' has status of Checked Out after fresh Storage request'
+        );
+    }
+}
+
+my $bill_req = $circ_ses->request(
+    'open-ils.circ.money.billing.retrieve.all',
+    $authtoken,
+    $circ->id
+);
+if (my $bill_resp = $bill_req->recv) {
+    if (my $bills = $bill_resp->content) {
+        is(
+            scalar( @{ $bills } ),
+            0,
+            'Zero bills associated with circulation'
+        );
+    }
+}
+
+my $xact_start = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($circ->xact_start))->epoch;
+my $due_date = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($circ->due_date))->epoch;
+my $twenty_days = OpenSRF::Utils->interval_to_seconds('480 h 0 m 0 s');
+
+# Rewrite history; technically we should rewrite status_changed_item on the copy as well, but, meh...
+$circ->xact_start( $apputils->epoch2ISO8601($xact_start - $twenty_days) );
+$circ->due_date( $apputils->epoch2ISO8601($due_date - $twenty_days) );
+
+my $xact = $cstore_ses->request('open-ils.cstore.transaction.begin')->gather(1);
+my $update_req = $cstore_ses->request(
+    'open-ils.cstore.direct.action.circulation.update',
+    $circ
+);
+if (my $update_resp = $update_req->gather(1)) {
+    pass(
+        'rewrote circ to have happened 20 days ago'
+    );
+} else {
+    fail(
+        'rewrote circ to have happened 20 days ago'
+    );
+}
+$cstore_ses->request('open-ils.cstore.transaction.commit')->gather(1);
+
+########
+
+my $checkin_resp = do_checkin(ITEM_BARCODE);
+is(
+    ref $checkin_resp,
+    'HASH',
+    'Checkin request returned a HASH'
+);
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . ITEM_ID . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+$bill_req = $circ_ses->request(
+    'open-ils.circ.money.billing.retrieve.all',
+    $authtoken,
+    $circ->id
+);
+if (my $bill_resp = $bill_req->recv) {
+    if (my $bills = $bill_resp->content) {
+        is(
+            scalar( @{ $bills } ),
+            13,
+            'Thirteen bills associated with circulation'
+        );
+    }
+}
+
+
+oils_logout();
+
+
diff --git a/Open-ILS/src/perlmods/live_t/04-overdue_with_closed_dates.t b/Open-ILS/src/perlmods/live_t/04-overdue_with_closed_dates.t
new file mode 100644 (file)
index 0000000..efcc693
--- /dev/null
@@ -0,0 +1,390 @@
+#!perl
+
+use Test::More tests => 22;
+
+diag("Test fine generation with closed date on checkin against the admin user.");
+
+use constant WORKSTATION_NAME => 'BR4-test-04-overdue-with-closed-dates.t';
+use constant WORKSTATION_LIB => 7;
+use constant ITEM_BARCODE => 'CONC72000345';
+use constant ITEM_ID => 1310;
+
+use strict;
+use warnings;
+use Data::Dumper;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use Digest::MD5 qw(md5_hex);
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use DateTime;
+use DateTime::Format::ISO8601;
+use OpenSRF::Utils qw/cleanse_ISO8601/;
+use OpenSRF::Utils::SettingsClient;
+
+# Some useful objects
+our $cache      = "OpenSRF::Utils::Cache";
+our $apputils   = "OpenILS::Application::AppUtils";
+our $memcache;
+our $authtoken;
+our $authtime;
+
+#----------------------------------------------------------------
+# Exit a script
+#----------------------------------------------------------------
+sub err {
+    my ($pkg, $file, $line, $sub)  = _caller();
+    no warnings;
+    die "Script halted with error ".
+        "($pkg : $file : $line : $sub):\n" . shift() . "\n";
+}
+
+#----------------------------------------------------------------
+# This is not the function you're looking for
+#----------------------------------------------------------------
+sub _caller {
+    my ($pkg, $file, $line, $sub)  = caller(2);
+    if(!$line) {
+        ($pkg, $file, $line)  = caller(1);
+        $sub = "";
+    }
+    return ($pkg, $file, $line, $sub);
+}
+
+#----------------------------------------------------------------
+# Connect to the servers
+#----------------------------------------------------------------
+sub osrf_connect {
+    my $config = `osrf_config --sysconfdir`;
+    chomp $config;
+    $config .= '/opensrf_core.xml';
+    err("Bootstrap config required") unless $config;
+    OpenSRF::System->bootstrap_client( config_file => $config );
+    Fieldmapper->import(IDL =>
+        OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
+}
+
+#----------------------------------------------------------------
+# Is the given object an OILS event?
+#----------------------------------------------------------------
+sub oils_is_event {
+    my $e = shift;
+    if( $e and ref($e) eq 'HASH' ) {
+        return 1 if defined($e->{ilsevent});
+    }
+    return 0;
+}
+
+#----------------------------------------------------------------
+# If the given object is an event, this prints the event info 
+# and exits the script
+#----------------------------------------------------------------
+sub oils_event_die {
+    my $evt = shift;
+    my ($pkg, $file, $line, $sub)  = _caller();
+    if(oils_is_event($evt)) {
+        if($evt->{ilsevent}) {
+            diag("\nReceived Event($pkg : $file : $line : $sub): \n" . Dumper($evt));
+            exit 1;
+        }
+    }
+}
+
+#----------------------------------------------------------------
+# Login to the auth server and set the global $authtoken var
+#----------------------------------------------------------------
+sub oils_login {
+    my( $username, $password, $type, $ws ) = @_;
+
+    $type |= "staff";
+
+    my $seed = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.init', $username );
+    err("No auth seed") unless $seed;
+
+    my $response = $apputils->simplereq( 'open-ils.auth',
+        'open-ils.auth.authenticate.complete',
+        {   username => $username,
+            password => md5_hex($seed . md5_hex($password)),
+            type => $type, workstation => $ws });
+
+    err("No auth response returned on login") unless $response;
+
+    oils_event_die($response);
+
+    $authtime  = $response->{payload}->{authtime};
+    $authtoken = $response->{payload}->{authtoken};
+    diag("authtime is $authtime, authtoken is $authtoken");
+    return $authtoken;
+}
+
+#----------------------------------------------------------------
+# Destroys the login session on the server
+#----------------------------------------------------------------
+sub oils_logout {
+    $apputils->simplereq(
+        'open-ils.auth',
+        'open-ils.auth.session.delete', (@_ ? shift : $authtoken) );
+}
+
+#----------------------------------------------------------------
+# var $response = simplereq( $service, $method, @params );
+#----------------------------------------------------------------
+sub simplereq    { return $apputils->simplereq(@_); }
+sub osrf_request { return $apputils->simplereq(@_); }
+
+#----------------------------------------------------------------
+
+sub register_workstation {
+    my $resp = osrf_request(
+        'open-ils.actor',
+        'open-ils.actor.workstation.register',
+        $authtoken, WORKSTATION_NAME, WORKSTATION_LIB);
+    return $resp;
+}
+
+sub do_checkout {
+    my( $patronid, $barcode ) = @_;
+    my $args = { patron => $patronid, barcode => $barcode };
+    my $resp = osrf_request(
+        'open-ils.circ',
+        'open-ils.circ.checkout.full', $authtoken, $args );
+    return $resp;
+}
+
+sub do_checkin {
+    my $barcode  = shift;
+    my $args = { barcode => $barcode };
+    my $resp = osrf_request(
+        'open-ils.circ',
+        'open-ils.circ.checkin', $authtoken, $args );
+    return $resp;
+}
+
+sub create_closed_date {
+    my $ten_days = OpenSRF::Utils->interval_to_seconds('240 h 0 m 0 s');
+    my $almost_twenty_four_hours = OpenSRF::Utils->interval_to_seconds('23 h 59 m 59 s');
+    #$circ->due_date( $apputils->epoch2ISO8601($due_date - $twenty_days) );
+
+    my $aoucd = Fieldmapper::actor::org_unit::closed_date->new;
+    $aoucd->org_unit(WORKSTATION_LIB);
+    $aoucd->reason('04-overdue_with_closed_dates.t');
+    $aoucd->close_start(
+        $apputils->epoch2ISO8601(
+            DateTime->today()->epoch() - $ten_days
+        )
+    );
+    $aoucd->close_end(
+        $apputils->epoch2ISO8601(
+            DateTime->today()->epoch() - $ten_days + $almost_twenty_four_hours
+        )
+    );
+    my $resp = osrf_request(
+        'open-ils.actor',
+        'open-ils.actor.org_unit.closed.create',
+        $authtoken, $aoucd);
+    return $resp;
+}
+
+sub delete_closed_date {
+    my $aoucd = shift;
+    my $resp = osrf_request(
+        'open-ils.actor',
+        'open-ils.actor.org_unit.closed.delete',
+        $authtoken, ref $aoucd ? $aoucd->id : $aoucd );
+    return $resp;
+}
+
+#----------------------------------------------------------------
+# The tests...  assumes stock sample data, full-auto install by
+# eg_wheezy_installer.sh, etc.
+#----------------------------------------------------------------
+
+osrf_connect();
+my $storage_ses = OpenSRF::AppSession->create('open-ils.storage');
+my $circ_ses = OpenSRF::AppSession->create('open-ils.circ');
+my $cstore_ses = OpenSRF::AppSession->connect('open-ils.cstore');
+
+my $user_req = $storage_ses->request('open-ils.storage.direct.actor.user.retrieve', 1);
+if (my $user_resp = $user_req->recv) {
+    if (my $user = $user_resp->content) {
+        is(
+            ref $user,
+            'Fieldmapper::actor::user',
+            'open-ils.storage.direct.actor.user.retrieve returned aou object'
+        );
+        is(
+            $user->usrname,
+            'admin',
+            'User with id = 1 is admin user'
+        );
+    }
+}
+
+my $item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->barcode,
+            ITEM_BARCODE,
+            'Item with id = ' . ITEM_ID . ' has barcode ' . ITEM_BARCODE
+        );
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . ITEM_ID . ' has status of Reshelving or Available'
+        );
+    }
+}
+
+oils_login('admin','demo123','staff');
+ok(
+    $authtoken,
+    'Have an authtoken'
+);
+my $ws = register_workstation();
+ok(
+    ! ref $ws,
+    'Registered a new workstation'
+);
+
+oils_logout();
+oils_login('admin','demo123','staff',WORKSTATION_NAME);
+ok(
+    $authtoken,
+    'Have an authtoken associated with the workstation'
+);
+
+my $closed_date_obj = create_closed_date();
+is(
+    ref $closed_date_obj,
+    'Fieldmapper::actor::org_unit::closed_date',
+    'Created a closed date for 10 days ago'
+);
+
+my $checkout_resp = do_checkout(1, ITEM_BARCODE);
+is(
+    ref $checkout_resp,
+    'HASH',
+    'Checkout request returned a HASH'
+);
+is(
+    $checkout_resp->{ilsevent},
+    0,
+    'Checkout returned a SUCCESS event'
+);
+ok(
+    ref $checkout_resp->{payload},
+    'Checkout response object has payload object'
+);
+ok(
+    ref $checkout_resp->{payload}->{circ},
+    'Payload object has circ object'
+);
+is(
+    $checkout_resp->{payload}->{circ}->duration,
+    '7 days',
+    'Circ objection has loan duration of "7 days"'
+);
+
+my $circ = $checkout_resp->{payload}->{circ};
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            1,
+            'Item with id = ' . ITEM_ID . ' has status of Checked Out after fresh Storage request'
+        );
+    }
+}
+
+my $bill_req = $circ_ses->request(
+    'open-ils.circ.money.billing.retrieve.all',
+    $authtoken,
+    $circ->id
+);
+if (my $bill_resp = $bill_req->recv) {
+    if (my $bills = $bill_resp->content) {
+        is(
+            scalar( @{ $bills } ),
+            0,
+            'Zero bills associated with circulation'
+        );
+    }
+}
+
+my $xact_start = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($circ->xact_start))->epoch;
+my $due_date = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($circ->due_date))->epoch;
+my $twenty_days = OpenSRF::Utils->interval_to_seconds('480 h 0 m 0 s');
+
+# Rewrite history; technically we should rewrite status_changed_item on the copy as well, but, meh...
+$circ->xact_start( $apputils->epoch2ISO8601($xact_start - $twenty_days) );
+$circ->due_date( $apputils->epoch2ISO8601($due_date - $twenty_days) );
+
+my $xact = $cstore_ses->request('open-ils.cstore.transaction.begin')->gather(1);
+my $update_req = $cstore_ses->request(
+    'open-ils.cstore.direct.action.circulation.update',
+    $circ
+);
+if (my $update_resp = $update_req->gather(1)) {
+    pass(
+        'rewrote circ to have happened 20 days ago'
+    );
+} else {
+    fail(
+        'rewrote circ to have happened 20 days ago'
+    );
+}
+$cstore_ses->request('open-ils.cstore.transaction.commit')->gather(1);
+
+########
+
+my $checkin_resp = do_checkin(ITEM_BARCODE);
+is(
+    ref $checkin_resp,
+    'HASH',
+    'Checkin request returned a HASH'
+);
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', ITEM_ID);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . ITEM_ID . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+$bill_req = $circ_ses->request(
+    'open-ils.circ.money.billing.retrieve.all',
+    $authtoken,
+    $circ->id
+);
+if (my $bill_resp = $bill_req->recv) {
+    if (my $bills = $bill_resp->content) {
+        is(
+            scalar( @{ $bills } ),
+            12,
+            'Twelve bills associated with circulation (instead of 13, thanks to closed date)'
+        );
+    }
+}
+
+my $tmp = delete_closed_date($closed_date_obj);
+is($tmp,    1,  'Removed closed date');
+
+oils_logout();
+
+