Bug 35581: Koha::Illrequest::SupplierUpdate* -> Koha::ILL::Request::SupplierUpdate*
[koha.git] / t / db_dependent / Illrequests.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use File::Basename qw/basename/;
21
22 use C4::Circulation qw( AddIssue AddReturn );
23
24 use Koha::Database;
25 use Koha::ILL::Request::Attributes;
26 use Koha::ILL::Request::Config;
27 use Koha::Biblios;
28 use Koha::Patrons;
29 use Koha::ItemTypes;
30 use Koha::Items;
31 use Koha::Libraries;
32 use Koha::Patron::MessagePreference::Attributes;
33 use Koha::Notice::Templates;
34 use Koha::AuthorisedValueCategories;
35 use Koha::AuthorisedValues;
36 use t::lib::Mocks;
37 use t::lib::TestBuilder;
38 use Test::MockObject;
39 use Test::MockModule;
40 use Test::Exception;
41 use Test::Deep qw/ cmp_deeply ignore /;
42 use Test::Warn;
43
44 use Test::More tests => 15;
45
46 my $schema = Koha::Database->new->schema;
47 my $builder = t::lib::TestBuilder->new;
48 use_ok('Koha::Illrequest');
49 use_ok('Koha::Illrequests');
50
51 subtest 'Basic object tests' => sub {
52
53     plan tests => 24;
54
55     $schema->storage->txn_begin;
56
57     Koha::Illrequests->search->delete;
58     my $illrq = $builder->build({ source => 'Illrequest' });
59     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
60
61     isa_ok($illrq_obj, 'Koha::Illrequest',
62            "Correctly create and load an illrequest object.");
63     isa_ok($illrq_obj->_config, 'Koha::ILL::Request::Config',
64            "Created a config object as part of Illrequest creation.");
65
66     is($illrq_obj->illrequest_id, $illrq->{illrequest_id},
67        "Illrequest_id getter works.");
68     is($illrq_obj->borrowernumber, $illrq->{borrowernumber},
69        "Borrowernumber getter works.");
70     is($illrq_obj->biblio_id, $illrq->{biblio_id},
71        "Biblio_Id getter works.");
72     is($illrq_obj->branchcode, $illrq->{branchcode},
73        "Branchcode getter works.");
74     is($illrq_obj->status, $illrq->{status},
75        "Status getter works.");
76     is($illrq_obj->placed, $illrq->{placed},
77        "Placed getter works.");
78     is($illrq_obj->replied, $illrq->{replied},
79        "Replied getter works.");
80     is($illrq_obj->updated, $illrq->{updated},
81        "Updated getter works.");
82     is($illrq_obj->completed, $illrq->{completed},
83        "Completed getter works.");
84     is($illrq_obj->medium, $illrq->{medium},
85        "Medium getter works.");
86     is($illrq_obj->accessurl, $illrq->{accessurl},
87        "Accessurl getter works.");
88     is($illrq_obj->cost, $illrq->{cost},
89        "Cost getter works.");
90     is($illrq_obj->price_paid, $illrq->{price_paid},
91        "Price_paid getter works.");
92     is($illrq_obj->notesopac, $illrq->{notesopac},
93        "Notesopac getter works.");
94     is($illrq_obj->notesstaff, $illrq->{notesstaff},
95        "Notesstaff getter works.");
96     is($illrq_obj->orderid, $illrq->{orderid},
97        "Orderid getter works.");
98     is($illrq_obj->backend, $illrq->{backend},
99        "Backend getter works.");
100
101     is($illrq_obj->get_type, undef,
102         'get_type() returns undef if no type is set');
103     $builder->build({
104         source => 'Illrequestattribute',
105         value  => {
106             illrequest_id => $illrq_obj->illrequest_id,
107             type => 'type',
108             value => 'book'
109         }
110     });
111     is($illrq_obj->get_type, 'book',
112         'get_type() returns correct type if set');
113
114     isnt($illrq_obj->status, 'COMP',
115          "ILL is not currently marked complete.");
116     $illrq_obj->mark_completed;
117     is($illrq_obj->status, 'COMP',
118        "ILL is now marked complete.");
119
120     $illrq_obj->delete;
121
122     is(Koha::Illrequests->search->count, 0,
123        "No illrequest found after delete.");
124
125     $schema->storage->txn_rollback;
126 };
127
128 subtest 'store borrowernumber change also updates holds' => sub {
129     plan tests => 5;
130
131     $schema->storage->txn_begin;
132
133     Koha::Illrequests->search->delete;
134
135     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
136     my $other_patron = $builder->build_object({ class => 'Koha::Patrons' });
137     my $biblio = $builder->build_object({ class => 'Koha::Biblios' });
138
139     my $request = $builder->build_object({
140         class => 'Koha::Illrequests',
141         value => {
142             borrowernumber => $patron->borrowernumber,
143             biblio_id => $biblio->biblionumber,
144         }
145     });
146     $builder->build({
147         source => 'Reserve',
148         value => {
149             borrowernumber => $patron->borrowernumber,
150             biblionumber => $request->biblio_id
151         }
152     });
153
154     my $hold = Koha::Holds->find({
155         biblionumber => $request->biblio_id,
156         borrowernumber => $request->borrowernumber,
157     });
158
159     is( $hold->borrowernumber, $request->borrowernumber,
160        'before change, original borrowernumber found' );
161
162     $request->borrowernumber( $other_patron->borrowernumber )->store;
163
164     # reload changes
165     $hold->discard_changes;
166
167     is( $hold->borrowernumber, $other_patron->borrowernumber,
168        'after change, changed borrowernumber found in holds' );
169
170     is( $request->borrowernumber, $other_patron->borrowernumber,
171        'after change, changed borrowernumber found in illrequests' );
172
173     my $new_request = Koha::Illrequest->new({
174         biblio_id => $biblio->biblionumber,
175         branchcode => $patron->branchcode,
176     })->borrowernumber( $patron->borrowernumber )->store;
177
178     is( $new_request->borrowernumber, $patron->borrowernumber,
179        'Koha::Illrequest->new()->store() works as expected');
180
181     my $new_holds_found = Koha::Holds->search({
182         biblionumber => $new_request->biblio_id,
183         borrowernumber => $new_request->borrowernumber,
184     })->count;
185
186     is( $new_holds_found, 0, 'no holds found with new()->store()' );
187
188     $schema->storage->txn_rollback;
189
190 };
191
192 subtest 'Working with related objects' => sub {
193
194     plan tests => 6;
195
196     $schema->storage->txn_begin;
197
198     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
199     my $illrq  = $builder->build_object(
200         {   class => 'Koha::Illrequests',
201             value => { borrowernumber => $patron->id, biblio_id => undef }
202         }
203     );
204
205     isa_ok( $illrq->patron, 'Koha::Patron', "OK accessing related patron." );
206
207     $builder->build(
208         {   source => 'Illrequestattribute',
209             value  => { illrequest_id => $illrq->illrequest_id, type => 'X' }
210         }
211     );
212     $builder->build(
213         {   source => 'Illrequestattribute',
214             value  => { illrequest_id => $illrq->illrequest_id, type => 'Y' }
215         }
216     );
217     $builder->build(
218         {   source => 'Illrequestattribute',
219             value  => { illrequest_id => $illrq->illrequest_id, type => 'Z' }
220         }
221     );
222
223     my $rs = Koha::ILL::Request::Attributes->search( { illrequest_id => $illrq->id } );
224
225     is( $illrq->extended_attributes->count,
226         $rs->count, "Fetching expected number of Illrequestattributes for our request." );
227
228     is( $illrq->biblio, undef, "->biblio returns undef if no biblio" );
229     my $biblio  = $builder->build_object( { class => 'Koha::Biblios' } );
230     my $req_bib = $builder->build_object(
231         {   class => 'Koha::Illrequests',
232             value => { biblio_id => $biblio->biblionumber }
233         }
234     );
235     isa_ok( $req_bib->biblio, 'Koha::Biblio', "OK accessing related biblio" );
236
237     $illrq->delete;
238     is( $rs->count, 0, "Correct number of illrequestattributes after delete." );
239
240     isa_ok( Koha::Patrons->find( $patron->id ), 'Koha::Patron', "Borrower was not deleted after illrq delete." );
241
242     $schema->storage->txn_rollback;
243 };
244
245 subtest 'Status Graph tests' => sub {
246
247     plan tests => 6;
248
249     $schema->storage->txn_begin;
250
251     my $illrq = $builder->build({source => 'Illrequest'});
252     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
253
254     # _core_status_graph tests: it's just a constant, so here we just make
255     # sure it returns a hashref.
256     is(ref $illrq_obj->_core_status_graph, "HASH",
257        "_core_status_graph returns a hash.");
258
259     # _status_graph_union: let's try different merge operations.
260     # Identity operation
261     is_deeply(
262         $illrq_obj->_status_graph_union($illrq_obj->_core_status_graph, {}),
263         $illrq_obj->_core_status_graph,
264         "core_status_graph + null = core_status_graph"
265     );
266
267     # Simple addition
268     is_deeply(
269         $illrq_obj->_status_graph_union({}, $illrq_obj->_core_status_graph),
270         $illrq_obj->_core_status_graph,
271         "null + core_status_graph = core_status_graph"
272     );
273
274     # Correct merge behaviour
275     is_deeply(
276         $illrq_obj->_status_graph_union({
277             REQ => {
278                 prev_actions   => [ ],
279                 id             => 'REQ',
280                 next_actions   => [ ],
281             },
282         }, {
283             QER => {
284                 prev_actions   => [ 'REQ' ],
285                 id             => 'QER',
286                 next_actions   => [ 'REQ' ],
287             },
288         }),
289         {
290             REQ => {
291                 prev_actions   => [ 'QER' ],
292                 id             => 'REQ',
293                 next_actions   => [ 'QER' ],
294             },
295             QER => {
296                 prev_actions   => [ 'REQ' ],
297                 id             => 'QER',
298                 next_actions   => [ 'REQ' ],
299             },
300         },
301         "REQ atom + linking QER = cyclical status graph"
302     );
303
304     # Create a new node, with no prev_actions and no next_actions. This should
305     # protect us against regressions related to bug 22280.
306     my $new_node = {
307         TEST => {
308             prev_actions   => [ ],
309             id             => 'TEST',
310             next_actions   => [ ],
311         },
312     };
313     # Add the new node to the core_status_grpah
314     my $new_graph = $illrq_obj->_status_graph_union( $new_node, $illrq_obj->_core_status_graph);
315     # Compare the updated graph to the expected graph
316     # The structure we compare against here is just a copy of the structure found
317     # in Koha::Illrequest::_core_status_graph() + the new node we created above
318     cmp_deeply( $new_graph,
319         {
320         TEST => {
321             prev_actions   => [ ],
322             id             => 'TEST',
323             next_actions   => [ ],
324         },
325         NEW => {
326             prev_actions => [ ],                           # Actions containing buttons
327                                                            # leading to this status
328             id             => 'NEW',                       # ID of this status
329             name           => 'New request',               # UI name of this status
330             ui_method_name => 'New request',               # UI name of method leading
331                                                            # to this status
332             method         => 'create',                    # method to this status
333             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
334                                                            # requests with this status
335             ui_method_icon => 'fa-plus',                   # UI Style class
336         },
337         REQ => {
338             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
339             id             => 'REQ',
340             name           => 'Requested',
341             ui_method_name => 'Confirm request',
342             method         => 'confirm',
343             next_actions   => [ 'REQREV', 'COMP', 'CHK' ],
344             ui_method_icon => 'fa-check',
345         },
346         GENREQ => {
347             prev_actions   => [ 'NEW', 'REQREV' ],
348             id             => 'GENREQ',
349             name           => 'Requested from partners',
350             ui_method_name => 'Place request with partners',
351             method         => 'generic_confirm',
352             next_actions   => [ 'COMP', 'CHK', 'REQREV' ],
353             ui_method_icon => 'fa-paper-plane',
354         },
355         REQREV => {
356             prev_actions   => [ 'REQ', 'GENREQ' ],
357             id             => 'REQREV',
358             name           => 'Request reverted',
359             ui_method_name => 'Revert request',
360             method         => 'cancel',
361             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
362             ui_method_icon => 'fa-times',
363         },
364         QUEUED => {
365             prev_actions   => [ ],
366             id             => 'QUEUED',
367             name           => 'Queued request',
368             ui_method_name => 0,
369             method         => 0,
370             next_actions   => [ 'REQ', 'KILL' ],
371             ui_method_icon => 0,
372         },
373         CANCREQ => {
374             prev_actions   => [ 'NEW' ],
375             id             => 'CANCREQ',
376             name           => 'Cancellation requested',
377             ui_method_name => 0,
378             method         => 0,
379             next_actions   => [ 'KILL', 'REQ' ],
380             ui_method_icon => 0,
381         },
382         COMP => {
383             prev_actions   => [ 'REQ' ],
384             id             => 'COMP',
385             name           => 'Completed',
386             ui_method_name => 'Mark completed',
387             method         => 'mark_completed',
388             next_actions   => [ 'CHK' ],
389             ui_method_icon => 'fa-check',
390         },
391         KILL => {
392             prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
393             id             => 'KILL',
394             name           => 0,
395             ui_method_name => 'Delete request',
396             method         => 'delete',
397             next_actions   => [ ],
398             ui_method_icon => 'fa-trash',
399         },
400         CHK => {
401             prev_actions   => [ 'REQ', 'GENREQ', 'COMP' ],
402             id             => 'CHK',
403             name           => 'Checked out',
404             ui_method_name => 'Check out',
405             needs_prefs    => [ 'CirculateILL' ],
406             needs_perms    => [ 'user_circulate_circulate_remaining_permissions' ],
407             needs_all      => ignore(),
408             method         => 'check_out',
409             next_actions   => [ ],
410             ui_method_icon => 'fa-upload',
411         },
412         RET => {
413             prev_actions   => [ 'CHK' ],
414             id             => 'RET',
415             name           => 'Returned to library',
416             ui_method_name => 'Check in',
417             method         => 'check_in',
418             next_actions   => [ 'COMP' ],
419             ui_method_icon => 'fa-download',
420         }
421     },
422         "new node + core_status_graph = bigger status graph"
423     ) || diag explain $new_graph;
424
425     # Create a duplicate node
426     my $dupe_node = {
427         REQ => {
428             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
429             id             => 'REQ',
430             name           => 'Requested',
431             ui_method_name => 'Confirm request dupe',
432             method         => 'confirm',
433             next_actions   => [ 'REQREV', 'COMP', 'CHK' ],
434             ui_method_icon => 'fa-check',
435         }
436     };
437     # Add the dupe node to the core_status_grpah
438     my $dupe_graph = $illrq_obj->_status_graph_union( $illrq_obj->_core_status_graph, $dupe_node);
439     # Compare the updated graph to the expected graph
440     # The structure we compare against here is just a copy of the structure found
441     # in Koha::Illrequest::_core_status_graph() + the new node we created above
442     cmp_deeply( $dupe_graph,
443         {
444         NEW => {
445             prev_actions => [ ],                           # Actions containing buttons
446                                                            # leading to this status
447             id             => 'NEW',                       # ID of this status
448             name           => 'New request',               # UI name of this status
449             ui_method_name => 'New request',               # UI name of method leading
450                                                            # to this status
451             method         => 'create',                    # method to this status
452             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
453                                                            # requests with this status
454             ui_method_icon => 'fa-plus',                   # UI Style class
455         },
456         REQ => {
457             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
458             id             => 'REQ',
459             name           => 'Requested',
460             ui_method_name => 'Confirm request dupe',
461             method         => 'confirm',
462             next_actions   => [ 'REQREV', 'COMP', 'CHK' ],
463             ui_method_icon => 'fa-check',
464         },
465         GENREQ => {
466             prev_actions   => [ 'NEW', 'REQREV' ],
467             id             => 'GENREQ',
468             name           => 'Requested from partners',
469             ui_method_name => 'Place request with partners',
470             method         => 'generic_confirm',
471             next_actions   => [ 'COMP', 'CHK', 'REQREV' ],
472             ui_method_icon => 'fa-paper-plane',
473         },
474         REQREV => {
475             prev_actions   => [ 'REQ', 'GENREQ' ],
476             id             => 'REQREV',
477             name           => 'Request reverted',
478             ui_method_name => 'Revert request',
479             method         => 'cancel',
480             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
481             ui_method_icon => 'fa-times',
482         },
483         QUEUED => {
484             prev_actions   => [ ],
485             id             => 'QUEUED',
486             name           => 'Queued request',
487             ui_method_name => 0,
488             method         => 0,
489             next_actions   => [ 'REQ', 'KILL' ],
490             ui_method_icon => 0,
491         },
492         CANCREQ => {
493             prev_actions   => [ 'NEW' ],
494             id             => 'CANCREQ',
495             name           => 'Cancellation requested',
496             ui_method_name => 0,
497             method         => 0,
498             next_actions   => [ 'KILL', 'REQ' ],
499             ui_method_icon => 0,
500         },
501         COMP => {
502             prev_actions   => [ 'REQ' ],
503             id             => 'COMP',
504             name           => 'Completed',
505             ui_method_name => 'Mark completed',
506             method         => 'mark_completed',
507             next_actions   => [ 'CHK' ],
508             ui_method_icon => 'fa-check',
509         },
510         KILL => {
511             prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
512             id             => 'KILL',
513             name           => 0,
514             ui_method_name => 'Delete request',
515             method         => 'delete',
516             next_actions   => [ ],
517             ui_method_icon => 'fa-trash',
518         },
519         CHK => {
520             prev_actions   => [ 'REQ', 'GENREQ', 'COMP' ],
521             id             => 'CHK',
522             name           => 'Checked out',
523             ui_method_name => 'Check out',
524             needs_prefs    => [ 'CirculateILL' ],
525             needs_perms    => [ 'user_circulate_circulate_remaining_permissions' ],
526             needs_all      => ignore(),
527             method         => 'check_out',
528             next_actions   => [ ],
529             ui_method_icon => 'fa-upload',
530         },
531         RET => {
532             prev_actions   => [ 'CHK' ],
533             id             => 'RET',
534             name           => 'Returned to library',
535             ui_method_name => 'Check in',
536             method         => 'check_in',
537             next_actions   => [ 'COMP' ],
538             ui_method_icon => 'fa-download',
539         }
540     },
541         "new node + core_status_graph = bigger status graph"
542     ) || diag explain $dupe_graph;
543
544     $schema->storage->txn_rollback;
545 };
546
547 subtest 'Backend testing (mocks)' => sub {
548
549     plan tests => 13;
550
551     $schema->storage->txn_begin;
552
553     # testing load_backend & available_backends requires that we have at least
554     # the Dummy plugin installed.  load_backend & available_backends don't
555     # currently have tests as a result.
556
557     t::lib::Mocks->mock_config('interlibrary_loans', { backend_dir => 'a_dir' }  );
558     my $backend = Test::MockObject->new;
559     $backend->set_isa('Koha::Illbackends::Mock');
560     $backend->set_always('name', 'Mock');
561
562     my $patron = $builder->build({ source => 'Borrower' });
563     my $illrq = $builder->build_object({
564         class => 'Koha::Illrequests',
565     });
566
567     $illrq->_backend($backend);
568
569     isa_ok($illrq->_backend, 'Koha::Illbackends::Mock',
570            "OK accessing mocked backend.");
571
572     # _backend_capability tests:
573     # We need to test whether this optional feature of a mocked backend
574     # behaves as expected.
575     # 3 scenarios: feature not implemented, feature implemented, but requested
576     # capability is not provided by backend, & feature is implemented &
577     # capability exists.  This method can be used to implement custom backend
578     # functionality, such as unmediated in the BLDSS backend (also see
579     # bugzilla 18837).
580     $backend->set_always('capabilities', undef);
581     is($illrq->_backend_capability('Test'), 0,
582        "0 returned on Mock not implementing capabilities.");
583
584     $backend->set_always('capabilities', 0);
585     is($illrq->_backend_capability('Test'), 0,
586        "0 returned on Mock not implementing Test capability.");
587
588     $backend->set_always('capabilities', sub { return 'bar'; } );
589     is($illrq->_backend_capability('Test'), 'bar',
590        "'bar' returned on Mock implementing Test capability.");
591
592     # metadata test: we need to be sure that we return the arbitrary values
593     # from the backend.
594     $backend->mock(
595         'metadata',
596         sub {
597             my ( $self, $rq ) = @_;
598             return {
599                 ID => $rq->illrequest_id,
600                 Title => $rq->patron->borrowernumber
601             }
602         }
603     );
604
605     is_deeply(
606         $illrq->metadata,
607         {
608             ID => $illrq->illrequest_id,
609             Title => $illrq->patron->borrowernumber
610         },
611         "Test metadata."
612     );
613
614     # capabilities:
615
616     # No backend graph extension
617     $backend->set_always('status_graph', {});
618     is_deeply($illrq->capabilities('COMP'),
619               {
620                   prev_actions   => [ 'REQ' ],
621                   id             => 'COMP',
622                   name           => 'Completed',
623                   ui_method_name => 'Mark completed',
624                   method         => 'mark_completed',
625                   next_actions   => [ 'CHK' ],
626                   ui_method_icon => 'fa-check',
627               },
628               "Dummy status graph for COMP.");
629     is($illrq->capabilities('UNKNOWN'), undef,
630        "Dummy status graph for UNKNOWN.");
631     is_deeply($illrq->capabilities(),
632               $illrq->_core_status_graph,
633               "Dummy full status graph.");
634     # Simple backend graph extension
635     $backend->set_always('status_graph',
636                          {
637                              QER => {
638                                  prev_actions   => [ 'REQ' ],
639                                  id             => 'QER',
640                                  next_actions   => [ 'REQ' ],
641                              },
642                          });
643     is_deeply($illrq->capabilities('QER'),
644               {
645                   prev_actions   => [ 'REQ' ],
646                   id             => 'QER',
647                   next_actions   => [ 'REQ' ],
648               },
649               "Simple status graph for QER.");
650     is($illrq->capabilities('UNKNOWN'), undef,
651        "Simple status graph for UNKNOWN.");
652     is_deeply($illrq->capabilities(),
653               $illrq->_status_graph_union(
654                   $illrq->_core_status_graph,
655                   {
656                       QER => {
657                           prev_actions   => [ 'REQ' ],
658                           id             => 'QER',
659                           next_actions   => [ 'REQ' ],
660                       },
661                   }
662               ),
663               "Simple full status graph.");
664
665     # custom_capability:
666
667     # No backend graph extension
668     $backend->set_always('status_graph', {});
669     is($illrq->custom_capability('unknown', {}), 0,
670        "Unknown candidate.");
671
672     # Simple backend graph extension
673     $backend->set_always('status_graph',
674                          {
675                              ID => {
676                                  prev_actions   => [ 'REQ' ],
677                                  id             => 'ID',
678                                  method         => 'identity',
679                                  next_actions   => [ 'REQ' ],
680                              },
681                          });
682     $backend->mock('identity',
683                    sub { my ( $self, $params ) = @_; return $params->{other}; });
684     is($illrq->custom_capability('identity', { test => 1, method => 'blah' })->{test}, 1,
685        "Resolve identity custom_capability");
686
687     $schema->storage->txn_rollback;
688 };
689
690
691 subtest 'Backend core methods' => sub {
692
693     plan tests => 20;
694
695     $schema->storage->txn_begin;
696
697     # Build infrastructure
698     my $backend = Test::MockObject->new;
699     $backend->set_isa('Koha::Illbackends::Mock');
700     $backend->set_always('name', 'Mock');
701     $backend->mock('capabilities', sub { return 'Mock'; });
702
703     my $config = Test::MockObject->new;
704     $config->set_always('backend_dir', "/tmp");
705     $config->set_always('getLimitRules',
706                         { default => { count => 0, method => 'active' } });
707
708     my $illrq = $builder->build_object({
709         class => 'Koha::Illrequests',
710         value => { backend => undef }
711     });
712     $illrq->_config($config);
713
714     # Test error conditions (no backend)
715     throws_ok { $illrq->load_backend; }
716         'Koha::Exceptions::Ill::InvalidBackendId',
717         'Exception raised correctly';
718
719     throws_ok { $illrq->load_backend(''); }
720         'Koha::Exceptions::Ill::InvalidBackendId',
721         'Exception raised correctly';
722
723     # Now load the mocked backend
724     $illrq->_backend($backend);
725
726     # expand_template:
727     is_deeply($illrq->expand_template({ test => 1, method => "bar" }),
728               {
729                   test => 1,
730                   method => "bar",
731                   template => "/tmp/Mock/intra-includes/bar.inc",
732                   opac_template => "/tmp/Mock/opac-includes/bar.inc",
733               },
734               "expand_template");
735
736     # backend_create
737     # we are testing simple cases.
738     $backend->set_series('create',
739                          { stage => 'bar', method => 'create' },
740                          { stage => 'commit', method => 'create' },
741                          { stage => 'commit', method => 'create' },
742                          { stage => 'commit', method => 'create' },
743                          { stage => 'commit', method => 'create' });
744     # Test non-commit
745     is_deeply($illrq->backend_create({test => 1}),
746               {
747                   stage => 'bar', method => 'create',
748                   template => "/tmp/Mock/intra-includes/create.inc",
749                   opac_template => "/tmp/Mock/opac-includes/create.inc",
750               },
751               "Backend create: arbitrary stage.");
752     # Test commit
753     is_deeply($illrq->backend_create({test => 1}),
754               {
755                   stage => 'commit', method => 'create', permitted => 0,
756                   template => "/tmp/Mock/intra-includes/create.inc",
757                   opac_template => "/tmp/Mock/opac-includes/create.inc",
758               },
759               "Backend create: arbitrary stage, not permitted.");
760     is($illrq->status, "QUEUED", "Backend create: queued if restricted.");
761     $config->set_always('getLimitRules', {});
762     $illrq->status('NEW');
763     is_deeply($illrq->backend_create({test => 1}),
764               {
765                   stage => 'commit', method => 'create', permitted => 1,
766                   template => "/tmp/Mock/intra-includes/create.inc",
767                   opac_template => "/tmp/Mock/opac-includes/create.inc",
768               },
769               "Backend create: arbitrary stage, permitted.");
770     is($illrq->status, "NEW", "Backend create: not-queued.");
771
772     # Test that enabling the unmediated workflow causes the backend's
773     # 'unmediated_ill' method to be called
774     t::lib::Mocks::mock_preference('ILLModuleUnmediated', '1');
775     $backend->mock(
776         'capabilities',
777         sub {
778             my ($self, $name) = @_;
779             if ($name eq 'unmediated_ill') {
780                 return sub {
781                     return { unmediated_ill => 1 };
782                 };
783             }
784         }
785     );
786     $illrq->status('NEW');
787     is_deeply(
788         $illrq->backend_create({test => 1}),
789         {
790             'opac_template' => '/tmp/Mock/opac-includes/.inc',
791             'template' => '/tmp/Mock/intra-includes/.inc',
792             'unmediated_ill' => 1
793         },
794         "Backend create: commit stage, permitted, ILLModuleUnmediated enabled."
795     );
796
797     # Test that disabling the unmediated workflow causes the backend's
798     # 'unmediated_ill' method to be NOT called
799     t::lib::Mocks::mock_preference('ILLModuleUnmediated', '0');
800     $illrq->status('NEW');
801     is_deeply(
802         $illrq->backend_create({test => 1}),
803         {
804             stage => 'commit', method => 'create', permitted => 1,
805             template => "/tmp/Mock/intra-includes/create.inc",
806             opac_template => "/tmp/Mock/opac-includes/create.inc",
807         },
808         "Backend create: commit stage, permitted, ILLModuleUnmediated disabled."
809     );
810
811     # backend_renew
812     $backend->set_series('renew', { stage => 'bar', method => 'renew' });
813     is_deeply($illrq->backend_renew({test => 1}),
814               {
815                   stage => 'bar', method => 'renew',
816                   template => "/tmp/Mock/intra-includes/renew.inc",
817                   opac_template => "/tmp/Mock/opac-includes/renew.inc",
818               },
819               "Backend renew: arbitrary stage.");
820
821     # backend_cancel
822     $backend->set_series('cancel', { stage => 'bar', method => 'cancel' });
823     is_deeply($illrq->backend_cancel({test => 1}),
824               {
825                   stage => 'bar', method => 'cancel',
826                   template => "/tmp/Mock/intra-includes/cancel.inc",
827                   opac_template => "/tmp/Mock/opac-includes/cancel.inc",
828               },
829               "Backend cancel: arbitrary stage.");
830
831     # backend_illview
832     $backend->set_series('illview', { stage => '', method => 'illview' });
833     is_deeply($illrq->backend_illview({test => 1}), 0,
834               "Backend illview optional method.");
835
836     # backend_update_status
837     $backend->set_series('update_status', { stage => 'bar', method => 'update_status' });
838     is_deeply($illrq->backend_update_status({test => 1}),
839               {
840                   stage => 'bar', method => 'update_status',
841                   template => "/tmp/Mock/intra-includes/update_status.inc",
842                   opac_template => "/tmp/Mock/opac-includes/update_status.inc",
843               },
844               "Backend update_status: arbitrary stage.");
845
846     # backend_confirm
847     $backend->set_series('confirm', { stage => 'bar', method => 'confirm' });
848     is_deeply($illrq->backend_confirm({test => 1}),
849               {
850                   stage => 'bar', method => 'confirm',
851                   template => "/tmp/Mock/intra-includes/confirm.inc",
852                   opac_template => "/tmp/Mock/opac-includes/confirm.inc",
853               },
854               "Backend confirm: arbitrary stage.");
855
856     # backend_get_update
857     $backend->mock(
858         'get_supplier_update',
859         sub {
860             my ( $self, $options ) = @_;
861             return $options;
862         }
863     );
864     $backend->mock('capabilities', sub { return sub { return 1; } });
865     is_deeply($illrq->backend_get_update({}), 1,
866               "Backend get_update method.");
867
868     $config->set_always('partner_code', "ILLTSTLIB");
869     $backend->set_always('metadata', { Test => "Foobar" });
870     my $illbrn = $builder->build({
871         source => 'Branch',
872         value => { branchemail => "", branchreplyto => "" }
873     });
874     my $partner1 = $builder->build({
875         source => 'Borrower',
876         value => { categorycode => "ILLTSTLIB" },
877     });
878     my $partner2 = $builder->build({
879         source => 'Borrower',
880         value => { categorycode => "ILLTSTLIB" },
881     });
882     my $gen_conf = $illrq->generic_confirm({
883         current_branchcode => $illbrn->{branchcode}
884     });
885     isnt(index($gen_conf->{value}->{draft}->{body}, $backend->metadata->{Test}), -1,
886          "Generic confirm: draft contains metadata."
887     );
888     is($gen_conf->{value}->{partners}->next->borrowernumber, $partner1->{borrowernumber},
889        "Generic cofnirm: partner 1 is correct."
890     );
891     is($gen_conf->{value}->{partners}->next->borrowernumber, $partner2->{borrowernumber},
892        "Generic confirm: partner 2 is correct."
893     );
894
895     dies_ok { $illrq->generic_confirm({
896         current_branchcode => $illbrn->{branchcode},
897         stage => 'draft'
898     }) }
899         "Generic confirm: missing to dies OK.";
900
901     $schema->storage->txn_rollback;
902 };
903
904
905 subtest 'Helpers' => sub {
906
907     plan tests => 25;
908
909     $schema->storage->txn_begin;
910
911     # Build infrastructure
912     my $backend = Test::MockObject->new;
913     $backend->set_isa('Koha::Illbackends::Mock');
914     $backend->set_always('name', 'Mock');
915     $backend->mock(
916         'metadata',
917         sub {
918             my ( $self, $rq ) = @_;
919             return {
920                 title => 'mytitle',
921                 author => 'myauthor'
922             }
923         }
924     );
925
926     my $config = Test::MockObject->new;
927     $config->set_always('backend_dir', "/tmp");
928
929     my $patron = $builder->build({
930         source => 'Borrower',
931         value => { categorycode => "A" }
932     });
933     # Create a mocked branch with no email addressed defined
934     my $illbrn = $builder->build({
935         source => 'Branch',
936         value => {
937             branchcode => 'HDE',
938             branchemail => "",
939             branchillemail => "",
940             branchreplyto => ""
941         }
942     });
943     my $illrq = $builder->build({
944         source => 'Illrequest',
945         value => { branchcode => "HDE", borrowernumber => $patron->{borrowernumber} }
946     });
947     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
948     $illrq_obj->_config($config);
949     $illrq_obj->_backend($backend);
950
951     #attach_processors
952     my $type = 'test_type_1';
953     my $name = 'test_name_1';
954     my $update = Test::MockObject->new;
955     $update->set_isa('Koha::ILL::Request::SupplierUpdate');
956     $update->{source_type} = $type;
957     $update->{source_name} = $name;
958     $update->{processors} = [];
959     $update->mock('attach_processor', sub {
960         my ( $self, $to_attach ) = @_;
961         push @{$self->{processors}}, $to_attach;
962     });
963     my $processor = Test::MockObject->new;
964     $processor->{target_source_type} = $type;
965     $processor->{target_source_name} = $name;
966     $illrq_obj->init_processors();
967     $illrq_obj->push_processor($processor);
968     $illrq_obj->attach_processors($update);
969     is_deeply(
970         scalar @{$update->{processors}},
971         1,
972         'attaching processors as appropriate works'
973     );
974
975     # getPrefix
976     $config->set_series('getPrefixes',
977                         { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
978                         { A => "ATEST", C => "CBAR", default => "DEFAULT" });
979     is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "HDE" }), "TEST",
980        "getPrefix: branch");
981     $config->set_series('getPrefixes',
982                         { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
983                         { A => "ATEST", C => "CBAR", default => "DEFAULT" });
984     is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
985        "getPrefix: default");
986     $config->set_always('getPrefixes', {});
987     is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
988        "getPrefix: the empty prefix");
989
990     # id_prefix
991     $config->set_series('getPrefixes',
992                         { HDE => "TEST", TSL => "BAR", default => "DEFAULT" },
993                         { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
994     is($illrq_obj->id_prefix, "TEST-", "id_prefix: branch");
995     $config->set_series('getPrefixes',
996                         { HDET => "TEST", TSLT => "BAR", default => "DEFAULT" },
997                         { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
998     is($illrq_obj->id_prefix, "", "id_prefix: default");
999
1000     # requires_moderation
1001     $illrq_obj->status('NEW')->store;
1002     is($illrq_obj->requires_moderation, undef, "requires_moderation: No.");
1003     $illrq_obj->status('CANCREQ')->store;
1004     is($illrq_obj->requires_moderation, 'CANCREQ', "requires_moderation: Yes.");
1005
1006     #send_patron_notice
1007     my $attr = Koha::Patron::MessagePreference::Attributes->find({ message_name => 'Ill_ready' });
1008     C4::Members::Messaging::SetMessagingPreference({
1009         borrowernumber => $patron->{borrowernumber},
1010         message_attribute_id => $attr->message_attribute_id,
1011         message_transport_types => ['email']
1012     });
1013     my $return_patron = $illrq_obj->send_patron_notice('ILL_PICKUP_READY');
1014     my $notice = $schema->resultset('MessageQueue')->search({
1015             letter_code => 'ILL_PICKUP_READY',
1016             message_transport_type => 'email',
1017             borrowernumber => $illrq_obj->borrowernumber
1018         })->next()->letter_code;
1019     is_deeply(
1020         $return_patron,
1021         { result => { success => ['email'], fail => [] } },
1022         "Correct return when notice created"
1023     );
1024     is($notice, 'ILL_PICKUP_READY' ,"Notice is correctly created");
1025
1026     # ill update notice, passes additional text parameter
1027     my $attr_update = Koha::Patron::MessagePreference::Attributes->find({ message_name => 'Ill_update' });
1028     C4::Members::Messaging::SetMessagingPreference({
1029         borrowernumber => $patron->{borrowernumber},
1030         message_attribute_id => $attr_update->message_attribute_id,
1031         message_transport_types => ['email']
1032     });
1033     my $return_patron_update = $illrq_obj->send_patron_notice('ILL_REQUEST_UPDATE', 'Some additional text');
1034     my $notice_update = $schema->resultset('MessageQueue')->search({
1035             letter_code => 'ILL_REQUEST_UPDATE',
1036             message_transport_type => 'email',
1037             borrowernumber => $illrq_obj->borrowernumber
1038         })->next()->letter_code;
1039     is_deeply(
1040         $return_patron_update,
1041         { result => { success => ['email'], fail => [] } },
1042         "Correct return when notice created"
1043     );
1044     is($notice_update, 'ILL_REQUEST_UPDATE' ,"Notice is correctly created");
1045
1046
1047     my $return_patron_fail = $illrq_obj->send_patron_notice();
1048     is_deeply(
1049         $return_patron_fail,
1050         { error => 'notice_no_type' },
1051         "Correct error when missing type"
1052     );
1053
1054     #send_staff_notice
1055     # Specify that no staff notices should be send
1056     t::lib::Mocks::mock_preference('ILLSendStaffNotices', '');
1057     my $return_staff_cancel_fail =
1058         $illrq_obj->send_staff_notice('ILL_REQUEST_CANCEL');
1059     is_deeply(
1060         $return_staff_cancel_fail,
1061         { error => 'notice_not_enabled' },
1062         "Does not send notices that are not enabled"
1063     );
1064     my $queue = $schema->resultset('MessageQueue')->search({
1065             letter_code => 'ILL_REQUEST_CANCEL'
1066         });
1067     is($queue->count, 0, "Notice is not queued");
1068
1069     # Specify that the cancel notice can be sent
1070     t::lib::Mocks::mock_preference('ILLSendStaffNotices', 'ILL_REQUEST_CANCEL');
1071     my $return_staff_cancel = $illrq_obj->send_staff_notice(
1072         'ILL_REQUEST_CANCEL'
1073     );
1074     is_deeply(
1075         $return_staff_cancel,
1076         { success => 'notice_queued' },
1077         "Correct return when staff notice created"
1078     );
1079     $queue = $schema->resultset('MessageQueue')->search({
1080             letter_code => 'ILL_REQUEST_CANCEL'
1081         });
1082     is($queue->count, 1, "Notice queued as expected");
1083
1084     my $return_staff_fail = $illrq_obj->send_staff_notice();
1085     is_deeply(
1086         $return_staff_fail,
1087         { error => 'notice_no_type' },
1088         "Correct error when missing type"
1089     );
1090     $queue = $schema->resultset('MessageQueue')->search({
1091             letter_code => 'ILL_REQUEST_CANCEL'
1092         });
1093     is($queue->count, 1, "Notice is not queued");
1094
1095     my $attribute = $builder->build({
1096         source => 'Illrequestattribute',
1097         value  => { illrequest_id => $illrq_obj->illrequest_id, type => 'pages', value => '42' }
1098     });
1099
1100     my $ILL_REQUEST_CANCEL_content =
1101         q{The patron for interlibrary loans request [% illrequest.illrequest_id %], with the following details, has requested cancellation of this ILL request:
1102
1103 [% ill_full_metadata %]
1104
1105 Attribute: Pages=[% illrequestattributes.pages %]
1106 };
1107     my $dbh = C4::Context->dbh;
1108     $dbh->do(q{UPDATE letter
1109         SET content=?
1110         WHERE code="ILL_REQUEST_CANCEL"
1111     }, undef, $ILL_REQUEST_CANCEL_content);
1112
1113     #get_notice
1114     my $not = $illrq_obj->get_notice({
1115         notice_code => 'ILL_REQUEST_CANCEL',
1116         transport   => 'email'
1117     });
1118
1119     # We test the properties of the hashref separately because the random
1120     # hash ordering of the metadata means we can't test the entire thing
1121     # with is_deeply
1122     ok(
1123         $not->{module} eq 'ill',
1124         'Correct module return from get_notice'
1125     );
1126     ok(
1127         $not->{name} eq 'ILL request cancelled',
1128         'Correct name return from get_notice'
1129     );
1130     ok(
1131         $not->{message_transport_type} eq 'email',
1132         'Correct message_transport_type return from get_notice'
1133     );
1134     ok(
1135         $not->{title} eq 'Interlibrary loan request cancelled',
1136         'Correct title return from get_notice'
1137     );
1138     $not->{content} =~ s/\s//g;
1139
1140     is(
1141         $not->{content},"Thepatronforinterlibraryloansrequest" . $illrq_obj->id . ",withthefollowingdetails,hasrequestedcancellationofthisILLrequest:-author:myauthor-title:mytitleAttribute:Pages=42",
1142         'Correct content returned from get_notice with metadata correctly ordered'
1143     );
1144
1145     $illrq_obj->append_to_note('Some text');
1146     like(
1147         $illrq_obj->notesstaff,
1148         qr/Some text$/,
1149         'appending to a note works'
1150     );
1151
1152     $schema->storage->txn_rollback;
1153 };
1154
1155
1156 subtest 'Censorship' => sub {
1157
1158     plan tests => 2;
1159
1160     $schema->storage->txn_begin;
1161
1162     # Build infrastructure
1163     my $backend = Test::MockObject->new;
1164     $backend->set_isa('Koha::Illbackends::Mock');
1165     $backend->set_always('name', 'Mock');
1166
1167     my $config = Test::MockObject->new;
1168     $config->set_always('backend_dir', "/tmp");
1169
1170     my $illrq = $builder->build({source => 'Illrequest'});
1171     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
1172     $illrq_obj->_config($config);
1173     $illrq_obj->_backend($backend);
1174
1175     $config->set_always('censorship', { censor_notes_staff => 1, censor_reply_date => 0 });
1176
1177     my $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564 });
1178     is_deeply($censor_out, { foo => 'bar', baz => 564, display_reply_date => 1 },
1179               "_censor: not OPAC, reply_date = 1");
1180
1181     $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564, opac => 1 });
1182     is_deeply($censor_out, {
1183         foo => 'bar', baz => 564, censor_notes_staff => 1,
1184         display_reply_date => 1, opac => 1
1185     }, "_censor: notes_staff = 0, reply_date = 0");
1186
1187     $schema->storage->txn_rollback;
1188 };
1189
1190 subtest 'Checking out' => sub {
1191
1192     plan tests => 17;
1193
1194     $schema->storage->txn_begin;
1195
1196     my $itemtype = $builder->build_object({
1197         class => 'Koha::ItemTypes',
1198         value => {
1199             notforloan => 1
1200         }
1201     });
1202     my $library = $builder->build_object({ class => 'Koha::Libraries' });
1203     my $biblio = $builder->build_sample_biblio();
1204     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1205     my $request = $builder->build_object({
1206         class => 'Koha::Illrequests',
1207         value => {
1208             borrowernumber => $patron->borrowernumber,
1209             biblio_id      => $biblio->biblionumber
1210         }
1211     });
1212
1213     # First test that calling check_out without a stage param returns
1214     # what's required to build the form
1215     my $no_stage = $request->check_out();
1216     is($no_stage->{method}, 'check_out');
1217     is($no_stage->{stage}, 'form');
1218     isa_ok($no_stage->{value}, 'HASH');
1219     isa_ok($no_stage->{value}->{itemtypes}, 'Koha::ItemTypes');
1220     isa_ok($no_stage->{value}->{libraries}, 'Koha::Libraries');
1221     isa_ok($no_stage->{value}->{statistical}, 'Koha::Patrons');
1222     isa_ok($no_stage->{value}->{biblio}, 'Koha::Biblio');
1223
1224     # Now test that form validation works when we supply a 'form' stage
1225     #
1226     # No item_type
1227     my $form_stage_missing_params = $request->check_out({
1228         stage => 'form'
1229     });
1230     is_deeply($form_stage_missing_params->{value}->{errors}, {
1231         item_type => 1
1232     });
1233     # inhouse passed but not a valid patron
1234     my $form_stage_bad_patron = $request->check_out({
1235         stage     => 'form',
1236         item_type => $itemtype->itemtype,
1237         inhouse   => 'I_DONT_EXIST'
1238     });
1239     is_deeply($form_stage_bad_patron->{value}->{errors}, {
1240         inhouse => 1
1241     });
1242     # Too many items attached to biblio
1243     my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1244     my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
1245     my $form_stage_two_items = $request->check_out({
1246         stage     => 'form',
1247         item_type => $itemtype->itemtype,
1248     });
1249     is_deeply($form_stage_two_items->{value}->{errors}, {
1250         itemcount => 1
1251     });
1252
1253     # Delete the items we created, so we can test that we can create one
1254     $item1->delete;
1255     $item2->delete;
1256
1257     # We need to mock the user environment for AddIssue
1258     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
1259     #
1260
1261     # First we pass bad parameters to the item creation to test we're
1262     # catching the failure of item creation
1263     my $form_stage_bad_branchcode;
1264     warning_like {
1265         $form_stage_bad_branchcode = $request->check_out({
1266             stage     => 'form',
1267             item_type => $itemtype->itemtype,
1268             branchcode => '---'
1269         });
1270     } qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/,
1271     "Item creation fails on bad parameters";
1272
1273     is_deeply($form_stage_bad_branchcode->{value}->{errors}, {
1274         item_creation => 1
1275     },"We get expected failure of item creation");
1276
1277     # Now create a proper item
1278     my $form_stage_good_branchcode = $request->check_out({
1279         stage      => 'form',
1280         item_type  => $itemtype->itemtype,
1281         branchcode => $library->branchcode
1282     });
1283     # By default, this item should not be loanable, so check that we're
1284     # informed of that fact
1285     is_deeply(
1286         $form_stage_good_branchcode->{value}->{check_out_errors},
1287         {
1288             error => {
1289                 NOT_FOR_LOAN => 1,
1290                 itemtype_notforloan => $itemtype->itemtype
1291             }
1292         },
1293         "We get expected error on notforloan of item"
1294     );
1295     # Delete the item that was created
1296     $biblio->items->delete;
1297     # Now create an itemtype that is loanable
1298     my $itemtype_loanable = $builder->build_object({
1299         class => 'Koha::ItemTypes',
1300         value => {
1301             notforloan => 0
1302         }
1303     });
1304     # We need to mock the user environment for AddIssue
1305     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
1306     my $form_stage_loanable = $request->check_out({
1307         stage      => 'form',
1308         item_type  => $itemtype_loanable->itemtype,
1309         branchcode => $library->branchcode
1310     });
1311     is($form_stage_loanable->{stage}, 'done_check_out');
1312     isa_ok($patron->checkouts, 'Koha::Checkouts');
1313     is($patron->checkouts->count, 1);
1314     is($request->status, 'CHK');
1315
1316     $schema->storage->txn_rollback;
1317 };
1318
1319 subtest 'Checking out with custom due date' => sub {
1320     plan tests => 1;
1321     $schema->storage->txn_begin;
1322
1323     my $library = $builder->build_object({ class => 'Koha::Libraries' });
1324     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1325     my $biblio = $builder->build_sample_biblio();
1326     my $itemtype_loanable = $builder->build_object({
1327         class => 'Koha::ItemTypes',
1328         value => {
1329             notforloan => 0
1330         }
1331     });
1332     my $request = $builder->build_object({
1333         class => 'Koha::Illrequests',
1334         value => {
1335             borrowernumber => $patron->borrowernumber,
1336             biblio_id      => $biblio->biblionumber
1337         }
1338     });
1339
1340     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
1341     my $duedate = '2099-05-21 00:00:00';
1342     my $form_stage_loanable = $request->check_out({
1343         stage      => 'form',
1344         item_type  => $itemtype_loanable->itemtype,
1345         branchcode => $library->branchcode,
1346         duedate    => $duedate
1347     });
1348     is($patron->checkouts->next->date_due, $duedate, "Custom due date was used");
1349
1350     $schema->storage->txn_rollback;
1351 };
1352
1353 subtest 'Checking Limits' => sub {
1354
1355     plan tests => 30;
1356
1357     $schema->storage->txn_begin;
1358
1359     # Build infrastructure
1360     my $backend = Test::MockObject->new;
1361     $backend->set_isa('Koha::Illbackends::Mock');
1362     $backend->set_always('name', 'Mock');
1363
1364     my $config = Test::MockObject->new;
1365     $config->set_always('backend_dir', "/tmp");
1366
1367     my $illrq = $builder->build({source => 'Illrequest'});
1368     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
1369     $illrq_obj->_config($config);
1370     $illrq_obj->_backend($backend);
1371
1372     # getLimits
1373     $config->set_series('getLimitRules',
1374                         { CPL => { count => 1, method => 'test' } },
1375                         { default => { count => 0, method => 'active' } });
1376     is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
1377               { count => 1, method => 'test' },
1378               "getLimits: by value.");
1379     is_deeply($illrq_obj->getLimits({ type => 'branch' }),
1380               { count => 0, method => 'active' },
1381               "getLimits: by default.");
1382     is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
1383               { count => -1, method => 'active' },
1384               "getLimits: by hard-coded.");
1385
1386     #_limit_counter
1387     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1388        1, "_limit_counter: Initial branch annual count.");
1389     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1390        1, "_limit_counter: Initial branch active count.");
1391     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1392        1, "_limit_counter: Initial patron annual count.");
1393     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1394        1, "_limit_counter: Initial patron active count.");
1395     $builder->build({
1396         source => 'Illrequest',
1397         value => {
1398             branchcode => $illrq_obj->branchcode,
1399             borrowernumber => $illrq_obj->borrowernumber,
1400         }
1401     });
1402     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1403        2, "_limit_counter: Add a qualifying request for branch annual count.");
1404     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1405        2, "_limit_counter: Add a qualifying request for branch active count.");
1406     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1407        2, "_limit_counter: Add a qualifying request for patron annual count.");
1408     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1409        2, "_limit_counter: Add a qualifying request for patron active count.");
1410     $builder->build({
1411         source => 'Illrequest',
1412         value => {
1413             branchcode => $illrq_obj->branchcode,
1414             borrowernumber => $illrq_obj->borrowernumber,
1415             placed => "2005-05-31",
1416         }
1417     });
1418     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1419        2, "_limit_counter: Add an out-of-date branch request.");
1420     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1421        3, "_limit_counter: Add a qualifying request for branch active count.");
1422     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1423        2, "_limit_counter: Add an out-of-date patron request.");
1424     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1425        3, "_limit_counter: Add a qualifying request for patron active count.");
1426     $builder->build({
1427         source => 'Illrequest',
1428         value => {
1429             branchcode => $illrq_obj->branchcode,
1430             borrowernumber => $illrq_obj->borrowernumber,
1431             status => "COMP",
1432         }
1433     });
1434     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1435        3, "_limit_counter: Add a qualifying request for branch annual count.");
1436     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1437        3, "_limit_counter: Add a completed request for branch active count.");
1438     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1439        3, "_limit_counter: Add a qualifying request for patron annual count.");
1440     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1441        3, "_limit_counter: Add a completed request for patron active count.");
1442
1443     # check_limits:
1444
1445     # We've tested _limit_counter, so all we need to test here is whether the
1446     # current counts of 3 for each work as they should against different
1447     # configuration declarations.
1448
1449     # No limits
1450     $config->set_always('getLimitRules', undef);
1451     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1452                                  librarycode => $illrq_obj->branchcode}),
1453        1, "check_limits: no configuration => no limits.");
1454
1455     # Branch tests
1456     $config->set_always('getLimitRules',
1457                         { $illrq_obj->branchcode => { count => 1, method => 'active' } });
1458     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1459                                  librarycode => $illrq_obj->branchcode}),
1460        0, "check_limits: branch active limit exceeded.");
1461     $config->set_always('getLimitRules',
1462                         { $illrq_obj->branchcode => { count => 1, method => 'annual' } });
1463     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1464                                  librarycode => $illrq_obj->branchcode}),
1465        0, "check_limits: branch annual limit exceeded.");
1466     $config->set_always('getLimitRules',
1467                         { $illrq_obj->branchcode => { count => 4, method => 'active' } });
1468     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1469                                  librarycode => $illrq_obj->branchcode}),
1470        1, "check_limits: branch active limit OK.");
1471     $config->set_always('getLimitRules',
1472                         { $illrq_obj->branchcode => { count => 4, method => 'annual' } });
1473     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1474                                  librarycode => $illrq_obj->branchcode}),
1475        1, "check_limits: branch annual limit OK.");
1476
1477     # Patron tests
1478     $config->set_always('getLimitRules',
1479                         { $illrq_obj->patron->categorycode => { count => 1, method => 'active' } });
1480     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1481                                  librarycode => $illrq_obj->branchcode}),
1482        0, "check_limits: patron category active limit exceeded.");
1483     $config->set_always('getLimitRules',
1484                         { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1485     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1486                                  librarycode => $illrq_obj->branchcode}),
1487        0, "check_limits: patron category annual limit exceeded.");
1488     $config->set_always('getLimitRules',
1489                         { $illrq_obj->patron->categorycode => { count => 4, method => 'active' } });
1490     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1491                                  librarycode => $illrq_obj->branchcode}),
1492        1, "check_limits: patron category active limit OK.");
1493     $config->set_always('getLimitRules',
1494                         { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1495     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1496                                  librarycode => $illrq_obj->branchcode}),
1497        1, "check_limits: patron category annual limit OK.");
1498
1499     # One rule cancels the other
1500     $config->set_series('getLimitRules',
1501                         # Branch rules allow request
1502                         { $illrq_obj->branchcode => { count => 4, method => 'active' } },
1503                         # Patron rule forbids it
1504                         { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1505     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1506                                  librarycode => $illrq_obj->branchcode}),
1507        0, "check_limits: patron category veto overrides branch OK.");
1508     $config->set_series('getLimitRules',
1509                         # Branch rules allow request
1510                         { $illrq_obj->branchcode => { count => 1, method => 'active' } },
1511                         # Patron rule forbids it
1512                         { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1513     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1514                                  librarycode => $illrq_obj->branchcode}),
1515        0, "check_limits: branch veto overrides patron category OK.");
1516
1517     $schema->storage->txn_rollback;
1518 };
1519
1520 subtest 'Custom statuses' => sub {
1521
1522     plan tests => 3;
1523
1524     $schema->storage->txn_begin;
1525
1526     my $cat = Koha::AuthorisedValueCategories->search(
1527         {
1528             category_name => 'ILL_STATUS_ALIAS'
1529         }
1530     );
1531
1532     if ($cat->count == 0) {
1533         $cat  = $builder->build_object(
1534             {
1535                 class => 'Koha::AuthorisedValueCategory',
1536                 value => {
1537                     category_name => 'ILL_STATUS_ALIAS'
1538                 }
1539             }
1540         );
1541     };
1542
1543     my $av = $builder->build_object(
1544         {
1545             class => 'Koha::AuthorisedValues',
1546             value => {
1547                 category => 'ILL_STATUS_ALIAS'
1548             }
1549         }
1550     );
1551
1552     is($av->category, 'ILL_STATUS_ALIAS',
1553        "Successfully created authorised value for custom status");
1554
1555     my $ill_req = $builder->build_object(
1556         {
1557             class => 'Koha::Illrequests',
1558             value => {
1559                 status_alias => $av->authorised_value
1560             }
1561         }
1562     );
1563     isa_ok($ill_req->statusalias, 'Koha::AuthorisedValue',
1564            "statusalias correctly returning Koha::AuthorisedValue object");
1565
1566     $ill_req->status("COMP");
1567     is($ill_req->statusalias, undef,
1568         "Koha::Illrequest->status overloading resetting status_alias");
1569
1570     $schema->storage->txn_rollback;
1571 };
1572
1573 subtest 'Checking in hook' => sub {
1574
1575     plan tests => 2;
1576
1577     $schema->storage->txn_begin;
1578
1579     # Build infrastructure
1580     my $backend = Test::MockObject->new;
1581     $backend->set_isa('Koha::Illbackends::Mock');
1582     $backend->set_always('name', 'Mock');
1583
1584     my $config = Test::MockObject->new;
1585     $config->set_always('backend_dir', "/tmp");
1586
1587     my $item   = $builder->build_sample_item();
1588     my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1589
1590     t::lib::Mocks::mock_userenv(
1591         {
1592             patron     => $patron,
1593             branchcode => $patron->branchcode
1594         }
1595     );
1596
1597     my $illrq = $builder->build_object(
1598         {
1599             class => 'Koha::Illrequests',
1600             value => {
1601                 biblio_id => $item->biblio->biblionumber,
1602                 status    => 'NEW'
1603             }
1604         }
1605     );
1606
1607     $illrq->_config($config);
1608     $illrq->_backend($backend);
1609
1610     t::lib::Mocks::mock_preference('CirculateILL', 1);
1611
1612     # Add an issue
1613     AddIssue( $patron, $item->barcode );
1614     # Make the item withdrawn so checking-in is rejected
1615     t::lib::Mocks::mock_preference('BlockReturnOfWithdrawnItems', 1);
1616     $item->set({ withdrawn => 1 })->store;
1617     AddReturn( $item->barcode, $patron->branchcode );
1618     # refresh request
1619     $illrq->discard_changes;
1620     isnt( $illrq->status, 'RET' );
1621
1622     # allow the check-in
1623     $item->set({ withdrawn => 0 })->store;
1624     AddReturn( $item->barcode, $patron->branchcode );
1625     # refresh request
1626     $illrq->discard_changes;
1627     is( $illrq->status, 'RET' );
1628
1629     $schema->storage->txn_rollback;
1630 };