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