Koha/t/db_dependent/Illrequests.t
Jonathan Druart 9d6d641d1f Bug 17600: Standardize our EXPORT_OK
On bug 17591 we discovered that there was something weird going on with
the way we export and use subroutines/modules.
This patch tries to standardize our EXPORT to use EXPORT_OK only.

That way we will need to explicitely define the subroutine we want to
use from a module.

This patch is a squashed version of:
Bug 17600: After export.pl
Bug 17600: After perlimport
Bug 17600: Manual changes
Bug 17600: Other manual changes after second perlimports run
Bug 17600: Fix tests

And a lot of other manual changes.

export.pl is a dirty script that can be found on bug 17600.

"perlimport" is:
git clone https://github.com/oalders/App-perlimports.git
cd App-perlimports/
cpanm --installdeps .
export PERL5LIB="$PERL5LIB:/kohadevbox/koha/App-perlimports/lib"
find . \( -name "*.pl" -o -name "*.pm" \) -exec perl App-perlimports/script/perlimports --inplace-edit --no-preserve-unused --filename {} \;

The ideas of this patch are to:
* use EXPORT_OK instead of EXPORT
* perltidy the EXPORT_OK list
* remove '&' before the subroutine names
* remove some uneeded use statements
* explicitely import the subroutines we need within the controllers or
modules

Note that the private subroutines (starting with _) should not be
exported (and not used from outside of the module except from tests).

EXPORT vs EXPORT_OK (from
https://www.thegeekstuff.com/2010/06/perl-exporter-examples/)
"""
Export allows to export the functions and variables of modules to user’s namespace using the standard import method. This way, we don’t need to create the objects for the modules to access it’s members.

@EXPORT and @EXPORT_OK are the two main variables used during export operation.

@EXPORT contains list of symbols (subroutines and variables) of the module to be exported into the caller namespace.

@EXPORT_OK does export of symbols on demand basis.
"""

If this patch caused a conflict with a patch you wrote prior to its
push:
* Make sure you are not reintroducing a "use" statement that has been
removed
* "$subroutine" is not exported by the C4::$MODULE module
means that you need to add the subroutine to the @EXPORT_OK list
* Bareword "$subroutine" not allowed while "strict subs"
means that you didn't imported the subroutine from the module:
  - use $MODULE qw( $subroutine list );
You can also use the fully qualified namespace: C4::$MODULE::$subroutine

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
2021-07-16 08:58:47 +02:00

1333 lines
48 KiB
Perl
Executable file

#!/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 3 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.
#
# 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 => 13;
my $schema = Koha::Database->new->schema;
my $builder = t::lib::TestBuilder->new;
use_ok('Koha::Illrequest');
use_ok('Koha::Illrequests');
subtest 'Basic object tests' => sub {
plan tests => 24;
$schema->storage->txn_begin;
Koha::Illrequests->search->delete;
my $illrq = $builder->build({ source => 'Illrequest' });
my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
isa_ok($illrq_obj, 'Koha::Illrequest',
"Correctly create and load an illrequest object.");
isa_ok($illrq_obj->_config, 'Koha::Illrequest::Config',
"Created a config object as part of Illrequest creation.");
is($illrq_obj->illrequest_id, $illrq->{illrequest_id},
"Illrequest_id getter works.");
is($illrq_obj->borrowernumber, $illrq->{borrowernumber},
"Borrowernumber getter works.");
is($illrq_obj->biblio_id, $illrq->{biblio_id},
"Biblio_Id getter works.");
is($illrq_obj->branchcode, $illrq->{branchcode},
"Branchcode getter works.");
is($illrq_obj->status, $illrq->{status},
"Status getter works.");
is($illrq_obj->placed, $illrq->{placed},
"Placed getter works.");
is($illrq_obj->replied, $illrq->{replied},
"Replied getter works.");
is($illrq_obj->updated, $illrq->{updated},
"Updated getter works.");
is($illrq_obj->completed, $illrq->{completed},
"Completed getter works.");
is($illrq_obj->medium, $illrq->{medium},
"Medium getter works.");
is($illrq_obj->accessurl, $illrq->{accessurl},
"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},
"Notesstaff getter works.");
is($illrq_obj->orderid, $illrq->{orderid},
"Orderid getter works.");
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;
is($illrq_obj->status, 'COMP',
"ILL is now marked complete.");
$illrq_obj->delete;
is(Koha::Illrequests->search->count, 0,
"No illrequest found after delete.");
$schema->storage->txn_rollback;
};
subtest 'Working with related objects' => sub {
plan tests => 7;
$schema->storage->txn_begin;
Koha::Illrequests->search->delete;
my $patron = $builder->build({ source => 'Borrower' });
my $illrq = $builder->build({
source => 'Illrequest',
value => { borrowernumber => $patron->{borrowernumber} }
});
my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
isa_ok($illrq_obj->patron, 'Koha::Patron',
"OK accessing related patron.");
$builder->build({
source => 'Illrequestattribute',
value => { illrequest_id => $illrq_obj->illrequest_id, type => 'X' }
});
$builder->build({
source => 'Illrequestattribute',
value => { illrequest_id => $illrq_obj->illrequest_id, type => 'Y' }
});
$builder->build({
source => 'Illrequestattribute',
value => { illrequest_id => $illrq_obj->illrequest_id, type => 'Z' }
});
is($illrq_obj->illrequestattributes->count, Koha::Illrequestattributes->search->count,
"Fetching expected number of Illrequestattributes for our request.");
my $illrq1 = $builder->build({ source => 'Illrequest' });
$builder->build({
source => 'Illrequestattribute',
value => { illrequest_id => $illrq1->{illrequest_id}, type => 'X' }
});
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.");
isa_ok(Koha::Patrons->find($patron->{borrowernumber}), 'Koha::Patron',
"Borrower was not deleted after illrq delete.");
$schema->storage->txn_rollback;
};
subtest 'Status Graph tests' => sub {
plan tests => 5;
$schema->storage->txn_begin;
my $illrq = $builder->build({source => 'Illrequest'});
my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
# _core_status_graph tests: it's just a constant, so here we just make
# sure it returns a hashref.
is(ref $illrq_obj->_core_status_graph, "HASH",
"_core_status_graph returns a hash.");
# _status_graph_union: let's try different merge operations.
# Identity operation
is_deeply(
$illrq_obj->_status_graph_union($illrq_obj->_core_status_graph, {}),
$illrq_obj->_core_status_graph,
"core_status_graph + null = core_status_graph"
);
# Simple addition
is_deeply(
$illrq_obj->_status_graph_union({}, $illrq_obj->_core_status_graph),
$illrq_obj->_core_status_graph,
"null + core_status_graph = core_status_graph"
);
# Correct merge behaviour
is_deeply(
$illrq_obj->_status_graph_union({
REQ => {
prev_actions => [ ],
id => 'REQ',
next_actions => [ ],
},
}, {
QER => {
prev_actions => [ 'REQ' ],
id => 'QER',
next_actions => [ 'REQ' ],
},
}),
{
REQ => {
prev_actions => [ 'QER' ],
id => 'REQ',
next_actions => [ 'QER' ],
},
QER => {
prev_actions => [ 'REQ' ],
id => 'QER',
next_actions => [ 'REQ' ],
},
},
"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;
};
subtest 'Backend testing (mocks)' => sub {
plan tests => 13;
$schema->storage->txn_begin;
# testing load_backend & available_backends requires that we have at least
# 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_object({
class => 'Koha::Illrequests',
});
$illrq->_backend($backend);
isa_ok($illrq->_backend, 'Koha::Illbackends::Mock',
"OK accessing mocked backend.");
# _backend_capability tests:
# We need to test whether this optional feature of a mocked backend
# behaves as expected.
# 3 scenarios: feature not implemented, feature implemented, but requested
# capability is not provided by backend, & feature is implemented &
# capability exists. This method can be used to implement custom backend
# functionality, such as unmediated in the BLDSS backend (also see
# bugzilla 18837).
$backend->set_always('capabilities', undef);
is($illrq->_backend_capability('Test'), 0,
"0 returned on Mock not implementing capabilities.");
$backend->set_always('capabilities', 0);
is($illrq->_backend_capability('Test'), 0,
"0 returned on Mock not implementing Test capability.");
$backend->set_always('capabilities', sub { return '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
# from the backend.
$backend->mock(
'metadata',
sub {
my ( $self, $rq ) = @_;
return {
ID => $rq->illrequest_id,
Title => $rq->patron->borrowernumber
}
}
);
is_deeply(
$illrq->metadata,
{
ID => $illrq->illrequest_id,
Title => $illrq->patron->borrowernumber
},
"Test metadata."
);
# capabilities:
# No backend graph extension
$backend->set_always('status_graph', {});
is_deeply($illrq->capabilities('COMP'),
{
prev_actions => [ 'REQ' ],
id => 'COMP',
name => 'Completed',
ui_method_name => 'Mark completed',
method => 'mark_completed',
next_actions => [ 'CHK' ],
ui_method_icon => 'fa-check',
},
"Dummy status graph for COMP.");
is($illrq->capabilities('UNKNOWN'), undef,
"Dummy status graph for UNKNOWN.");
is_deeply($illrq->capabilities(),
$illrq->_core_status_graph,
"Dummy full status graph.");
# Simple backend graph extension
$backend->set_always('status_graph',
{
QER => {
prev_actions => [ 'REQ' ],
id => 'QER',
next_actions => [ 'REQ' ],
},
});
is_deeply($illrq->capabilities('QER'),
{
prev_actions => [ 'REQ' ],
id => 'QER',
next_actions => [ 'REQ' ],
},
"Simple status graph for QER.");
is($illrq->capabilities('UNKNOWN'), undef,
"Simple status graph for UNKNOWN.");
is_deeply($illrq->capabilities(),
$illrq->_status_graph_union(
$illrq->_core_status_graph,
{
QER => {
prev_actions => [ 'REQ' ],
id => 'QER',
next_actions => [ 'REQ' ],
},
}
),
"Simple full status graph.");
# custom_capability:
# No backend graph extension
$backend->set_always('status_graph', {});
is($illrq->custom_capability('unknown', {}), 0,
"Unknown candidate.");
# Simple backend graph extension
$backend->set_always('status_graph',
{
ID => {
prev_actions => [ 'REQ' ],
id => 'ID',
method => 'identity',
next_actions => [ 'REQ' ],
},
});
$backend->mock('identity',
sub { my ( $self, $params ) = @_; return $params->{other}; });
is($illrq->custom_capability('identity', { test => 1, method => 'blah' })->{test}, 1,
"Resolve identity custom_capability");
$schema->storage->txn_rollback;
};
subtest 'Backend core methods' => sub {
plan tests => 18;
$schema->storage->txn_begin;
# Build infrastructure
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_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->expandTemplate({ test => 1, method => "bar" }),
{
test => 1,
method => "bar",
template => "/tmp/Mock/intra-includes/bar.inc",
opac_template => "/tmp/Mock/opac-includes/bar.inc",
},
"ExpandTemplate");
# backend_create
# we are testing simple cases.
$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 non-commit
is_deeply($illrq->backend_create({test => 1}),
{
stage => 'bar', method => 'create',
template => "/tmp/Mock/intra-includes/create.inc",
opac_template => "/tmp/Mock/opac-includes/create.inc",
},
"Backend create: arbitrary stage.");
# Test commit
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->status, "QUEUED", "Backend create: queued if restricted.");
$config->set_always('getLimitRules', {});
$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->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->backend_renew({test => 1}),
{
stage => 'bar', method => 'renew',
template => "/tmp/Mock/intra-includes/renew.inc",
opac_template => "/tmp/Mock/opac-includes/renew.inc",
},
"Backend renew: arbitrary stage.");
# backend_cancel
$backend->set_series('cancel', { stage => 'bar', method => 'cancel' });
is_deeply($illrq->backend_cancel({test => 1}),
{
stage => 'bar', method => 'cancel',
template => "/tmp/Mock/intra-includes/cancel.inc",
opac_template => "/tmp/Mock/opac-includes/cancel.inc",
},
"Backend cancel: arbitrary stage.");
# backend_update_status
$backend->set_series('update_status', { stage => 'bar', method => 'update_status' });
is_deeply($illrq->backend_update_status({test => 1}),
{
stage => 'bar', method => 'update_status',
template => "/tmp/Mock/intra-includes/update_status.inc",
opac_template => "/tmp/Mock/opac-includes/update_status.inc",
},
"Backend update_status: arbitrary stage.");
# backend_confirm
$backend->set_series('confirm', { stage => 'bar', method => 'confirm' });
is_deeply($illrq->backend_confirm({test => 1}),
{
stage => 'bar', method => 'confirm',
template => "/tmp/Mock/intra-includes/confirm.inc",
opac_template => "/tmp/Mock/opac-includes/confirm.inc",
},
"Backend confirm: arbitrary stage.");
$config->set_always('partner_code', "ILLTSTLIB");
$backend->set_always('metadata', { Test => "Foobar" });
my $illbrn = $builder->build({
source => 'Branch',
value => { branchemail => "", branchreplyto => "" }
});
my $partner1 = $builder->build({
source => 'Borrower',
value => { categorycode => "ILLTSTLIB" },
});
my $partner2 = $builder->build({
source => 'Borrower',
value => { categorycode => "ILLTSTLIB" },
});
my $gen_conf = $illrq->generic_confirm({
current_branchcode => $illbrn->{branchcode}
});
isnt(index($gen_conf->{value}->{draft}->{body}, $backend->metadata->{Test}), -1,
"Generic confirm: draft contains metadata."
);
is($gen_conf->{value}->{partners}->next->borrowernumber, $partner1->{borrowernumber},
"Generic cofnirm: partner 1 is correct."
);
is($gen_conf->{value}->{partners}->next->borrowernumber, $partner2->{borrowernumber},
"Generic confirm: partner 2 is correct."
);
dies_ok { $illrq->generic_confirm({
current_branchcode => $illbrn->{branchcode},
stage => 'draft'
}) }
"Generic confirm: missing to dies OK.";
$schema->storage->txn_rollback;
};
subtest 'Helpers' => sub {
plan tests => 20;
$schema->storage->txn_begin;
# Build infrastructure
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");
my $patron = $builder->build({
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 => "HDE", borrowernumber => $patron->{borrowernumber} }
});
my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
$illrq_obj->_config($config);
$illrq_obj->_backend($backend);
# getPrefix
$config->set_series('getPrefixes',
{ HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
{ A => "ATEST", C => "CBAR", default => "DEFAULT" });
is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "HDE" }), "TEST",
"getPrefix: branch");
$config->set_series('getPrefixes',
{ HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
{ A => "ATEST", C => "CBAR", default => "DEFAULT" });
is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
"getPrefix: default");
$config->set_always('getPrefixes', {});
is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
"getPrefix: the empty prefix");
# id_prefix
$config->set_series('getPrefixes',
{ 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',
{ HDET => "TEST", TSLT => "BAR", default => "DEFAULT" },
{ AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
is($illrq_obj->id_prefix, "", "id_prefix: default");
# requires_moderation
$illrq_obj->status('NEW')->store;
is($illrq_obj->requires_moderation, undef, "requires_moderation: No.");
$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;
};
subtest 'Censorship' => sub {
plan tests => 2;
$schema->storage->txn_begin;
# 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 $illrq = $builder->build({source => 'Illrequest'});
my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
$illrq_obj->_config($config);
$illrq_obj->_backend($backend);
$config->set_always('censorship', { censor_notes_staff => 1, censor_reply_date => 0 });
my $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564 });
is_deeply($censor_out, { foo => 'bar', baz => 564, display_reply_date => 1 },
"_censor: not OPAC, reply_date = 1");
$censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564, opac => 1 });
is_deeply($censor_out, {
foo => 'bar', baz => 564, censor_notes_staff => 1,
display_reply_date => 1, opac => 1
}, "_censor: notes_staff = 0, reply_date = 0");
$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;
$schema->storage->txn_begin;
# 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 $illrq = $builder->build({source => 'Illrequest'});
my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
$illrq_obj->_config($config);
$illrq_obj->_backend($backend);
# getLimits
$config->set_series('getLimitRules',
{ CPL => { count => 1, method => 'test' } },
{ default => { count => 0, method => 'active' } });
is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
{ count => 1, method => 'test' },
"getLimits: by value.");
is_deeply($illrq_obj->getLimits({ type => 'branch' }),
{ count => 0, method => 'active' },
"getLimits: by default.");
is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
{ count => -1, method => 'active' },
"getLimits: by hard-coded.");
#_limit_counter
is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1, "_limit_counter: Initial branch annual count.");
is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1, "_limit_counter: Initial branch active count.");
is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1, "_limit_counter: Initial patron annual count.");
is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1, "_limit_counter: Initial patron active count.");
$builder->build({
source => 'Illrequest',
value => {
branchcode => $illrq_obj->branchcode,
borrowernumber => $illrq_obj->borrowernumber,
}
});
is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
2, "_limit_counter: Add a qualifying request for branch annual count.");
is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
2, "_limit_counter: Add a qualifying request for branch active count.");
is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
2, "_limit_counter: Add a qualifying request for patron annual count.");
is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
2, "_limit_counter: Add a qualifying request for patron active count.");
$builder->build({
source => 'Illrequest',
value => {
branchcode => $illrq_obj->branchcode,
borrowernumber => $illrq_obj->borrowernumber,
placed => "2005-05-31",
}
});
is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
2, "_limit_counter: Add an out-of-date branch request.");
is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
3, "_limit_counter: Add a qualifying request for branch active count.");
is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
2, "_limit_counter: Add an out-of-date patron request.");
is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
3, "_limit_counter: Add a qualifying request for patron active count.");
$builder->build({
source => 'Illrequest',
value => {
branchcode => $illrq_obj->branchcode,
borrowernumber => $illrq_obj->borrowernumber,
status => "COMP",
}
});
is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
3, "_limit_counter: Add a qualifying request for branch annual count.");
is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
3, "_limit_counter: Add a completed request for branch active count.");
is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
3, "_limit_counter: Add a qualifying request for patron annual count.");
is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
3, "_limit_counter: Add a completed request for patron active count.");
# check_limits:
# We've tested _limit_counter, so all we need to test here is whether the
# current counts of 3 for each work as they should against different
# configuration declarations.
# No limits
$config->set_always('getLimitRules', undef);
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
1, "check_limits: no configuration => no limits.");
# Branch tests
$config->set_always('getLimitRules',
{ $illrq_obj->branchcode => { count => 1, method => 'active' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
0, "check_limits: branch active limit exceeded.");
$config->set_always('getLimitRules',
{ $illrq_obj->branchcode => { count => 1, method => 'annual' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
0, "check_limits: branch annual limit exceeded.");
$config->set_always('getLimitRules',
{ $illrq_obj->branchcode => { count => 4, method => 'active' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
1, "check_limits: branch active limit OK.");
$config->set_always('getLimitRules',
{ $illrq_obj->branchcode => { count => 4, method => 'annual' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
1, "check_limits: branch annual limit OK.");
# Patron tests
$config->set_always('getLimitRules',
{ $illrq_obj->patron->categorycode => { count => 1, method => 'active' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
0, "check_limits: patron category active limit exceeded.");
$config->set_always('getLimitRules',
{ $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
0, "check_limits: patron category annual limit exceeded.");
$config->set_always('getLimitRules',
{ $illrq_obj->patron->categorycode => { count => 4, method => 'active' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
1, "check_limits: patron category active limit OK.");
$config->set_always('getLimitRules',
{ $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
1, "check_limits: patron category annual limit OK.");
# One rule cancels the other
$config->set_series('getLimitRules',
# Branch rules allow request
{ $illrq_obj->branchcode => { count => 4, method => 'active' } },
# Patron rule forbids it
{ $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
0, "check_limits: patron category veto overrides branch OK.");
$config->set_series('getLimitRules',
# Branch rules allow request
{ $illrq_obj->branchcode => { count => 1, method => 'active' } },
# Patron rule forbids it
{ $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
is($illrq_obj->check_limits({patron => $illrq_obj->patron,
librarycode => $illrq_obj->branchcode}),
0, "check_limits: branch veto overrides patron category OK.");
$schema->storage->txn_rollback;
};
subtest 'Custom statuses' => sub {
plan tests => 3;
$schema->storage->txn_begin;
my $cat = Koha::AuthorisedValueCategories->search(
{
category_name => 'ILLSTATUS'
}
);
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;
# 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 => {
biblio_id => $item->biblio->biblionumber,
status => 'NEW'
}
}
);
$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;
};