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