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