Bug 17600: Standardize our EXPORT_OK
[srvgit] / t / db_dependent / Illrequests.t
old mode 100644 (file)
new mode 100755 (executable)
index b65dc08..24ef726
@@ -1,35 +1,47 @@
 #!/usr/bin/perl
-#
+
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
-#
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
 
 use File::Basename qw/basename/;
+
+use C4::Circulation qw( AddIssue AddReturn );
+
 use Koha::Database;
 use Koha::Illrequestattributes;
 use Koha::Illrequest::Config;
+use Koha::Biblios;
 use Koha::Patrons;
+use Koha::ItemTypes;
+use Koha::Items;
+use Koha::Libraries;
+use Koha::MessageAttributes;
+use Koha::Notice::Templates;
+use Koha::AuthorisedValueCategories;
+use Koha::AuthorisedValues;
 use t::lib::Mocks;
 use t::lib::TestBuilder;
 use Test::MockObject;
 use Test::MockModule;
 use Test::Exception;
+use Test::Deep qw/ cmp_deeply ignore /;
+use Test::Warn;
 
-use Test::More tests => 11;
+use Test::More tests => 13;
 
 my $schema = Koha::Database->new->schema;
 my $builder = t::lib::TestBuilder->new;
@@ -38,7 +50,7 @@ use_ok('Koha::Illrequests');
 
 subtest 'Basic object tests' => sub {
 
-    plan tests => 21;
+    plan tests => 24;
 
     $schema->storage->txn_begin;
 
@@ -75,6 +87,8 @@ subtest 'Basic object tests' => sub {
        "Accessurl getter works.");
     is($illrq_obj->cost, $illrq->{cost},
        "Cost getter works.");
+    is($illrq_obj->price_paid, $illrq->{price_paid},
+       "Price_paid getter works.");
     is($illrq_obj->notesopac, $illrq->{notesopac},
        "Notesopac getter works.");
     is($illrq_obj->notesstaff, $illrq->{notesstaff},
@@ -84,6 +98,19 @@ subtest 'Basic object tests' => sub {
     is($illrq_obj->backend, $illrq->{backend},
        "Backend getter works.");
 
+    is($illrq_obj->get_type, undef,
+        'get_type() returns undef if no type is set');
+    $builder->build({
+        source => 'Illrequestattribute',
+        value  => {
+            illrequest_id => $illrq_obj->illrequest_id,
+            type => 'type',
+            value => 'book'
+        }
+    });
+    is($illrq_obj->get_type, 'book',
+        'get_type() returns correct type if set');
+
     isnt($illrq_obj->status, 'COMP',
          "ILL is not currently marked complete.");
     $illrq_obj->mark_completed;
@@ -100,10 +127,12 @@ subtest 'Basic object tests' => sub {
 
 subtest 'Working with related objects' => sub {
 
-    plan tests => 5;
+    plan tests => 7;
 
     $schema->storage->txn_begin;
 
+    Koha::Illrequests->search->delete;
+
     my $patron = $builder->build({ source => 'Borrower' });
     my $illrq = $builder->build({
         source => 'Illrequest',
@@ -139,6 +168,16 @@ subtest 'Working with related objects' => sub {
     is($illrq_obj->illrequestattributes->count + 1, Koha::Illrequestattributes->search->count,
        "Fetching expected number of Illrequestattributes for our request.");
 
+    is($illrq_obj->biblio, undef, "->biblio returns undef if no biblio");
+    my $biblio = $builder->build_object({ class => 'Koha::Biblios' });
+    my $req_bib = $builder->build_object({
+        class => 'Koha::Illrequests',
+        value => {
+            biblio_id      => $biblio->biblionumber
+        }
+    });
+    isa_ok($req_bib->biblio, 'Koha::Biblio', "OK accessing related biblio");
+
     $illrq_obj->delete;
     is(Koha::Illrequestattributes->search->count, 1,
        "Correct number of illrequestattributes after delete.");
@@ -151,7 +190,7 @@ subtest 'Working with related objects' => sub {
 
 subtest 'Status Graph tests' => sub {
 
-    plan tests => 4;
+    plan tests => 5;
 
     $schema->storage->txn_begin;
 
@@ -208,6 +247,127 @@ subtest 'Status Graph tests' => sub {
         "REQ atom + linking QER = cyclical status graph"
     );
 
+    # Create a new node, with no prev_actions and no next_actions. This should
+    # protect us against regressions related to bug 22280.
+    my $new_node = {
+        TEST => {
+            prev_actions   => [ ],
+            id             => 'TEST',
+            next_actions   => [ ],
+        },
+    };
+    # Add the new node to the core_status_grpah
+    my $new_graph = $illrq_obj->_status_graph_union( $new_node, $illrq_obj->_core_status_graph);
+    # Compare the updated graph to the expected graph
+    # The structure we compare against here is just a copy of the structure found
+    # in Koha::Illrequest::_core_status_graph() + the new node we created above
+    cmp_deeply( $new_graph,
+        {
+        TEST => {
+            prev_actions   => [ ],
+            id             => 'TEST',
+            next_actions   => [ ],
+        },
+        NEW => {
+            prev_actions => [ ],                           # Actions containing buttons
+                                                           # leading to this status
+            id             => 'NEW',                       # ID of this status
+            name           => 'New request',               # UI name of this status
+            ui_method_name => 'New request',               # UI name of method leading
+                                                           # to this status
+            method         => 'create',                    # method to this status
+            next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
+                                                           # requests with this status
+            ui_method_icon => 'fa-plus',                   # UI Style class
+        },
+        REQ => {
+            prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
+            id             => 'REQ',
+            name           => 'Requested',
+            ui_method_name => 'Confirm request',
+            method         => 'confirm',
+            next_actions   => [ 'REQREV', 'COMP', 'CHK' ],
+            ui_method_icon => 'fa-check',
+        },
+        GENREQ => {
+            prev_actions   => [ 'NEW', 'REQREV' ],
+            id             => 'GENREQ',
+            name           => 'Requested from partners',
+            ui_method_name => 'Place request with partners',
+            method         => 'generic_confirm',
+            next_actions   => [ 'COMP', 'CHK' ],
+            ui_method_icon => 'fa-send-o',
+        },
+        REQREV => {
+            prev_actions   => [ 'REQ' ],
+            id             => 'REQREV',
+            name           => 'Request reverted',
+            ui_method_name => 'Revert Request',
+            method         => 'cancel',
+            next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
+            ui_method_icon => 'fa-times',
+        },
+        QUEUED => {
+            prev_actions   => [ ],
+            id             => 'QUEUED',
+            name           => 'Queued request',
+            ui_method_name => 0,
+            method         => 0,
+            next_actions   => [ 'REQ', 'KILL' ],
+            ui_method_icon => 0,
+        },
+        CANCREQ => {
+            prev_actions   => [ 'NEW' ],
+            id             => 'CANCREQ',
+            name           => 'Cancellation requested',
+            ui_method_name => 0,
+            method         => 0,
+            next_actions   => [ 'KILL', 'REQ' ],
+            ui_method_icon => 0,
+        },
+        COMP => {
+            prev_actions   => [ 'REQ' ],
+            id             => 'COMP',
+            name           => 'Completed',
+            ui_method_name => 'Mark completed',
+            method         => 'mark_completed',
+            next_actions   => [ 'CHK' ],
+            ui_method_icon => 'fa-check',
+        },
+        KILL => {
+            prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
+            id             => 'KILL',
+            name           => 0,
+            ui_method_name => 'Delete request',
+            method         => 'delete',
+            next_actions   => [ ],
+            ui_method_icon => 'fa-trash',
+        },
+        CHK => {
+            prev_actions   => [ 'REQ', 'GENREQ', 'COMP' ],
+            id             => 'CHK',
+            name           => 'Checked out',
+            ui_method_name => 'Check out',
+            needs_prefs    => [ 'CirculateILL' ],
+            needs_perms    => [ 'user_circulate_circulate_remaining_permissions' ],
+            needs_all      => ignore(),
+            method         => 'check_out',
+            next_actions   => [ ],
+            ui_method_icon => 'fa-upload',
+        },
+        RET => {
+            prev_actions   => [ 'CHK' ],
+            id             => 'RET',
+            name           => 'Returned to library',
+            ui_method_name => 'Check in',
+            method         => 'check_in',
+            next_actions   => [ 'COMP' ],
+            ui_method_icon => 'fa-download',
+        }
+    },
+        "new node + core_status_graph = bigger status graph"
+    ) || diag explain $new_graph;
+
     $schema->storage->txn_rollback;
 };
 
@@ -221,20 +381,19 @@ subtest 'Backend testing (mocks)' => sub {
     # the Dummy plugin installed.  load_backend & available_backends don't
     # currently have tests as a result.
 
+    t::lib::Mocks->mock_config('interlibrary_loans', { backend_dir => 'a_dir' }  );
     my $backend = Test::MockObject->new;
     $backend->set_isa('Koha::Illbackends::Mock');
     $backend->set_always('name', 'Mock');
 
     my $patron = $builder->build({ source => 'Borrower' });
-    my $illrq = $builder->build({
-        source => 'Illrequest',
-        value => { borrowernumber => $patron->{borrowernumber} }
+    my $illrq = $builder->build_object({
+        class => 'Koha::Illrequests',
     });
-    my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
 
-    $illrq_obj->_backend($backend);
+    $illrq->_backend($backend);
 
-    isa_ok($illrq_obj->_backend, 'Koha::Illbackends::Mock',
+    isa_ok($illrq->_backend, 'Koha::Illbackends::Mock',
            "OK accessing mocked backend.");
 
     # _backend_capability tests:
@@ -246,15 +405,15 @@ subtest 'Backend testing (mocks)' => sub {
     # functionality, such as unmediated in the BLDSS backend (also see
     # bugzilla 18837).
     $backend->set_always('capabilities', undef);
-    is($illrq_obj->_backend_capability('Test'), 0,
+    is($illrq->_backend_capability('Test'), 0,
        "0 returned on Mock not implementing capabilities.");
 
     $backend->set_always('capabilities', 0);
-    is($illrq_obj->_backend_capability('Test'), 0,
+    is($illrq->_backend_capability('Test'), 0,
        "0 returned on Mock not implementing Test capability.");
 
     $backend->set_always('capabilities', sub { return 'bar'; } );
-    is($illrq_obj->_backend_capability('Test'), 'bar',
+    is($illrq->_backend_capability('Test'), 'bar',
        "'bar' returned on Mock implementing Test capability.");
 
     # metadata test: we need to be sure that we return the arbitrary values
@@ -271,10 +430,10 @@ subtest 'Backend testing (mocks)' => sub {
     );
 
     is_deeply(
-        $illrq_obj->metadata,
+        $illrq->metadata,
         {
-            ID => $illrq_obj->illrequest_id,
-            Title => $illrq_obj->patron->borrowernumber
+            ID => $illrq->illrequest_id,
+            Title => $illrq->patron->borrowernumber
         },
         "Test metadata."
     );
@@ -283,21 +442,21 @@ subtest 'Backend testing (mocks)' => sub {
 
     # No backend graph extension
     $backend->set_always('status_graph', {});
-    is_deeply($illrq_obj->capabilities('COMP'),
+    is_deeply($illrq->capabilities('COMP'),
               {
                   prev_actions   => [ 'REQ' ],
                   id             => 'COMP',
                   name           => 'Completed',
                   ui_method_name => 'Mark completed',
                   method         => 'mark_completed',
-                  next_actions   => [ ],
+                  next_actions   => [ 'CHK' ],
                   ui_method_icon => 'fa-check',
               },
               "Dummy status graph for COMP.");
-    is($illrq_obj->capabilities('UNKNOWN'), undef,
+    is($illrq->capabilities('UNKNOWN'), undef,
        "Dummy status graph for UNKNOWN.");
-    is_deeply($illrq_obj->capabilities(),
-              $illrq_obj->_core_status_graph,
+    is_deeply($illrq->capabilities(),
+              $illrq->_core_status_graph,
               "Dummy full status graph.");
     # Simple backend graph extension
     $backend->set_always('status_graph',
@@ -308,18 +467,18 @@ subtest 'Backend testing (mocks)' => sub {
                                  next_actions   => [ 'REQ' ],
                              },
                          });
-    is_deeply($illrq_obj->capabilities('QER'),
+    is_deeply($illrq->capabilities('QER'),
               {
                   prev_actions   => [ 'REQ' ],
                   id             => 'QER',
                   next_actions   => [ 'REQ' ],
               },
               "Simple status graph for QER.");
-    is($illrq_obj->capabilities('UNKNOWN'), undef,
+    is($illrq->capabilities('UNKNOWN'), undef,
        "Simple status graph for UNKNOWN.");
-    is_deeply($illrq_obj->capabilities(),
-              $illrq_obj->_status_graph_union(
-                  $illrq_obj->_core_status_graph,
+    is_deeply($illrq->capabilities(),
+              $illrq->_status_graph_union(
+                  $illrq->_core_status_graph,
                   {
                       QER => {
                           prev_actions   => [ 'REQ' ],
@@ -334,7 +493,7 @@ subtest 'Backend testing (mocks)' => sub {
 
     # No backend graph extension
     $backend->set_always('status_graph', {});
-    is($illrq_obj->custom_capability('unknown', {}), 0,
+    is($illrq->custom_capability('unknown', {}), 0,
        "Unknown candidate.");
 
     # Simple backend graph extension
@@ -349,7 +508,7 @@ subtest 'Backend testing (mocks)' => sub {
                          });
     $backend->mock('identity',
                    sub { my ( $self, $params ) = @_; return $params->{other}; });
-    is($illrq_obj->custom_capability('identity', { test => 1 })->{test}, 1,
+    is($illrq->custom_capability('identity', { test => 1, method => 'blah' })->{test}, 1,
        "Resolve identity custom_capability");
 
     $schema->storage->txn_rollback;
@@ -358,7 +517,7 @@ subtest 'Backend testing (mocks)' => sub {
 
 subtest 'Backend core methods' => sub {
 
-    plan tests => 16;
+    plan tests => 18;
 
     $schema->storage->txn_begin;
 
@@ -366,19 +525,33 @@ subtest 'Backend core methods' => sub {
     my $backend = Test::MockObject->new;
     $backend->set_isa('Koha::Illbackends::Mock');
     $backend->set_always('name', 'Mock');
+    $backend->mock('capabilities', sub { return 'Mock'; });
 
     my $config = Test::MockObject->new;
     $config->set_always('backend_dir', "/tmp");
     $config->set_always('getLimitRules',
                         { default => { count => 0, method => 'active' } });
 
-    my $illrq = $builder->build({source => 'Illrequest'});
-    my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
-    $illrq_obj->_config($config);
-    $illrq_obj->_backend($backend);
+    my $illrq = $builder->build_object({
+        class => 'Koha::Illrequests',
+        value => { backend => undef }
+    });
+    $illrq->_config($config);
+
+    # Test error conditions (no backend)
+    throws_ok { $illrq->load_backend; }
+        'Koha::Exceptions::Ill::InvalidBackendId',
+        'Exception raised correctly';
+
+    throws_ok { $illrq->load_backend(''); }
+        'Koha::Exceptions::Ill::InvalidBackendId',
+        'Exception raised correctly';
+
+    # Now load the mocked backend
+    $illrq->_backend($backend);
 
     # expandTemplate:
-    is_deeply($illrq_obj->expandTemplate({ test => 1, method => "bar" }),
+    is_deeply($illrq->expandTemplate({ test => 1, method => "bar" }),
               {
                   test => 1,
                   method => "bar",
@@ -392,24 +565,11 @@ subtest 'Backend core methods' => sub {
     $backend->set_series('create',
                          { stage => 'bar', method => 'create' },
                          { stage => 'commit', method => 'create' },
+                         { stage => 'commit', method => 'create' },
+                         { stage => 'commit', method => 'create' },
                          { stage => 'commit', method => 'create' });
-    # Test Copyright Clearance
-    t::lib::Mocks::mock_preference("ILLModuleCopyrightClearance", "Test Copyright Clearance.");
-    is_deeply($illrq_obj->backend_create({test => 1}),
-              {
-                  error   => 0,
-                  status  => '',
-                  message => '',
-                  method  => 'create',
-                  stage   => 'copyrightclearance',
-                  value   => {
-                      backend => "Mock"
-                  }
-              },
-              "Backend create: copyright clearance.");
-    t::lib::Mocks::mock_preference("ILLModuleCopyrightClearance", "");
     # Test non-commit
-    is_deeply($illrq_obj->backend_create({test => 1}),
+    is_deeply($illrq->backend_create({test => 1}),
               {
                   stage => 'bar', method => 'create',
                   template => "/tmp/Mock/intra-includes/create.inc",
@@ -417,28 +577,67 @@ subtest 'Backend core methods' => sub {
               },
               "Backend create: arbitrary stage.");
     # Test commit
-    is_deeply($illrq_obj->backend_create({test => 1}),
+    is_deeply($illrq->backend_create({test => 1}),
               {
                   stage => 'commit', method => 'create', permitted => 0,
                   template => "/tmp/Mock/intra-includes/create.inc",
                   opac_template => "/tmp/Mock/opac-includes/create.inc",
               },
               "Backend create: arbitrary stage, not permitted.");
-    is($illrq_obj->status, "QUEUED", "Backend create: queued if restricted.");
+    is($illrq->status, "QUEUED", "Backend create: queued if restricted.");
     $config->set_always('getLimitRules', {});
-    $illrq_obj->status('NEW');
-    is_deeply($illrq_obj->backend_create({test => 1}),
+    $illrq->status('NEW');
+    is_deeply($illrq->backend_create({test => 1}),
               {
                   stage => 'commit', method => 'create', permitted => 1,
                   template => "/tmp/Mock/intra-includes/create.inc",
                   opac_template => "/tmp/Mock/opac-includes/create.inc",
               },
               "Backend create: arbitrary stage, permitted.");
-    is($illrq_obj->status, "NEW", "Backend create: not-queued.");
+    is($illrq->status, "NEW", "Backend create: not-queued.");
+
+    # Test that enabling the unmediated workflow causes the backend's
+    # 'unmediated_ill' method to be called
+    t::lib::Mocks::mock_preference('ILLModuleUnmediated', '1');
+    $backend->mock(
+        'capabilities',
+        sub {
+            my ($self, $name) = @_;
+            if ($name eq 'unmediated_ill') {
+                return sub {
+                    return { unmediated_ill => 1 };
+                };
+            }
+        }
+    );
+    $illrq->status('NEW');
+    is_deeply(
+        $illrq->backend_create({test => 1}),
+        {
+            'opac_template' => '/tmp/Mock/opac-includes/.inc',
+            'template' => '/tmp/Mock/intra-includes/.inc',
+            'unmediated_ill' => 1
+        },
+        "Backend create: commit stage, permitted, ILLModuleUnmediated enabled."
+    );
+
+    # Test that disabling the unmediated workflow causes the backend's
+    # 'unmediated_ill' method to be NOT called
+    t::lib::Mocks::mock_preference('ILLModuleUnmediated', '0');
+    $illrq->status('NEW');
+    is_deeply(
+        $illrq->backend_create({test => 1}),
+        {
+            stage => 'commit', method => 'create', permitted => 1,
+            template => "/tmp/Mock/intra-includes/create.inc",
+            opac_template => "/tmp/Mock/opac-includes/create.inc",
+        },
+        "Backend create: commit stage, permitted, ILLModuleUnmediated disabled."
+    );
 
     # backend_renew
     $backend->set_series('renew', { stage => 'bar', method => 'renew' });
-    is_deeply($illrq_obj->backend_renew({test => 1}),
+    is_deeply($illrq->backend_renew({test => 1}),
               {
                   stage => 'bar', method => 'renew',
                   template => "/tmp/Mock/intra-includes/renew.inc",
@@ -448,7 +647,7 @@ subtest 'Backend core methods' => sub {
 
     # backend_cancel
     $backend->set_series('cancel', { stage => 'bar', method => 'cancel' });
-    is_deeply($illrq_obj->backend_cancel({test => 1}),
+    is_deeply($illrq->backend_cancel({test => 1}),
               {
                   stage => 'bar', method => 'cancel',
                   template => "/tmp/Mock/intra-includes/cancel.inc",
@@ -458,7 +657,7 @@ subtest 'Backend core methods' => sub {
 
     # backend_update_status
     $backend->set_series('update_status', { stage => 'bar', method => 'update_status' });
-    is_deeply($illrq_obj->backend_update_status({test => 1}),
+    is_deeply($illrq->backend_update_status({test => 1}),
               {
                   stage => 'bar', method => 'update_status',
                   template => "/tmp/Mock/intra-includes/update_status.inc",
@@ -468,7 +667,7 @@ subtest 'Backend core methods' => sub {
 
     # backend_confirm
     $backend->set_series('confirm', { stage => 'bar', method => 'confirm' });
-    is_deeply($illrq_obj->backend_confirm({test => 1}),
+    is_deeply($illrq->backend_confirm({test => 1}),
               {
                   stage => 'bar', method => 'confirm',
                   template => "/tmp/Mock/intra-includes/confirm.inc",
@@ -490,7 +689,7 @@ subtest 'Backend core methods' => sub {
         source => 'Borrower',
         value => { categorycode => "ILLTSTLIB" },
     });
-    my $gen_conf = $illrq_obj->generic_confirm({
+    my $gen_conf = $illrq->generic_confirm({
         current_branchcode => $illbrn->{branchcode}
     });
     isnt(index($gen_conf->{value}->{draft}->{body}, $backend->metadata->{Test}), -1,
@@ -503,26 +702,19 @@ subtest 'Backend core methods' => sub {
        "Generic confirm: partner 2 is correct."
     );
 
-    dies_ok { $illrq_obj->generic_confirm({
+    dies_ok { $illrq->generic_confirm({
         current_branchcode => $illbrn->{branchcode},
         stage => 'draft'
     }) }
         "Generic confirm: missing to dies OK.";
 
-    dies_ok { $illrq_obj->generic_confirm({
-        current_branchcode => $illbrn->{branchcode},
-        partners => $partner1->{email},
-        stage => 'draft'
-    }) }
-        "Generic confirm: missing from dies OK.";
-
     $schema->storage->txn_rollback;
 };
 
 
 subtest 'Helpers' => sub {
 
-    plan tests => 9;
+    plan tests => 20;
 
     $schema->storage->txn_begin;
 
@@ -530,6 +722,16 @@ subtest 'Helpers' => sub {
     my $backend = Test::MockObject->new;
     $backend->set_isa('Koha::Illbackends::Mock');
     $backend->set_always('name', 'Mock');
+    $backend->mock(
+        'metadata',
+        sub {
+            my ( $self, $rq ) = @_;
+            return {
+                title => 'mytitle',
+                author => 'myauthor'
+            }
+        }
+    );
 
     my $config = Test::MockObject->new;
     $config->set_always('backend_dir', "/tmp");
@@ -538,9 +740,19 @@ subtest 'Helpers' => sub {
         source => 'Borrower',
         value => { categorycode => "A" }
     });
+    # Create a mocked branch with no email addressed defined
+    my $illbrn = $builder->build({
+        source => 'Branch',
+        value => {
+            branchcode => 'HDE',
+            branchemail => "",
+            branchillemail => "",
+            branchreplyto => ""
+        }
+    });
     my $illrq = $builder->build({
         source => 'Illrequest',
-        value => { branchcode => "CPL", borrowernumber => $patron->{borrowernumber} }
+        value => { branchcode => "HDE", borrowernumber => $patron->{borrowernumber} }
     });
     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
     $illrq_obj->_config($config);
@@ -548,37 +760,28 @@ subtest 'Helpers' => sub {
 
     # getPrefix
     $config->set_series('getPrefixes',
-                        { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
-                        { A => "ATEST", C => "CBAR", default => "DEFAULT" });
-    is($illrq_obj->getPrefix({ brw_cat => "C", branch => "CPL" }), "CBAR",
-       "getPrefix: brw_cat");
-    $config->set_series('getPrefixes',
-                        { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
+                        { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
                         { A => "ATEST", C => "CBAR", default => "DEFAULT" });
-    is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "CPL" }), "TEST",
+    is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "HDE" }), "TEST",
        "getPrefix: branch");
     $config->set_series('getPrefixes',
-                        { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
+                        { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
                         { A => "ATEST", C => "CBAR", default => "DEFAULT" });
-    is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "UNKNOWN" }), "DEFAULT",
+    is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
        "getPrefix: default");
     $config->set_always('getPrefixes', {});
-    is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "UNKNOWN" }), "",
+    is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
        "getPrefix: the empty prefix");
 
     # id_prefix
     $config->set_series('getPrefixes',
-                        { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
-                        { A => "ATEST", C => "CBAR", default => "DEFAULT" });
-    is($illrq_obj->id_prefix, "ATEST-", "id_prefix: brw_cat");
-    $config->set_series('getPrefixes',
-                        { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
+                        { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
                         { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
     is($illrq_obj->id_prefix, "TEST-", "id_prefix: branch");
     $config->set_series('getPrefixes',
-                        { CPLT => "TEST", TSLT => "BAR", default => "DEFAULT" },
+                        { HDET => "TEST", TSLT => "BAR", default => "DEFAULT" },
                         { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
-    is($illrq_obj->id_prefix, "DEFAULT-", "id_prefix: default");
+    is($illrq_obj->id_prefix, "", "id_prefix: default");
 
     # requires_moderation
     $illrq_obj->status('NEW')->store;
@@ -586,6 +789,100 @@ subtest 'Helpers' => sub {
     $illrq_obj->status('CANCREQ')->store;
     is($illrq_obj->requires_moderation, 'CANCREQ', "requires_moderation: Yes.");
 
+    #send_patron_notice
+    my $attr = Koha::MessageAttributes->find({ message_name => 'Ill_ready' });
+    C4::Members::Messaging::SetMessagingPreference({
+        borrowernumber => $patron->{borrowernumber},
+        message_attribute_id => $attr->message_attribute_id,
+        message_transport_types => ['email']
+    });
+    my $return_patron = $illrq_obj->send_patron_notice('ILL_PICKUP_READY');
+    my $notice = $schema->resultset('MessageQueue')->search({
+            letter_code => 'ILL_PICKUP_READY',
+            message_transport_type => 'email',
+            borrowernumber => $illrq_obj->borrowernumber
+        })->next()->letter_code;
+    is_deeply(
+        $return_patron,
+        { result => { success => ['email'], fail => [] } },
+        "Correct return when notice created"
+    );
+    is($notice, 'ILL_PICKUP_READY' ,"Notice is correctly created");
+
+    my $return_patron_fail = $illrq_obj->send_patron_notice();
+    is_deeply(
+        $return_patron_fail,
+        { error => 'notice_no_type' },
+        "Correct error when missing type"
+    );
+
+    #send_staff_notice
+    # Specify that no staff notices should be send
+    t::lib::Mocks::mock_preference('ILLSendStaffNotices', '');
+    my $return_staff_cancel_fail =
+        $illrq_obj->send_staff_notice('ILL_REQUEST_CANCEL');
+    is_deeply(
+        $return_staff_cancel_fail,
+        { error => 'notice_not_enabled' },
+        "Does not send notices that are not enabled"
+    );
+    my $queue = $schema->resultset('MessageQueue')->search({
+            letter_code => 'ILL_REQUEST_CANCEL'
+        });
+    is($queue->count, 0, "Notice is not queued");
+
+    # Specify that the cancel notice can be sent
+    t::lib::Mocks::mock_preference('ILLSendStaffNotices', 'ILL_REQUEST_CANCEL');
+    my $return_staff_cancel = $illrq_obj->send_staff_notice(
+        'ILL_REQUEST_CANCEL'
+    );
+    is_deeply(
+        $return_staff_cancel,
+        { success => 'notice_queued' },
+        "Correct return when staff notice created"
+    );
+    $queue = $schema->resultset('MessageQueue')->search({
+            letter_code => 'ILL_REQUEST_CANCEL'
+        });
+    is($queue->count, 1, "Notice queued as expected");
+
+    my $return_staff_fail = $illrq_obj->send_staff_notice();
+    is_deeply(
+        $return_staff_fail,
+        { error => 'notice_no_type' },
+        "Correct error when missing type"
+    );
+    $queue = $schema->resultset('MessageQueue')->search({
+            letter_code => 'ILL_REQUEST_CANCEL'
+        });
+    is($queue->count, 1, "Notice is not queued");
+
+    #get_notice
+    my $not = $illrq_obj->get_notice({
+        notice_code => 'ILL_REQUEST_CANCEL',
+        transport   => 'email'
+    });
+
+    # We test the properties of the hashref separately because the random
+    # hash ordering of the metadata means we can't test the entire thing
+    # with is_deeply
+    ok(
+        $not->{module} eq 'ill',
+        'Correct module return from get_notice'
+    );
+    ok(
+        $not->{name} eq 'ILL request cancelled',
+        'Correct name return from get_notice'
+    );
+    ok(
+        $not->{message_transport_type} eq 'email',
+        'Correct message_transport_type return from get_notice'
+    );
+    ok(
+        $not->{title} eq 'Interlibrary loan request cancelled',
+        'Correct title return from get_notice'
+    );
+
     $schema->storage->txn_rollback;
 };
 
@@ -624,6 +921,138 @@ subtest 'Censorship' => sub {
     $schema->storage->txn_rollback;
 };
 
+subtest 'Checking out' => sub {
+
+    plan tests => 17;
+
+    $schema->storage->txn_begin;
+
+    my $itemtype = $builder->build_object({
+        class => 'Koha::ItemTypes',
+        value => {
+            notforloan => 1
+        }
+    });
+    my $library = $builder->build_object({ class => 'Koha::Libraries' });
+    my $biblio = $builder->build_sample_biblio();
+    my $patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { category_type => 'x' }
+    });
+    my $request = $builder->build_object({
+        class => 'Koha::Illrequests',
+        value => {
+            borrowernumber => $patron->borrowernumber,
+            biblio_id      => $biblio->biblionumber
+        }
+    });
+
+    # First test that calling check_out without a stage param returns
+    # what's required to build the form
+    my $no_stage = $request->check_out();
+    is($no_stage->{method}, 'check_out');
+    is($no_stage->{stage}, 'form');
+    isa_ok($no_stage->{value}, 'HASH');
+    isa_ok($no_stage->{value}->{itemtypes}, 'Koha::ItemTypes');
+    isa_ok($no_stage->{value}->{libraries}, 'Koha::Libraries');
+    isa_ok($no_stage->{value}->{statistical}, 'Koha::Patrons');
+    isa_ok($no_stage->{value}->{biblio}, 'Koha::Biblio');
+
+    # Now test that form validation works when we supply a 'form' stage
+    #
+    # No item_type
+    my $form_stage_missing_params = $request->check_out({
+        stage => 'form'
+    });
+    is_deeply($form_stage_missing_params->{value}->{errors}, {
+        item_type => 1
+    });
+    # inhouse passed but not a valid patron
+    my $form_stage_bad_patron = $request->check_out({
+        stage     => 'form',
+        item_type => $itemtype->itemtype,
+        inhouse   => 'I_DONT_EXIST'
+    });
+    is_deeply($form_stage_bad_patron->{value}->{errors}, {
+        inhouse => 1
+    });
+    # Too many items attached to biblio
+    my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
+    my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
+    my $form_stage_two_items = $request->check_out({
+        stage     => 'form',
+        item_type => $itemtype->itemtype,
+    });
+    is_deeply($form_stage_two_items->{value}->{errors}, {
+        itemcount => 1
+    });
+
+    # Delete the items we created, so we can test that we can create one
+    $item1->delete;
+    $item2->delete;
+
+    # We need to mock the user environment for AddIssue
+    t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
+    #
+
+    # First we pass bad parameters to the item creation to test we're
+    # catching the failure of item creation
+    my $form_stage_bad_branchcode;
+    warning_like {
+        $form_stage_bad_branchcode = $request->check_out({
+            stage     => 'form',
+            item_type => $itemtype->itemtype,
+            branchcode => '---'
+        });
+    } qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/,
+    "Item creation fails on bad parameters";
+
+    is_deeply($form_stage_bad_branchcode->{value}->{errors}, {
+        item_creation => 1
+    },"We get expected failure of item creation");
+
+    # Now create a proper item
+    my $form_stage_good_branchcode = $request->check_out({
+        stage      => 'form',
+        item_type  => $itemtype->itemtype,
+        branchcode => $library->branchcode
+    });
+    # By default, this item should not be loanable, so check that we're
+    # informed of that fact
+    is_deeply(
+        $form_stage_good_branchcode->{value}->{check_out_errors},
+        {
+            error => {
+                NOT_FOR_LOAN => 1,
+                itemtype_notforloan => $itemtype->itemtype
+            }
+        },
+        "We get expected error on notforloan of item"
+    );
+    # Delete the item that was created
+    $biblio->items->delete;
+    # Now create an itemtype that is loanable
+    my $itemtype_loanable = $builder->build_object({
+        class => 'Koha::ItemTypes',
+        value => {
+            notforloan => 0
+        }
+    });
+    # We need to mock the user environment for AddIssue
+    t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
+    my $form_stage_loanable = $request->check_out({
+        stage      => 'form',
+        item_type  => $itemtype_loanable->itemtype,
+        branchcode => $library->branchcode
+    });
+    is($form_stage_loanable->{stage}, 'done_check_out');
+    isa_ok($patron->checkouts, 'Koha::Checkouts');
+    is($patron->checkouts->count, 1);
+    is($request->status, 'CHK');
+
+    $schema->storage->txn_rollback;
+};
+
 subtest 'Checking Limits' => sub {
 
     plan tests => 30;
@@ -791,58 +1220,114 @@ subtest 'Checking Limits' => sub {
     $schema->storage->txn_rollback;
 };
 
-subtest 'TO_JSON() tests' => sub {
+subtest 'Custom statuses' => sub {
 
-    plan tests => 10;
+    plan tests => 3;
 
-    my $illreqmodule = Test::MockModule->new('Koha::Illrequest');
+    $schema->storage->txn_begin;
 
-    # Mock ->capabilities
-    $illreqmodule->mock( 'capabilities', sub { return 'capable'; } );
+    my $cat = Koha::AuthorisedValueCategories->search(
+        {
+            category_name => 'ILLSTATUS'
+        }
+    );
 
-    # Mock ->metadata
-    $illreqmodule->mock( 'metadata', sub { return 'metawhat?'; } );
+    if ($cat->count == 0) {
+        $cat  = $builder->build_object(
+            {
+                class => 'Koha::AuthorisedValueCategory',
+                value => {
+                    category_name => 'ILLSTATUS'
+                }
+            }
+        );
+    };
+
+    my $av = $builder->build_object(
+        {
+            class => 'Koha::AuthorisedValues',
+            value => {
+                category => 'ILLSTATUS'
+            }
+        }
+    );
+
+    is($av->category, 'ILLSTATUS',
+       "Successfully created authorised value for custom status");
+
+    my $ill_req = $builder->build_object(
+        {
+            class => 'Koha::Illrequests',
+            value => {
+                status_alias => $av->authorised_value
+            }
+        }
+    );
+    isa_ok($ill_req->statusalias, 'Koha::AuthorisedValue',
+           "statusalias correctly returning Koha::AuthorisedValue object");
+
+    $ill_req->status("COMP");
+    is($ill_req->statusalias, undef,
+        "Koha::Illrequest->status overloading resetting status_alias");
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'Checking in hook' => sub {
+
+    plan tests => 2;
 
     $schema->storage->txn_begin;
 
-    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
-    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
-    my $illreq  = $builder->build_object(
+    # Build infrastructure
+    my $backend = Test::MockObject->new;
+    $backend->set_isa('Koha::Illbackends::Mock');
+    $backend->set_always('name', 'Mock');
+
+    my $config = Test::MockObject->new;
+    $config->set_always('backend_dir', "/tmp");
+
+    my $item   = $builder->build_sample_item();
+    my $patron = $builder->build_object({ class => 'Koha::Patrons' });
+
+    t::lib::Mocks::mock_userenv(
+        {
+            patron     => $patron,
+            branchcode => $patron->branchcode
+        }
+    );
+
+    my $illrq = $builder->build_object(
         {
             class => 'Koha::Illrequests',
             value => {
-                branchcode     => $library->branchcode,
-                borrowernumber => $patron->borrowernumber
+                biblio_id => $item->biblio->biblionumber,
+                status    => 'NEW'
             }
         }
     );
-    my $illreq_json = $illreq->TO_JSON;
-    is( $illreq_json->{patron},
-        undef, '%embed not passed, no \'patron\' attribute' );
-    is( $illreq_json->{metadata},
-        undef, '%embed not passed, no \'metadata\' attribute' );
-    is( $illreq_json->{capabilities},
-        undef, '%embed not passed, no \'capabilities\' attribute' );
-    is( $illreq_json->{branch},
-        undef, '%embed not passed, no \'branch\' attribute' );
-
-    $illreq_json = $illreq->TO_JSON(
-        { patron => 1, metadata => 1, capabilities => 1, branch => 1 } );
-    is( $illreq_json->{patron}->{firstname},
-        $patron->firstname,
-        '%embed passed, \'patron\' attribute correct (firstname)' );
-    is( $illreq_json->{patron}->{surname},
-        $patron->surname,
-        '%embed passed, \'patron\' attribute correct (surname)' );
-    is( $illreq_json->{patron}->{cardnumber},
-        $patron->cardnumber,
-        '%embed passed, \'patron\' attribute correct (cardnumber)' );
-    is( $illreq_json->{metadata},
-        'metawhat?', '%embed passed, \'metadata\' attribute correct' );
-    is( $illreq_json->{capabilities},
-        'capable', '%embed passed, \'capabilities\' attribute correct' );
-    is( $illreq_json->{branch}->{branchcode},
-        $library->branchcode, '%embed not passed, no \'branch\' attribute' );
+
+    $illrq->_config($config);
+    $illrq->_backend($backend);
+
+    t::lib::Mocks::mock_preference('CirculateILL', 1);
+
+    # Add an issue
+    AddIssue( $patron->unblessed, $item->barcode );
+    # Make the item withdrawn so checking-in is rejected
+    t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', 1);
+    $item->set({ withdrawn => 1 })->store;
+    AddReturn( $item->barcode, $patron->branchcode );
+    # refresh request
+    $illrq->discard_changes;
+    isnt( $illrq->status, 'RET' );
+
+    # allow the check-in
+    $item->set({ withdrawn => 0 })->store;
+    AddReturn( $item->barcode, $patron->branchcode );
+    # refresh request
+    $illrq->discard_changes;
+    is( $illrq->status, 'RET' );
 
     $schema->storage->txn_rollback;
 };