Bug 26115: Remove leftover Carp::Always
[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 use Koha::Database;
22 use Koha::Illrequestattributes;
23 use Koha::Illrequest::Config;
24 use Koha::Biblios;
25 use Koha::Patrons;
26 use Koha::ItemTypes;
27 use Koha::Items;
28 use Koha::Libraries;
29 use Koha::AuthorisedValueCategories;
30 use Koha::AuthorisedValues;
31 use t::lib::Mocks;
32 use t::lib::TestBuilder;
33 use Test::MockObject;
34 use Test::MockModule;
35 use Test::Exception;
36 use Test::Deep qw/ cmp_deeply ignore /;
37 use Test::Warn;
38
39 use Test::More tests => 12;
40
41 my $schema = Koha::Database->new->schema;
42 my $builder = t::lib::TestBuilder->new;
43 use_ok('Koha::Illrequest');
44 use_ok('Koha::Illrequests');
45
46 subtest 'Basic object tests' => sub {
47
48     plan tests => 24;
49
50     $schema->storage->txn_begin;
51
52     Koha::Illrequests->search->delete;
53     my $illrq = $builder->build({ source => 'Illrequest' });
54     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
55
56     isa_ok($illrq_obj, 'Koha::Illrequest',
57            "Correctly create and load an illrequest object.");
58     isa_ok($illrq_obj->_config, 'Koha::Illrequest::Config',
59            "Created a config object as part of Illrequest creation.");
60
61     is($illrq_obj->illrequest_id, $illrq->{illrequest_id},
62        "Illrequest_id getter works.");
63     is($illrq_obj->borrowernumber, $illrq->{borrowernumber},
64        "Borrowernumber getter works.");
65     is($illrq_obj->biblio_id, $illrq->{biblio_id},
66        "Biblio_Id getter works.");
67     is($illrq_obj->branchcode, $illrq->{branchcode},
68        "Branchcode getter works.");
69     is($illrq_obj->status, $illrq->{status},
70        "Status getter works.");
71     is($illrq_obj->placed, $illrq->{placed},
72        "Placed getter works.");
73     is($illrq_obj->replied, $illrq->{replied},
74        "Replied getter works.");
75     is($illrq_obj->updated, $illrq->{updated},
76        "Updated getter works.");
77     is($illrq_obj->completed, $illrq->{completed},
78        "Completed getter works.");
79     is($illrq_obj->medium, $illrq->{medium},
80        "Medium getter works.");
81     is($illrq_obj->accessurl, $illrq->{accessurl},
82        "Accessurl getter works.");
83     is($illrq_obj->cost, $illrq->{cost},
84        "Cost getter works.");
85     is($illrq_obj->price_paid, $illrq->{price_paid},
86        "Price_paid getter works.");
87     is($illrq_obj->notesopac, $illrq->{notesopac},
88        "Notesopac getter works.");
89     is($illrq_obj->notesstaff, $illrq->{notesstaff},
90        "Notesstaff getter works.");
91     is($illrq_obj->orderid, $illrq->{orderid},
92        "Orderid getter works.");
93     is($illrq_obj->backend, $illrq->{backend},
94        "Backend getter works.");
95
96     is($illrq_obj->get_type, undef,
97         'get_type() returns undef if no type is set');
98     $builder->build({
99         source => 'Illrequestattribute',
100         value  => {
101             illrequest_id => $illrq_obj->illrequest_id,
102             type => 'type',
103             value => 'book'
104         }
105     });
106     is($illrq_obj->get_type, 'book',
107         'get_type() returns correct type if set');
108
109     isnt($illrq_obj->status, 'COMP',
110          "ILL is not currently marked complete.");
111     $illrq_obj->mark_completed;
112     is($illrq_obj->status, 'COMP',
113        "ILL is now marked complete.");
114
115     $illrq_obj->delete;
116
117     is(Koha::Illrequests->search->count, 0,
118        "No illrequest found after delete.");
119
120     $schema->storage->txn_rollback;
121 };
122
123 subtest 'Working with related objects' => sub {
124
125     plan tests => 7;
126
127     $schema->storage->txn_begin;
128
129     Koha::Illrequests->search->delete;
130
131     my $patron = $builder->build({ source => 'Borrower' });
132     my $illrq = $builder->build({
133         source => 'Illrequest',
134         value => { borrowernumber => $patron->{borrowernumber} }
135     });
136     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
137
138     isa_ok($illrq_obj->patron, 'Koha::Patron',
139            "OK accessing related patron.");
140
141     $builder->build({
142         source => 'Illrequestattribute',
143         value  => { illrequest_id => $illrq_obj->illrequest_id, type => 'X' }
144     });
145     $builder->build({
146         source => 'Illrequestattribute',
147         value  => { illrequest_id => $illrq_obj->illrequest_id, type => 'Y' }
148     });
149     $builder->build({
150         source => 'Illrequestattribute',
151         value  => { illrequest_id => $illrq_obj->illrequest_id, type => 'Z' }
152     });
153
154     is($illrq_obj->illrequestattributes->count, Koha::Illrequestattributes->search->count,
155        "Fetching expected number of Illrequestattributes for our request.");
156
157     my $illrq1 = $builder->build({ source => 'Illrequest' });
158     $builder->build({
159         source => 'Illrequestattribute',
160         value  => { illrequest_id => $illrq1->{illrequest_id}, type => 'X' }
161     });
162
163     is($illrq_obj->illrequestattributes->count + 1, Koha::Illrequestattributes->search->count,
164        "Fetching expected number of Illrequestattributes for our request.");
165
166     is($illrq_obj->biblio, undef, "->biblio returns undef if no biblio");
167     my $biblio = $builder->build_object({ class => 'Koha::Biblios' });
168     my $req_bib = $builder->build_object({
169         class => 'Koha::Illrequests',
170         value => {
171             biblio_id      => $biblio->biblionumber
172         }
173     });
174     isa_ok($req_bib->biblio, 'Koha::Biblio', "OK accessing related biblio");
175
176     $illrq_obj->delete;
177     is(Koha::Illrequestattributes->search->count, 1,
178        "Correct number of illrequestattributes after delete.");
179
180     isa_ok(Koha::Patrons->find($patron->{borrowernumber}), 'Koha::Patron',
181            "Borrower was not deleted after illrq delete.");
182
183     $schema->storage->txn_rollback;
184 };
185
186 subtest 'Status Graph tests' => sub {
187
188     plan tests => 5;
189
190     $schema->storage->txn_begin;
191
192     my $illrq = $builder->build({source => 'Illrequest'});
193     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
194
195     # _core_status_graph tests: it's just a constant, so here we just make
196     # sure it returns a hashref.
197     is(ref $illrq_obj->_core_status_graph, "HASH",
198        "_core_status_graph returns a hash.");
199
200     # _status_graph_union: let's try different merge operations.
201     # Identity operation
202     is_deeply(
203         $illrq_obj->_status_graph_union($illrq_obj->_core_status_graph, {}),
204         $illrq_obj->_core_status_graph,
205         "core_status_graph + null = core_status_graph"
206     );
207
208     # Simple addition
209     is_deeply(
210         $illrq_obj->_status_graph_union({}, $illrq_obj->_core_status_graph),
211         $illrq_obj->_core_status_graph,
212         "null + core_status_graph = core_status_graph"
213     );
214
215     # Correct merge behaviour
216     is_deeply(
217         $illrq_obj->_status_graph_union({
218             REQ => {
219                 prev_actions   => [ ],
220                 id             => 'REQ',
221                 next_actions   => [ ],
222             },
223         }, {
224             QER => {
225                 prev_actions   => [ 'REQ' ],
226                 id             => 'QER',
227                 next_actions   => [ 'REQ' ],
228             },
229         }),
230         {
231             REQ => {
232                 prev_actions   => [ 'QER' ],
233                 id             => 'REQ',
234                 next_actions   => [ 'QER' ],
235             },
236             QER => {
237                 prev_actions   => [ 'REQ' ],
238                 id             => 'QER',
239                 next_actions   => [ 'REQ' ],
240             },
241         },
242         "REQ atom + linking QER = cyclical status graph"
243     );
244
245     # Create a new node, with no prev_actions and no next_actions. This should
246     # protect us against regressions related to bug 22280.
247     my $new_node = {
248         TEST => {
249             prev_actions   => [ ],
250             id             => 'TEST',
251             next_actions   => [ ],
252         },
253     };
254     # Add the new node to the core_status_grpah
255     my $new_graph = $illrq_obj->_status_graph_union( $new_node, $illrq_obj->_core_status_graph);
256     # Compare the updated graph to the expected graph
257     # The structure we compare against here is just a copy of the structure found
258     # in Koha::Illrequest::_core_status_graph() + the new node we created above
259     cmp_deeply( $new_graph,
260         {
261         TEST => {
262             prev_actions   => [ ],
263             id             => 'TEST',
264             next_actions   => [ ],
265         },
266         NEW => {
267             prev_actions => [ ],                           # Actions containing buttons
268                                                            # leading to this status
269             id             => 'NEW',                       # ID of this status
270             name           => 'New request',               # UI name of this status
271             ui_method_name => 'New request',               # UI name of method leading
272                                                            # to this status
273             method         => 'create',                    # method to this status
274             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
275                                                            # requests with this status
276             ui_method_icon => 'fa-plus',                   # UI Style class
277         },
278         REQ => {
279             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
280             id             => 'REQ',
281             name           => 'Requested',
282             ui_method_name => 'Confirm request',
283             method         => 'confirm',
284             next_actions   => [ 'REQREV', 'COMP', 'CHK' ],
285             ui_method_icon => 'fa-check',
286         },
287         GENREQ => {
288             prev_actions   => [ 'NEW', 'REQREV' ],
289             id             => 'GENREQ',
290             name           => 'Requested from partners',
291             ui_method_name => 'Place request with partners',
292             method         => 'generic_confirm',
293             next_actions   => [ 'COMP', 'CHK' ],
294             ui_method_icon => 'fa-send-o',
295         },
296         REQREV => {
297             prev_actions   => [ 'REQ' ],
298             id             => 'REQREV',
299             name           => 'Request reverted',
300             ui_method_name => 'Revert Request',
301             method         => 'cancel',
302             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
303             ui_method_icon => 'fa-times',
304         },
305         QUEUED => {
306             prev_actions   => [ ],
307             id             => 'QUEUED',
308             name           => 'Queued request',
309             ui_method_name => 0,
310             method         => 0,
311             next_actions   => [ 'REQ', 'KILL' ],
312             ui_method_icon => 0,
313         },
314         CANCREQ => {
315             prev_actions   => [ 'NEW' ],
316             id             => 'CANCREQ',
317             name           => 'Cancellation requested',
318             ui_method_name => 0,
319             method         => 0,
320             next_actions   => [ 'KILL', 'REQ' ],
321             ui_method_icon => 0,
322         },
323         COMP => {
324             prev_actions   => [ 'REQ' ],
325             id             => 'COMP',
326             name           => 'Completed',
327             ui_method_name => 'Mark completed',
328             method         => 'mark_completed',
329             next_actions   => [ 'CHK' ],
330             ui_method_icon => 'fa-check',
331         },
332         KILL => {
333             prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
334             id             => 'KILL',
335             name           => 0,
336             ui_method_name => 'Delete request',
337             method         => 'delete',
338             next_actions   => [ ],
339             ui_method_icon => 'fa-trash',
340         },
341         CHK => {
342             prev_actions   => [ 'REQ', 'GENREQ', 'COMP' ],
343             id             => 'CHK',
344             name           => 'Checked out',
345             ui_method_name => 'Check out',
346             needs_prefs    => [ 'CirculateILL' ],
347             needs_perms    => [ 'user_circulate_circulate_remaining_permissions' ],
348             needs_all      => ignore(),
349             method         => 'check_out',
350             next_actions   => [ ],
351             ui_method_icon => 'fa-upload',
352         },
353         RET => {
354             prev_actions   => [ 'CHK' ],
355             id             => 'RET',
356             name           => 'Returned to library',
357             ui_method_name => 'Check in',
358             method         => 'check_in',
359             next_actions   => [ 'COMP' ],
360             ui_method_icon => 'fa-download',
361         }
362     },
363         "new node + core_status_graph = bigger status graph"
364     ) || diag explain $new_graph;
365
366     $schema->storage->txn_rollback;
367 };
368
369 subtest 'Backend testing (mocks)' => sub {
370
371     plan tests => 13;
372
373     $schema->storage->txn_begin;
374
375     # testing load_backend & available_backends requires that we have at least
376     # the Dummy plugin installed.  load_backend & available_backends don't
377     # currently have tests as a result.
378
379     t::lib::Mocks->mock_config('interlibrary_loans', { backend_dir => 'a_dir' }  );
380     my $backend = Test::MockObject->new;
381     $backend->set_isa('Koha::Illbackends::Mock');
382     $backend->set_always('name', 'Mock');
383
384     my $patron = $builder->build({ source => 'Borrower' });
385     my $illrq = $builder->build_object({
386         class => 'Koha::Illrequests',
387     });
388
389     $illrq->_backend($backend);
390
391     isa_ok($illrq->_backend, 'Koha::Illbackends::Mock',
392            "OK accessing mocked backend.");
393
394     # _backend_capability tests:
395     # We need to test whether this optional feature of a mocked backend
396     # behaves as expected.
397     # 3 scenarios: feature not implemented, feature implemented, but requested
398     # capability is not provided by backend, & feature is implemented &
399     # capability exists.  This method can be used to implement custom backend
400     # functionality, such as unmediated in the BLDSS backend (also see
401     # bugzilla 18837).
402     $backend->set_always('capabilities', undef);
403     is($illrq->_backend_capability('Test'), 0,
404        "0 returned on Mock not implementing capabilities.");
405
406     $backend->set_always('capabilities', 0);
407     is($illrq->_backend_capability('Test'), 0,
408        "0 returned on Mock not implementing Test capability.");
409
410     $backend->set_always('capabilities', sub { return 'bar'; } );
411     is($illrq->_backend_capability('Test'), 'bar',
412        "'bar' returned on Mock implementing Test capability.");
413
414     # metadata test: we need to be sure that we return the arbitrary values
415     # from the backend.
416     $backend->mock(
417         'metadata',
418         sub {
419             my ( $self, $rq ) = @_;
420             return {
421                 ID => $rq->illrequest_id,
422                 Title => $rq->patron->borrowernumber
423             }
424         }
425     );
426
427     is_deeply(
428         $illrq->metadata,
429         {
430             ID => $illrq->illrequest_id,
431             Title => $illrq->patron->borrowernumber
432         },
433         "Test metadata."
434     );
435
436     # capabilities:
437
438     # No backend graph extension
439     $backend->set_always('status_graph', {});
440     is_deeply($illrq->capabilities('COMP'),
441               {
442                   prev_actions   => [ 'REQ' ],
443                   id             => 'COMP',
444                   name           => 'Completed',
445                   ui_method_name => 'Mark completed',
446                   method         => 'mark_completed',
447                   next_actions   => [ 'CHK' ],
448                   ui_method_icon => 'fa-check',
449               },
450               "Dummy status graph for COMP.");
451     is($illrq->capabilities('UNKNOWN'), undef,
452        "Dummy status graph for UNKNOWN.");
453     is_deeply($illrq->capabilities(),
454               $illrq->_core_status_graph,
455               "Dummy full status graph.");
456     # Simple backend graph extension
457     $backend->set_always('status_graph',
458                          {
459                              QER => {
460                                  prev_actions   => [ 'REQ' ],
461                                  id             => 'QER',
462                                  next_actions   => [ 'REQ' ],
463                              },
464                          });
465     is_deeply($illrq->capabilities('QER'),
466               {
467                   prev_actions   => [ 'REQ' ],
468                   id             => 'QER',
469                   next_actions   => [ 'REQ' ],
470               },
471               "Simple status graph for QER.");
472     is($illrq->capabilities('UNKNOWN'), undef,
473        "Simple status graph for UNKNOWN.");
474     is_deeply($illrq->capabilities(),
475               $illrq->_status_graph_union(
476                   $illrq->_core_status_graph,
477                   {
478                       QER => {
479                           prev_actions   => [ 'REQ' ],
480                           id             => 'QER',
481                           next_actions   => [ 'REQ' ],
482                       },
483                   }
484               ),
485               "Simple full status graph.");
486
487     # custom_capability:
488
489     # No backend graph extension
490     $backend->set_always('status_graph', {});
491     is($illrq->custom_capability('unknown', {}), 0,
492        "Unknown candidate.");
493
494     # Simple backend graph extension
495     $backend->set_always('status_graph',
496                          {
497                              ID => {
498                                  prev_actions   => [ 'REQ' ],
499                                  id             => 'ID',
500                                  method         => 'identity',
501                                  next_actions   => [ 'REQ' ],
502                              },
503                          });
504     $backend->mock('identity',
505                    sub { my ( $self, $params ) = @_; return $params->{other}; });
506     is($illrq->custom_capability('identity', { test => 1, method => 'blah' })->{test}, 1,
507        "Resolve identity custom_capability");
508
509     $schema->storage->txn_rollback;
510 };
511
512
513 subtest 'Backend core methods' => sub {
514
515     plan tests => 19;
516
517     $schema->storage->txn_begin;
518
519     # Build infrastructure
520     my $backend = Test::MockObject->new;
521     $backend->set_isa('Koha::Illbackends::Mock');
522     $backend->set_always('name', 'Mock');
523     $backend->mock('capabilities', sub { return 'Mock'; });
524
525     my $config = Test::MockObject->new;
526     $config->set_always('backend_dir', "/tmp");
527     $config->set_always('getLimitRules',
528                         { default => { count => 0, method => 'active' } });
529
530     my $illrq = $builder->build_object({
531         class => 'Koha::Illrequests',
532         value => { backend => undef }
533     });
534     $illrq->_config($config);
535
536     # Test error conditions (no backend)
537     throws_ok { $illrq->load_backend; }
538         'Koha::Exceptions::Ill::InvalidBackendId',
539         'Exception raised correctly';
540
541     throws_ok { $illrq->load_backend(''); }
542         'Koha::Exceptions::Ill::InvalidBackendId',
543         'Exception raised correctly';
544
545     # Now load the mocked backend
546     $illrq->_backend($backend);
547
548     # expandTemplate:
549     is_deeply($illrq->expandTemplate({ test => 1, method => "bar" }),
550               {
551                   test => 1,
552                   method => "bar",
553                   template => "/tmp/Mock/intra-includes/bar.inc",
554                   opac_template => "/tmp/Mock/opac-includes/bar.inc",
555               },
556               "ExpandTemplate");
557
558     # backend_create
559     # we are testing simple cases.
560     $backend->set_series('create',
561                          { stage => 'bar', method => 'create' },
562                          { stage => 'commit', method => 'create' },
563                          { stage => 'commit', method => 'create' },
564                          { stage => 'commit', method => 'create' },
565                          { stage => 'commit', method => 'create' });
566     # Test non-commit
567     is_deeply($illrq->backend_create({test => 1}),
568               {
569                   stage => 'bar', method => 'create',
570                   template => "/tmp/Mock/intra-includes/create.inc",
571                   opac_template => "/tmp/Mock/opac-includes/create.inc",
572               },
573               "Backend create: arbitrary stage.");
574     # Test commit
575     is_deeply($illrq->backend_create({test => 1}),
576               {
577                   stage => 'commit', method => 'create', permitted => 0,
578                   template => "/tmp/Mock/intra-includes/create.inc",
579                   opac_template => "/tmp/Mock/opac-includes/create.inc",
580               },
581               "Backend create: arbitrary stage, not permitted.");
582     is($illrq->status, "QUEUED", "Backend create: queued if restricted.");
583     $config->set_always('getLimitRules', {});
584     $illrq->status('NEW');
585     is_deeply($illrq->backend_create({test => 1}),
586               {
587                   stage => 'commit', method => 'create', permitted => 1,
588                   template => "/tmp/Mock/intra-includes/create.inc",
589                   opac_template => "/tmp/Mock/opac-includes/create.inc",
590               },
591               "Backend create: arbitrary stage, permitted.");
592     is($illrq->status, "NEW", "Backend create: not-queued.");
593
594     # Test that enabling the unmediated workflow causes the backend's
595     # 'unmediated_ill' method to be called
596     t::lib::Mocks::mock_preference('ILLModuleUnmediated', '1');
597     $backend->mock(
598         'capabilities',
599         sub {
600             my ($self, $name) = @_;
601             if ($name eq 'unmediated_ill') {
602                 return sub {
603                     return { unmediated_ill => 1 };
604                 };
605             }
606         }
607     );
608     $illrq->status('NEW');
609     is_deeply(
610         $illrq->backend_create({test => 1}),
611         {
612             'opac_template' => '/tmp/Mock/opac-includes/.inc',
613             'template' => '/tmp/Mock/intra-includes/.inc',
614             'unmediated_ill' => 1
615         },
616         "Backend create: commit stage, permitted, ILLModuleUnmediated enabled."
617     );
618
619     # Test that disabling the unmediated workflow causes the backend's
620     # 'unmediated_ill' method to be NOT called
621     t::lib::Mocks::mock_preference('ILLModuleUnmediated', '0');
622     $illrq->status('NEW');
623     is_deeply(
624         $illrq->backend_create({test => 1}),
625         {
626             stage => 'commit', method => 'create', permitted => 1,
627             template => "/tmp/Mock/intra-includes/create.inc",
628             opac_template => "/tmp/Mock/opac-includes/create.inc",
629         },
630         "Backend create: commit stage, permitted, ILLModuleUnmediated disabled."
631     );
632
633     # backend_renew
634     $backend->set_series('renew', { stage => 'bar', method => 'renew' });
635     is_deeply($illrq->backend_renew({test => 1}),
636               {
637                   stage => 'bar', method => 'renew',
638                   template => "/tmp/Mock/intra-includes/renew.inc",
639                   opac_template => "/tmp/Mock/opac-includes/renew.inc",
640               },
641               "Backend renew: arbitrary stage.");
642
643     # backend_cancel
644     $backend->set_series('cancel', { stage => 'bar', method => 'cancel' });
645     is_deeply($illrq->backend_cancel({test => 1}),
646               {
647                   stage => 'bar', method => 'cancel',
648                   template => "/tmp/Mock/intra-includes/cancel.inc",
649                   opac_template => "/tmp/Mock/opac-includes/cancel.inc",
650               },
651               "Backend cancel: arbitrary stage.");
652
653     # backend_update_status
654     $backend->set_series('update_status', { stage => 'bar', method => 'update_status' });
655     is_deeply($illrq->backend_update_status({test => 1}),
656               {
657                   stage => 'bar', method => 'update_status',
658                   template => "/tmp/Mock/intra-includes/update_status.inc",
659                   opac_template => "/tmp/Mock/opac-includes/update_status.inc",
660               },
661               "Backend update_status: arbitrary stage.");
662
663     # backend_confirm
664     $backend->set_series('confirm', { stage => 'bar', method => 'confirm' });
665     is_deeply($illrq->backend_confirm({test => 1}),
666               {
667                   stage => 'bar', method => 'confirm',
668                   template => "/tmp/Mock/intra-includes/confirm.inc",
669                   opac_template => "/tmp/Mock/opac-includes/confirm.inc",
670               },
671               "Backend confirm: arbitrary stage.");
672
673     $config->set_always('partner_code', "ILLTSTLIB");
674     $backend->set_always('metadata', { Test => "Foobar" });
675     my $illbrn = $builder->build({
676         source => 'Branch',
677         value => { branchemail => "", branchreplyto => "" }
678     });
679     my $partner1 = $builder->build({
680         source => 'Borrower',
681         value => { categorycode => "ILLTSTLIB" },
682     });
683     my $partner2 = $builder->build({
684         source => 'Borrower',
685         value => { categorycode => "ILLTSTLIB" },
686     });
687     my $gen_conf = $illrq->generic_confirm({
688         current_branchcode => $illbrn->{branchcode}
689     });
690     isnt(index($gen_conf->{value}->{draft}->{body}, $backend->metadata->{Test}), -1,
691          "Generic confirm: draft contains metadata."
692     );
693     is($gen_conf->{value}->{partners}->next->borrowernumber, $partner1->{borrowernumber},
694        "Generic cofnirm: partner 1 is correct."
695     );
696     is($gen_conf->{value}->{partners}->next->borrowernumber, $partner2->{borrowernumber},
697        "Generic confirm: partner 2 is correct."
698     );
699
700     dies_ok { $illrq->generic_confirm({
701         current_branchcode => $illbrn->{branchcode},
702         stage => 'draft'
703     }) }
704         "Generic confirm: missing to dies OK.";
705
706     dies_ok { $illrq->generic_confirm({
707         current_branchcode => $illbrn->{branchcode},
708         partners => $partner1->{email},
709         stage => 'draft'
710     }) }
711         "Generic confirm: missing from dies OK.";
712
713     $schema->storage->txn_rollback;
714 };
715
716
717 subtest 'Helpers' => sub {
718
719     plan tests => 7;
720
721     $schema->storage->txn_begin;
722
723     # Build infrastructure
724     my $backend = Test::MockObject->new;
725     $backend->set_isa('Koha::Illbackends::Mock');
726     $backend->set_always('name', 'Mock');
727
728     my $config = Test::MockObject->new;
729     $config->set_always('backend_dir', "/tmp");
730
731     my $patron = $builder->build({
732         source => 'Borrower',
733         value => { categorycode => "A" }
734     });
735     my $illrq = $builder->build({
736         source => 'Illrequest',
737         value => { branchcode => "CPL", borrowernumber => $patron->{borrowernumber} }
738     });
739     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
740     $illrq_obj->_config($config);
741     $illrq_obj->_backend($backend);
742
743     # getPrefix
744     $config->set_series('getPrefixes',
745                         { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
746                         { A => "ATEST", C => "CBAR", default => "DEFAULT" });
747     is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "CPL" }), "TEST",
748        "getPrefix: branch");
749     $config->set_series('getPrefixes',
750                         { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
751                         { A => "ATEST", C => "CBAR", default => "DEFAULT" });
752     is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
753        "getPrefix: default");
754     $config->set_always('getPrefixes', {});
755     is($illrq_obj->getPrefix({ branch => "UNKNOWN" }), "",
756        "getPrefix: the empty prefix");
757
758     # id_prefix
759     $config->set_series('getPrefixes',
760                         { CPL => "TEST", TSL => "BAR", default => "DEFAULT" },
761                         { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
762     is($illrq_obj->id_prefix, "TEST-", "id_prefix: branch");
763     $config->set_series('getPrefixes',
764                         { CPLT => "TEST", TSLT => "BAR", default => "DEFAULT" },
765                         { AB => "ATEST", CD => "CBAR", default => "DEFAULT" });
766     is($illrq_obj->id_prefix, "", "id_prefix: default");
767
768     # requires_moderation
769     $illrq_obj->status('NEW')->store;
770     is($illrq_obj->requires_moderation, undef, "requires_moderation: No.");
771     $illrq_obj->status('CANCREQ')->store;
772     is($illrq_obj->requires_moderation, 'CANCREQ', "requires_moderation: Yes.");
773
774     $schema->storage->txn_rollback;
775 };
776
777
778 subtest 'Censorship' => sub {
779
780     plan tests => 2;
781
782     $schema->storage->txn_begin;
783
784     # Build infrastructure
785     my $backend = Test::MockObject->new;
786     $backend->set_isa('Koha::Illbackends::Mock');
787     $backend->set_always('name', 'Mock');
788
789     my $config = Test::MockObject->new;
790     $config->set_always('backend_dir', "/tmp");
791
792     my $illrq = $builder->build({source => 'Illrequest'});
793     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
794     $illrq_obj->_config($config);
795     $illrq_obj->_backend($backend);
796
797     $config->set_always('censorship', { censor_notes_staff => 1, censor_reply_date => 0 });
798
799     my $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564 });
800     is_deeply($censor_out, { foo => 'bar', baz => 564, display_reply_date => 1 },
801               "_censor: not OPAC, reply_date = 1");
802
803     $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564, opac => 1 });
804     is_deeply($censor_out, {
805         foo => 'bar', baz => 564, censor_notes_staff => 1,
806         display_reply_date => 1, opac => 1
807     }, "_censor: notes_staff = 0, reply_date = 0");
808
809     $schema->storage->txn_rollback;
810 };
811
812 subtest 'Checking out' => sub {
813
814     plan tests => 17;
815
816     $schema->storage->txn_begin;
817
818     my $itemtype = $builder->build_object({
819         class => 'Koha::ItemTypes',
820         value => {
821             notforloan => 1
822         }
823     });
824     my $library = $builder->build_object({ class => 'Koha::Libraries' });
825     my $biblio = $builder->build_sample_biblio();
826     my $patron = $builder->build_object({
827         class => 'Koha::Patrons',
828         value => { category_type => 'x' }
829     });
830     my $request = $builder->build_object({
831         class => 'Koha::Illrequests',
832         value => {
833             borrowernumber => $patron->borrowernumber,
834             biblio_id      => $biblio->biblionumber
835         }
836     });
837
838     # First test that calling check_out without a stage param returns
839     # what's required to build the form
840     my $no_stage = $request->check_out();
841     is($no_stage->{method}, 'check_out');
842     is($no_stage->{stage}, 'form');
843     isa_ok($no_stage->{value}, 'HASH');
844     isa_ok($no_stage->{value}->{itemtypes}, 'Koha::ItemTypes');
845     isa_ok($no_stage->{value}->{libraries}, 'Koha::Libraries');
846     isa_ok($no_stage->{value}->{statistical}, 'Koha::Patrons');
847     isa_ok($no_stage->{value}->{biblio}, 'Koha::Biblio');
848
849     # Now test that form validation works when we supply a 'form' stage
850     #
851     # No item_type
852     my $form_stage_missing_params = $request->check_out({
853         stage => 'form'
854     });
855     is_deeply($form_stage_missing_params->{value}->{errors}, {
856         item_type => 1
857     });
858     # inhouse passed but not a valid patron
859     my $form_stage_bad_patron = $request->check_out({
860         stage     => 'form',
861         item_type => $itemtype->itemtype,
862         inhouse   => 'I_DONT_EXIST'
863     });
864     is_deeply($form_stage_bad_patron->{value}->{errors}, {
865         inhouse => 1
866     });
867     # Too many items attached to biblio
868     my $item1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
869     my $item2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
870     my $form_stage_two_items = $request->check_out({
871         stage     => 'form',
872         item_type => $itemtype->itemtype,
873     });
874     is_deeply($form_stage_two_items->{value}->{errors}, {
875         itemcount => 1
876     });
877
878     # Delete the items we created, so we can test that we can create one
879     $item1->delete;
880     $item2->delete;
881
882     # We need to mock the user environment for AddIssue
883     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
884     #
885
886     # First we pass bad parameters to the item creation to test we're
887     # catching the failure of item creation
888     my $form_stage_bad_branchcode;
889     warning_like {
890         $form_stage_bad_branchcode = $request->check_out({
891             stage     => 'form',
892             item_type => $itemtype->itemtype,
893             branchcode => '---'
894         });
895     } qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/,
896     "Item creation fails on bad parameters";
897
898     is_deeply($form_stage_bad_branchcode->{value}->{errors}, {
899         item_creation => 1
900     },"We get expected failure of item creation");
901
902     # Now create a proper item
903     my $form_stage_good_branchcode = $request->check_out({
904         stage      => 'form',
905         item_type  => $itemtype->itemtype,
906         branchcode => $library->branchcode
907     });
908     # By default, this item should not be loanable, so check that we're
909     # informed of that fact
910     is_deeply(
911         $form_stage_good_branchcode->{value}->{check_out_errors},
912         {
913             error => {
914                 NOT_FOR_LOAN => 1,
915                 itemtype_notforloan => $itemtype->itemtype
916             }
917         },
918         "We get expected error on notforloan of item"
919     );
920     # Delete the item that was created
921     $biblio->items->delete;
922     # Now create an itemtype that is loanable
923     my $itemtype_loanable = $builder->build_object({
924         class => 'Koha::ItemTypes',
925         value => {
926             notforloan => 0
927         }
928     });
929     # We need to mock the user environment for AddIssue
930     t::lib::Mocks::mock_userenv({ branchcode => $library->branchcode });
931     my $form_stage_loanable = $request->check_out({
932         stage      => 'form',
933         item_type  => $itemtype_loanable->itemtype,
934         branchcode => $library->branchcode
935     });
936     is($form_stage_loanable->{stage}, 'done_check_out');
937     isa_ok($patron->checkouts, 'Koha::Checkouts');
938     is($patron->checkouts->count, 1);
939     is($request->status, 'CHK');
940
941     $schema->storage->txn_rollback;
942 };
943
944 subtest 'Checking Limits' => sub {
945
946     plan tests => 30;
947
948     $schema->storage->txn_begin;
949
950     # Build infrastructure
951     my $backend = Test::MockObject->new;
952     $backend->set_isa('Koha::Illbackends::Mock');
953     $backend->set_always('name', 'Mock');
954
955     my $config = Test::MockObject->new;
956     $config->set_always('backend_dir', "/tmp");
957
958     my $illrq = $builder->build({source => 'Illrequest'});
959     my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id});
960     $illrq_obj->_config($config);
961     $illrq_obj->_backend($backend);
962
963     # getLimits
964     $config->set_series('getLimitRules',
965                         { CPL => { count => 1, method => 'test' } },
966                         { default => { count => 0, method => 'active' } });
967     is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
968               { count => 1, method => 'test' },
969               "getLimits: by value.");
970     is_deeply($illrq_obj->getLimits({ type => 'branch' }),
971               { count => 0, method => 'active' },
972               "getLimits: by default.");
973     is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }),
974               { count => -1, method => 'active' },
975               "getLimits: by hard-coded.");
976
977     #_limit_counter
978     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
979        1, "_limit_counter: Initial branch annual count.");
980     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
981        1, "_limit_counter: Initial branch active count.");
982     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
983        1, "_limit_counter: Initial patron annual count.");
984     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
985        1, "_limit_counter: Initial patron active count.");
986     $builder->build({
987         source => 'Illrequest',
988         value => {
989             branchcode => $illrq_obj->branchcode,
990             borrowernumber => $illrq_obj->borrowernumber,
991         }
992     });
993     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
994        2, "_limit_counter: Add a qualifying request for branch annual count.");
995     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
996        2, "_limit_counter: Add a qualifying request for branch active count.");
997     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
998        2, "_limit_counter: Add a qualifying request for patron annual count.");
999     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1000        2, "_limit_counter: Add a qualifying request for patron active count.");
1001     $builder->build({
1002         source => 'Illrequest',
1003         value => {
1004             branchcode => $illrq_obj->branchcode,
1005             borrowernumber => $illrq_obj->borrowernumber,
1006             placed => "2005-05-31",
1007         }
1008     });
1009     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1010        2, "_limit_counter: Add an out-of-date branch request.");
1011     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1012        3, "_limit_counter: Add a qualifying request for branch active count.");
1013     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1014        2, "_limit_counter: Add an out-of-date patron request.");
1015     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1016        3, "_limit_counter: Add a qualifying request for patron active count.");
1017     $builder->build({
1018         source => 'Illrequest',
1019         value => {
1020             branchcode => $illrq_obj->branchcode,
1021             borrowernumber => $illrq_obj->borrowernumber,
1022             status => "COMP",
1023         }
1024     });
1025     is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }),
1026        3, "_limit_counter: Add a qualifying request for branch annual count.");
1027     is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }),
1028        3, "_limit_counter: Add a completed request for branch active count.");
1029     is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }),
1030        3, "_limit_counter: Add a qualifying request for patron annual count.");
1031     is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }),
1032        3, "_limit_counter: Add a completed request for patron active count.");
1033
1034     # check_limits:
1035
1036     # We've tested _limit_counter, so all we need to test here is whether the
1037     # current counts of 3 for each work as they should against different
1038     # configuration declarations.
1039
1040     # No limits
1041     $config->set_always('getLimitRules', undef);
1042     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1043                                  librarycode => $illrq_obj->branchcode}),
1044        1, "check_limits: no configuration => no limits.");
1045
1046     # Branch tests
1047     $config->set_always('getLimitRules',
1048                         { $illrq_obj->branchcode => { count => 1, method => 'active' } });
1049     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1050                                  librarycode => $illrq_obj->branchcode}),
1051        0, "check_limits: branch active limit exceeded.");
1052     $config->set_always('getLimitRules',
1053                         { $illrq_obj->branchcode => { count => 1, method => 'annual' } });
1054     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1055                                  librarycode => $illrq_obj->branchcode}),
1056        0, "check_limits: branch annual limit exceeded.");
1057     $config->set_always('getLimitRules',
1058                         { $illrq_obj->branchcode => { count => 4, method => 'active' } });
1059     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1060                                  librarycode => $illrq_obj->branchcode}),
1061        1, "check_limits: branch active limit OK.");
1062     $config->set_always('getLimitRules',
1063                         { $illrq_obj->branchcode => { count => 4, method => 'annual' } });
1064     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1065                                  librarycode => $illrq_obj->branchcode}),
1066        1, "check_limits: branch annual limit OK.");
1067
1068     # Patron tests
1069     $config->set_always('getLimitRules',
1070                         { $illrq_obj->patron->categorycode => { count => 1, method => 'active' } });
1071     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1072                                  librarycode => $illrq_obj->branchcode}),
1073        0, "check_limits: patron category active limit exceeded.");
1074     $config->set_always('getLimitRules',
1075                         { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1076     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1077                                  librarycode => $illrq_obj->branchcode}),
1078        0, "check_limits: patron category annual limit exceeded.");
1079     $config->set_always('getLimitRules',
1080                         { $illrq_obj->patron->categorycode => { count => 4, method => 'active' } });
1081     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1082                                  librarycode => $illrq_obj->branchcode}),
1083        1, "check_limits: patron category active limit OK.");
1084     $config->set_always('getLimitRules',
1085                         { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1086     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1087                                  librarycode => $illrq_obj->branchcode}),
1088        1, "check_limits: patron category annual limit OK.");
1089
1090     # One rule cancels the other
1091     $config->set_series('getLimitRules',
1092                         # Branch rules allow request
1093                         { $illrq_obj->branchcode => { count => 4, method => 'active' } },
1094                         # Patron rule forbids it
1095                         { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } });
1096     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1097                                  librarycode => $illrq_obj->branchcode}),
1098        0, "check_limits: patron category veto overrides branch OK.");
1099     $config->set_series('getLimitRules',
1100                         # Branch rules allow request
1101                         { $illrq_obj->branchcode => { count => 1, method => 'active' } },
1102                         # Patron rule forbids it
1103                         { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } });
1104     is($illrq_obj->check_limits({patron => $illrq_obj->patron,
1105                                  librarycode => $illrq_obj->branchcode}),
1106        0, "check_limits: branch veto overrides patron category OK.");
1107
1108     $schema->storage->txn_rollback;
1109 };
1110
1111 subtest 'Custom statuses' => sub {
1112
1113     plan tests => 3;
1114
1115     $schema->storage->txn_begin;
1116
1117     my $cat = Koha::AuthorisedValueCategories->search(
1118         {
1119             category_name => 'ILLSTATUS'
1120         }
1121     );
1122
1123     if ($cat->count == 0) {
1124         $cat  = $builder->build_object(
1125             {
1126                 class => 'Koha::AuthorisedValueCategory',
1127                 value => {
1128                     category_name => 'ILLSTATUS'
1129                 }
1130             }
1131         );
1132     };
1133
1134     my $av = $builder->build_object(
1135         {
1136             class => 'Koha::AuthorisedValues',
1137             value => {
1138                 category => 'ILLSTATUS'
1139             }
1140         }
1141     );
1142
1143     is($av->category, 'ILLSTATUS',
1144        "Successfully created authorised value for custom status");
1145
1146     my $ill_req = $builder->build_object(
1147         {
1148             class => 'Koha::Illrequests',
1149             value => {
1150                 status_alias => $av->authorised_value
1151             }
1152         }
1153     );
1154     isa_ok($ill_req->statusalias, 'Koha::AuthorisedValue',
1155            "statusalias correctly returning Koha::AuthorisedValue object");
1156
1157     $ill_req->status("COMP");
1158     is($ill_req->statusalias, undef,
1159         "Koha::Illrequest->status overloading resetting status_alias");
1160
1161     $schema->storage->txn_rollback;
1162 };