Bug 23012: (QA follow-up) Combine method to get both values
[koha.git] / t / db_dependent / Koha / CirculationRules.t
1 #!/usr/bin/perl
2
3 # Copyright 2020 Koha Development team
4 #
5 # This file is part of Koha
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Benchmark;
23 use Test::More tests => 7;
24 use Test::Deep qw( cmp_methods );
25 use Test::Exception;
26
27 use Koha::CirculationRules;
28 use Koha::Database;
29
30 use t::lib::Mocks;
31 use t::lib::TestBuilder;
32 use Koha::Cache::Memory::Lite;
33
34 my $schema = Koha::Database->new->schema;
35 my $builder = t::lib::TestBuilder->new;
36
37 subtest 'get_effective_issuing_rule' => sub {
38     plan tests => 2;
39
40     $schema->storage->txn_begin;
41
42     my $categorycode = $builder->build({ source => 'Category' })->{'categorycode'};
43     my $itemtype     = $builder->build({ source => 'Itemtype' })->{'itemtype'};
44     my $branchcode   = $builder->build({ source => 'Branch' })->{'branchcode'};
45
46     subtest 'Call with undefined values' => sub {
47         plan tests => 5;
48
49         my $rule;
50         Koha::CirculationRules->delete;
51
52         is(Koha::CirculationRules->search->count, 0, 'There are no issuing rules.');
53         # undef, undef, undef => 1
54         $rule = Koha::CirculationRules->get_effective_rule({
55             branchcode   => undef,
56             categorycode => undef,
57             itemtype     => undef,
58             rule_name    => 'fine',
59             rule_value   => 1,
60         });
61         is($rule, undef, 'When I attempt to get effective issuing rule by'
62            .' providing undefined values, then undef is returned.');
63
64        # undef, undef, undef => 2
65         ok(
66             Koha::CirculationRule->new(
67                 {
68                     branchcode   => undef,
69                     categorycode => undef,
70                     itemtype     => undef,
71                     rule_name    => 'fine',
72                     rule_value   => 2,
73                 }
74               )->store,
75             'Given I added an issuing rule branchcode => undef,'
76            .' categorycode => undef, itemtype => undef,');
77         $rule = Koha::CirculationRules->get_effective_rule({
78             branchcode   => undef,
79             categorycode => undef,
80             itemtype     => undef,
81             rule_name    => 'fine',
82         });
83         _is_row_match(
84             $rule,
85             {
86                 branchcode   => undef,
87                 categorycode => undef,
88                 itemtype     => undef,
89                 rule_name    => 'fine',
90                 rule_value   => 2,
91             },
92             'When I attempt to get effective'
93            .' issuing rule by providing undefined values, then the above one is'
94            .' returned.'
95         );
96     };
97
98     subtest 'Performance' => sub {
99         plan tests => 4;
100
101         my $worst_case = timethis(500,
102                     sub { Koha::CirculationRules->get_effective_rule({
103                             branchcode   => 'nonexistent',
104                             categorycode => 'nonexistent',
105                             itemtype     => 'nonexistent',
106                             rule_name    => 'nonexistent',
107                         });
108                     }
109                 );
110         my $mid_case = timethis(500,
111                     sub { Koha::CirculationRules->get_effective_rule({
112                             branchcode   => $branchcode,
113                             categorycode => 'nonexistent',
114                             itemtype     => 'nonexistent',
115                             rule_name    => 'nonexistent',
116                         });
117                     }
118                 );
119         my $sec_best_case = timethis(500,
120                     sub { Koha::CirculationRules->get_effective_rule({
121                             branchcode   => $branchcode,
122                             categorycode => $categorycode,
123                             itemtype     => 'nonexistent',
124                             rule_name    => 'nonexistent',
125                         });
126                     }
127                 );
128         my $best_case = timethis(500,
129                     sub { Koha::CirculationRules->get_effective_rule({
130                             branchcode   => $branchcode,
131                             categorycode => $categorycode,
132                             itemtype     => $itemtype,
133                             rule_name    => 'nonexistent',
134                         });
135                     }
136                 );
137         ok($worst_case, 'In worst case, get_effective_issuing_rule finds matching'
138            .' rule '.sprintf('%.2f', $worst_case->iters/$worst_case->cpu_a)
139            .' times per second.');
140         ok($mid_case, 'In mid case, get_effective_issuing_rule finds matching'
141            .' rule '.sprintf('%.2f', $mid_case->iters/$mid_case->cpu_a)
142            .' times per second.');
143         ok($sec_best_case, 'In second best case, get_effective_issuing_rule finds matching'
144            .' rule '.sprintf('%.2f', $sec_best_case->iters/$sec_best_case->cpu_a)
145            .' times per second.');
146         ok($best_case, 'In best case, get_effective_issuing_rule finds matching'
147            .' rule '.sprintf('%.2f', $best_case->iters/$best_case->cpu_a)
148            .' times per second.');
149     };
150
151     $schema->storage->txn_rollback;
152
153 };
154
155 subtest 'set_rule' => sub {
156     plan tests => 3;
157
158     $schema->storage->txn_begin;
159
160     my $branchcode   = $builder->build({ source => 'Branch' })->{'branchcode'};
161     my $categorycode = $builder->build({ source => 'Category' })->{'categorycode'};
162     my $itemtype     = $builder->build({ source => 'Itemtype' })->{'itemtype'};
163
164     subtest 'Correct call' => sub {
165         plan tests => 5;
166
167         Koha::CirculationRules->delete;
168
169         lives_ok( sub {
170             Koha::CirculationRules->set_rule( {
171                 branchcode => $branchcode,
172                 rule_name => 'lostreturn',
173                 rule_value => '',
174             } );
175         }, 'setting lostreturn with branch' );
176
177         lives_ok( sub {
178             Koha::CirculationRules->set_rule( {
179                 branchcode => $branchcode,
180                 rule_name => 'processingreturn',
181                 rule_value => '',
182             } );
183         }, 'setting processingreturn with branch' );
184
185         lives_ok( sub {
186             Koha::CirculationRules->set_rule( {
187                 branchcode => $branchcode,
188                 categorycode => $categorycode,
189                 rule_name => 'patron_maxissueqty',
190                 rule_value => '',
191             } );
192         }, 'setting patron_maxissueqty with branch/category succeeds' );
193
194         lives_ok( sub {
195             Koha::CirculationRules->set_rule( {
196                 branchcode => $branchcode,
197                 itemtype => $itemtype,
198                 rule_name => 'holdallowed',
199                 rule_value => '',
200             } );
201         }, 'setting holdallowed with branch/itemtype succeeds' );
202
203         lives_ok( sub {
204             Koha::CirculationRules->set_rule( {
205                 branchcode => $branchcode,
206                 categorycode => $categorycode,
207                 itemtype => $itemtype,
208                 rule_name => 'fine',
209                 rule_value => '',
210             } );
211         }, 'setting fine with branch/category/itemtype succeeds' );
212     };
213
214     subtest 'Call with missing params' => sub {
215         plan tests => 5;
216
217         Koha::CirculationRules->delete;
218
219         throws_ok( sub {
220             Koha::CirculationRules->set_rule( {
221                 rule_name => 'lostreturn',
222                 rule_value => '',
223             } );
224         }, qr/branchcode/, 'setting lostreturn without branch fails' );
225
226         throws_ok( sub {
227             Koha::CirculationRules->set_rule( {
228                 rule_name => 'processingreturn',
229                 rule_value => '',
230             } );
231         }, qr/branchcode/, 'setting processingreturn without branch fails' );
232
233         throws_ok( sub {
234             Koha::CirculationRules->set_rule( {
235                 branchcode => $branchcode,
236                 rule_name => 'patron_maxissueqty',
237                 rule_value => '',
238             } );
239         }, qr/categorycode/, 'setting patron_maxissueqty without categorycode fails' );
240
241         throws_ok( sub {
242             Koha::CirculationRules->set_rule( {
243                 branchcode => $branchcode,
244                 rule_name => 'holdallowed',
245                 rule_value => '',
246             } );
247         }, qr/itemtype/, 'setting holdallowed without itemtype fails' );
248
249         throws_ok( sub {
250             Koha::CirculationRules->set_rule( {
251                 branchcode => $branchcode,
252                 categorycode => $categorycode,
253                 rule_name => 'fine',
254                 rule_value => '',
255             } );
256         }, qr/itemtype/, 'setting fine without itemtype fails' );
257     };
258
259     subtest 'Call with extra params' => sub {
260         plan tests => 4;
261
262         Koha::CirculationRules->delete;
263
264         throws_ok( sub {
265             Koha::CirculationRules->set_rule( {
266                 branchcode => $branchcode,
267                 categorycode => $categorycode,
268                 rule_name => 'lostreturn',
269                 rule_value => '',
270             } );
271         }, qr/categorycode/, 'setting lostreturn with categorycode fails' );
272
273         throws_ok( sub {
274             Koha::CirculationRules->set_rule( {
275                 branchcode => $branchcode,
276                 categorycode => $categorycode,
277                 rule_name => 'processingreturn',
278                 rule_value => '',
279             } );
280         }, qr/categorycode/, 'setting processingreturn with categorycode fails' );
281
282         throws_ok( sub {
283             Koha::CirculationRules->set_rule( {
284                 branchcode => $branchcode,
285                 categorycode => $categorycode,
286                 itemtype => $itemtype,
287                 rule_name => 'patron_maxissueqty',
288                 rule_value => '',
289             } );
290         }, qr/itemtype/, 'setting patron_maxissueqty with itemtype fails' );
291
292         throws_ok( sub {
293             Koha::CirculationRules->set_rule( {
294                 branchcode => $branchcode,
295                 rule_name => 'holdallowed',
296                 categorycode => $categorycode,
297                 itemtype => $itemtype,
298                 rule_value => '',
299             } );
300         }, qr/categorycode/, 'setting holdallowed with categorycode fails' );
301     };
302
303     $schema->storage->txn_rollback;
304 };
305
306 subtest 'clone' => sub {
307     plan tests => 2;
308
309     $schema->storage->txn_begin;
310
311     my $branchcode   = $builder->build({ source => 'Branch' })->{'branchcode'};
312     my $categorycode = $builder->build({ source => 'Category' })->{'categorycode'};
313     my $itemtype     = $builder->build({ source => 'Itemtype' })->{'itemtype'};
314
315     subtest 'Clone multiple rules' => sub {
316         plan tests => 4;
317
318         Koha::CirculationRules->delete;
319
320         Koha::CirculationRule->new({
321             branchcode   => undef,
322             categorycode => $categorycode,
323             itemtype     => $itemtype,
324             rule_name    => 'fine',
325             rule_value   => 5,
326         })->store;
327
328         Koha::CirculationRule->new({
329             branchcode   => undef,
330             categorycode => $categorycode,
331             itemtype     => $itemtype,
332             rule_name    => 'lengthunit',
333             rule_value   => 'days',
334         })->store;
335
336         Koha::CirculationRules->search({ branchcode => undef })->clone($branchcode);
337
338         my $rule_fine = Koha::CirculationRules->get_effective_rule({
339             branchcode   => $branchcode,
340             categorycode => $categorycode,
341             itemtype     => $itemtype,
342             rule_name    => 'fine',
343         });
344         my $rule_lengthunit = Koha::CirculationRules->get_effective_rule({
345             branchcode   => $branchcode,
346             categorycode => $categorycode,
347             itemtype     => $itemtype,
348             rule_name    => 'lengthunit',
349         });
350
351         _is_row_match(
352             $rule_fine,
353             {
354                 branchcode   => $branchcode,
355                 categorycode => $categorycode,
356                 itemtype     => $itemtype,
357                 rule_name    => 'fine',
358                 rule_value   => 5,
359             },
360             'When I attempt to get cloned fine rule,'
361            .' then the above one is returned.'
362         );
363         _is_row_match(
364             $rule_lengthunit,
365             {
366                 branchcode   => $branchcode,
367                 categorycode => $categorycode,
368                 itemtype     => $itemtype,
369                 rule_name    => 'lengthunit',
370                 rule_value   => 'days',
371             },
372             'When I attempt to get cloned lengthunit rule,'
373            .' then the above one is returned.'
374         );
375
376     };
377
378     subtest 'Clone one rule' => sub {
379         plan tests => 2;
380
381         Koha::CirculationRules->delete;
382
383         Koha::CirculationRule->new({
384             branchcode   => undef,
385             categorycode => $categorycode,
386             itemtype     => $itemtype,
387             rule_name    => 'fine',
388             rule_value   => 5,
389         })->store;
390
391         my $rule = Koha::CirculationRules->search({ branchcode => undef })->next;
392         $rule->clone($branchcode);
393
394         my $cloned_rule = Koha::CirculationRules->get_effective_rule({
395             branchcode   => $branchcode,
396             categorycode => $categorycode,
397             itemtype     => $itemtype,
398             rule_name    => 'fine',
399         });
400
401         _is_row_match(
402             $cloned_rule,
403             {
404                 branchcode   => $branchcode,
405                 categorycode => $categorycode,
406                 itemtype     => $itemtype,
407                 rule_name    => 'fine',
408                 rule_value   => '5',
409             },
410             'When I attempt to get cloned fine rule,'
411            .' then the above one is returned.'
412         );
413
414     };
415
416     $schema->storage->txn_rollback;
417 };
418
419 subtest 'set_rule + get_effective_rule' => sub {
420     plan tests => 9;
421
422     $schema->storage->txn_begin;
423
424     my $categorycode = $builder->build_object( { class => 'Koha::Patron::Categories' } )->categorycode;
425     my $itemtype     = $builder->build_object( { class => 'Koha::ItemTypes' } )->itemtype;
426     my $branchcode   = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
427     my $branchcode_2 = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
428     my $rule_name    = 'maxissueqty';
429     my $default_rule_value = 1;
430
431     my $rule;
432     Koha::CirculationRules->delete;
433
434     throws_ok { Koha::CirculationRules->get_effective_rule }
435     'Koha::Exceptions::MissingParameter',
436     "Exception should be raised if get_effective_rule is called without rule_name parameter";
437
438     $rule = Koha::CirculationRules->get_effective_rule(
439         {
440             branchcode   => $branchcode,
441             categorycode => $categorycode,
442             itemtype     => $itemtype,
443             rule_name    => $rule_name,
444         }
445     );
446     is( $rule, undef, 'Undef should be returned if no rule exist' );
447
448     Koha::CirculationRules->set_rule(
449         {
450             branchcode   => '*',
451             categorycode => '*',
452             itemtype     => '*',
453             rule_name    => $rule_name,
454             rule_value   => $default_rule_value,
455         }
456     );
457
458     $rule = Koha::CirculationRules->get_effective_rule(
459         {
460             branchcode   => undef,
461             categorycode => undef,
462             itemtype     => undef,
463             rule_name    => $rule_name,
464         }
465     );
466     is( $rule->rule_value, $default_rule_value, 'undef means default' );
467     $rule = Koha::CirculationRules->get_effective_rule(
468         {
469             branchcode   => '*',
470             categorycode => '*',
471             itemtype     => '*',
472             rule_name    => $rule_name,
473         }
474     );
475
476     is( $rule->rule_value, $default_rule_value, '* means default' );
477
478     $rule = Koha::CirculationRules->get_effective_rule(
479         {
480             branchcode   => $branchcode_2,
481             categorycode => '*',
482             itemtype     => '*',
483             rule_name    => $rule_name,
484         }
485     );
486     is( $rule->rule_value, 1,
487         'Default rule is returned if there is no rule for this branchcode' );
488
489     subtest 'test rules that cannot be blank' => sub {
490         plan tests => 3;
491         foreach my $no_blank_rule ( ('holdallowed','hold_fulfillment_policy','returnbranch') ){
492             Koha::CirculationRules->set_rule(
493                 {
494                     branchcode   => $branchcode,
495                     itemtype     => '*',
496                     rule_name    => $no_blank_rule,
497                     rule_value   => '',
498                 }
499             );
500
501             $rule = Koha::CirculationRules->get_effective_rule(
502                 {
503                     branchcode   => $branchcode,
504                     categorycode => undef,
505                     itemtype     => undef,
506                     rule_name    => $no_blank_rule,
507                 }
508             );
509             is( $rule, undef, 'Rules that cannot be blank are not set when passed blank string' );
510         }
511     };
512
513
514     subtest 'test rule matching with different combinations of rule scopes' => sub {
515         my ( $tests, $order ) = _prepare_tests_for_rule_scope_combinations(
516             {
517                 branchcode   => $branchcode,
518                 categorycode => $categorycode,
519                 itemtype     => $itemtype,
520             },
521             'maxissueqty'
522         );
523
524         plan tests => 2**scalar @$order;
525
526         foreach my $test (@$tests) {
527             my $rule_params = {%$test};
528             $rule_params->{rule_name} = $rule_name;
529             my $rule_value = $rule_params->{rule_value} = int( rand(10) );
530
531             Koha::CirculationRules->set_rule($rule_params);
532
533             my $rule = Koha::CirculationRules->get_effective_rule(
534                 {
535                     branchcode   => $branchcode,
536                     categorycode => $categorycode,
537                     itemtype     => $itemtype,
538                     rule_name    => $rule_name,
539                 }
540             );
541
542             my $scope_output = '';
543             foreach my $key ( values @$order ) {
544                 $scope_output .= " $key" if $test->{$key} ne '*';
545             }
546
547             is( $rule->rule_value, $rule_value,
548                 'Explicitly scoped'
549                   . ( $scope_output ? $scope_output : ' nothing' ) );
550         }
551     };
552
553     my $our_branch_rules = Koha::CirculationRules->search({branchcode => $branchcode});
554     is( $our_branch_rules->count, 4, "We added 8 rules");
555     $our_branch_rules->delete;
556     is( $our_branch_rules->count, 0, "We deleted 8 rules");
557
558     $schema->storage->txn_rollback;
559 };
560
561 subtest 'get_onshelfholds_policy() tests' => sub {
562
563     plan tests => 2;
564
565     $schema->storage->txn_begin;
566
567     my $item = $builder->build_sample_item();
568
569     my $circ_rules = Koha::CirculationRules->new;
570     # Cleanup
571     $circ_rules->search({ rule_name => 'onshelfholds' })->delete;
572
573     $circ_rules->set_rule(
574         {
575             branchcode   => '*',
576             categorycode => '*',
577             itemtype     => '*',
578             rule_name    => 'onshelfholds',
579             rule_value   => 1,
580         }
581     );
582
583     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 1, 'If rule_value is set on a matching rule, return it' );
584     # Delete the rule (i.e. get_effective_rule returns undef)
585     $circ_rules->delete;
586     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 0, 'If no matching rule, fallback to 0' );
587
588     $schema->storage->txn_rollback;
589 };
590
591 subtest 'get_effective_daysmode' => sub {
592     plan tests => 4;
593
594     $schema->storage->txn_begin;
595
596     my $item_1 = $builder->build_sample_item();
597     my $item_2 = $builder->build_sample_item();
598
599     my $circ_rules =
600       Koha::CirculationRules->search( { rule_name => 'daysmode' } )->delete;
601
602     # Default value 'Datedue' at pref level
603     t::lib::Mocks::mock_preference( 'useDaysMode', 'Datedue' );
604
605     is(
606         Koha::CirculationRules->get_effective_daysmode(
607             {
608                 categorycode => undef,
609                 itemtype     => $item_1->effective_itemtype,
610                 branchcode   => undef
611             }
612         ),
613         'Datedue',
614         'daysmode default to pref value if the rule does not exist'
615     );
616
617     Koha::CirculationRules->set_rule(
618         {
619             branchcode   => '*',
620             categorycode => '*',
621             itemtype     => '*',
622             rule_name    => 'daysmode',
623             rule_value   => 'Calendar',
624         }
625     );
626     Koha::CirculationRules->set_rule(
627         {
628             branchcode   => '*',
629             categorycode => '*',
630             itemtype     => $item_1->effective_itemtype,
631             rule_name    => 'daysmode',
632             rule_value   => 'Days',
633         }
634     );
635
636     is(
637         Koha::CirculationRules->get_effective_daysmode(
638             {
639                 categorycode => undef,
640                 itemtype     => $item_1->effective_itemtype,
641                 branchcode   => undef
642             }
643         ),
644         'Days',
645         "daysmode for item_1 is the specific rule"
646     );
647     is(
648         Koha::CirculationRules->get_effective_daysmode(
649             {
650                 categorycode => undef,
651                 itemtype     => $item_2->effective_itemtype,
652                 branchcode   => undef
653             }
654         ),
655         'Calendar',
656         "daysmode for item_2 is the one defined for the default circ rule"
657     );
658
659     Koha::CirculationRules->set_rule(
660         {
661             branchcode   => '*',
662             categorycode => '*',
663             itemtype     => $item_2->effective_itemtype,
664             rule_name    => 'daysmode',
665             rule_value   => '',
666         }
667     );
668
669     is(
670         Koha::CirculationRules->get_effective_daysmode(
671             {
672                 categorycode => undef,
673                 itemtype     => $item_2->effective_itemtype,
674                 branchcode   => undef
675             }
676         ),
677         'Datedue',
678         'daysmode default to pref value if the rule exists but set to""'
679     );
680
681     $schema->storage->txn_rollback;
682 };
683
684 subtest 'get_lostreturn_policy() tests' => sub {
685     plan tests => 7;
686
687     $schema->storage->txn_begin;
688
689     $schema->resultset('CirculationRule')->search()->delete;
690
691     my $default_proc_rule_charge = $builder->build(
692         {
693             source => 'CirculationRule',
694             value  => {
695                 branchcode   => undef,
696                 categorycode => undef,
697                 itemtype     => undef,
698                 rule_name    => 'processingreturn',
699                 rule_value   => 'charge'
700             }
701         }
702     );
703     my $default_lost_rule_charge = $builder->build(
704         {
705             source => 'CirculationRule',
706             value  => {
707                 branchcode   => undef,
708                 categorycode => undef,
709                 itemtype     => undef,
710                 rule_name    => 'lostreturn',
711                 rule_value   => 'charge'
712             }
713         }
714     );
715     my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
716     my $specific_lost_rule_false = $builder->build(
717         {
718             source => 'CirculationRule',
719             value  => {
720                 branchcode   => $branchcode,
721                 categorycode => undef,
722                 itemtype     => undef,
723                 rule_name    => 'lostreturn',
724                 rule_value   => 0
725             }
726         }
727     );
728     my $specific_proc_rule_false = $builder->build(
729         {
730             source => 'CirculationRule',
731             value  => {
732                 branchcode   => $branchcode,
733                 categorycode => undef,
734                 itemtype     => undef,
735                 rule_name    => 'processingreturn',
736                 rule_value   => 0
737             }
738         }
739     );
740     my $branchcode2 = $builder->build( { source => 'Branch' } )->{branchcode};
741     my $specific_lost_rule_refund = $builder->build(
742         {
743             source => 'CirculationRule',
744             value  => {
745                 branchcode   => $branchcode2,
746                 categorycode => undef,
747                 itemtype     => undef,
748                 rule_name    => 'lostreturn',
749                 rule_value   => 'refund'
750             }
751         }
752     );
753     my $specific_proc_rule_refund = $builder->build(
754         {
755             source => 'CirculationRule',
756             value  => {
757                 branchcode   => $branchcode2,
758                 categorycode => undef,
759                 itemtype     => undef,
760                 rule_name    => 'processingreturn',
761                 rule_value   => 'refund'
762             }
763         }
764     );
765     my $branchcode3 = $builder->build( { source => 'Branch' } )->{branchcode};
766     my $specific_lost_rule_restore = $builder->build(
767         {
768             source => 'CirculationRule',
769             value  => {
770                 branchcode   => $branchcode3,
771                 categorycode => undef,
772                 itemtype     => undef,
773                 rule_name    => 'lostreturn',
774                 rule_value   => 'restore'
775             }
776         }
777     );
778     my $specific_proc_rule_restore = $builder->build(
779         {
780             source => 'CirculationRule',
781             value  => {
782                 branchcode   => $branchcode3,
783                 categorycode => undef,
784                 itemtype     => undef,
785                 rule_name    => 'processingreturn',
786                 rule_value   => 'restore'
787             }
788         }
789     );
790
791     # Make sure we have an unused branchcode
792     my $branchcode4 = $builder->build( { source => 'Branch' } )->{branchcode};
793     my $specific_lost_rule_dummy = $builder->build(
794         {
795             source => 'CirculationRule',
796             value  => {
797                 branchcode   => $branchcode4,
798                 categorycode => undef,
799                 itemtype     => undef,
800                 rule_name    => 'lostreturn',
801                 rule_value   => 'refund'
802             }
803         }
804     );
805     my $specific_proc_rule_dummy = $builder->build(
806         {
807             source => 'CirculationRule',
808             value  => {
809                 branchcode   => $branchcode4,
810                 categorycode => undef,
811                 itemtype     => undef,
812                 rule_name    => 'processingreturn',
813                 rule_value   => 'refund'
814             }
815         }
816     );
817     my $branch_without_rule = $specific_lost_rule_dummy->{ branchcode };
818     Koha::CirculationRules
819         ->search(
820             {
821                 branchcode   => $branch_without_rule,
822                 categorycode => undef,
823                 itemtype     => undef,
824                 rule_name    => 'lostreturn',
825                 rule_value   => 'refund'
826             }
827           )
828         ->next
829         ->delete;
830     Koha::CirculationRules
831         ->search(
832             {
833                 branchcode   => $branch_without_rule,
834                 categorycode => undef,
835                 itemtype     => undef,
836                 rule_name    => 'processingreturn',
837                 rule_value   => 'refund'
838             }
839           )
840         ->next
841         ->delete;
842
843     my $item = $builder->build_sample_item(
844         {
845             homebranch    => $specific_lost_rule_restore->{branchcode},
846             holdingbranch => $specific_lost_rule_false->{branchcode}
847         }
848     );
849     my $params = {
850         return_branch => $specific_lost_rule_refund->{ branchcode },
851         item          => $item
852     };
853
854     # Specific rules
855     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
856     is_deeply( Koha::CirculationRules->get_lostreturn_policy( $params ),
857         { lostreturn => 'refund', processingreturn => 'refund' },'Specific rule for checkin branch is applied (refund)');
858
859     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHomeBranch' );
860     is_deeply( Koha::CirculationRules->get_lostreturn_policy( $params ),
861          { lostreturn => 'restore', processingreturn => 'restore' },'Specific rule for home branch is applied (restore)');
862
863     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHoldingBranch' );
864     is_deeply( Koha::CirculationRules->get_lostreturn_policy( $params ),
865          { lostreturn => 0, processingreturn => 0 },'Specific rule for holding branch is applied (false)');
866
867     # Default rule check
868     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
869     $params->{return_branch} = $branch_without_rule;
870     is_deeply( Koha::CirculationRules->get_lostreturn_policy( $params ),
871          { lostreturn => 'charge', processingreturn => 'charge' },'No rule for branch, global rule applied (charge)');
872
873     # Change the default value just to try
874     Koha::CirculationRules->search({ branchcode => undef, rule_name => 'lostreturn' })->next->rule_value(0)->store;
875     Koha::CirculationRules->search({ branchcode => undef, rule_name => 'processingreturn' })->next->rule_value(0)->store;
876     my $memory_cache = Koha::Cache::Memory::Lite->get_instance;
877     $memory_cache->flush();
878     is_deeply( Koha::CirculationRules->get_lostreturn_policy( $params ),
879          { lostreturn => 0, processingreturn => 0 },'No rule for branch, global rule applied (false)');
880
881     # No default rule defined check
882     Koha::CirculationRules
883         ->search(
884             {
885                 branchcode   => undef,
886                 categorycode => undef,
887                 itemtype     => undef,
888                 rule_name    => 'lostreturn'
889             }
890           )
891         ->next
892         ->delete;
893     # No default rule defined check
894     Koha::CirculationRules
895         ->search(
896             {
897                 branchcode   => undef,
898                 categorycode => undef,
899                 itemtype     => undef,
900                 rule_name    => 'processingreturn'
901             }
902           )
903         ->next
904         ->delete;
905     is_deeply( Koha::CirculationRules->get_lostreturn_policy( $params ),
906          { lostreturn => 'refund', processingreturn => 'refund' },'No rule for branch, no default rule, fallback default (refund)');
907
908     # Fallback to ItemHoldBranch if CheckinLibrary is undefined
909     $params->{return_branch} = undef;
910     is_deeply( Koha::CirculationRules->get_lostreturn_policy( $params ),
911          { lostreturn => 'restore', processingreturn => 'restore' },'return_branch undefined, fallback to ItemHomeBranch rule (restore)');
912
913     $schema->storage->txn_rollback;
914 };
915
916 sub _is_row_match {
917     my ( $rule, $expected, $message ) = @_;
918
919     ok( $rule, $message ) ?
920         cmp_methods( $rule, [ %$expected ], $message ) :
921         fail( $message );
922 }
923
924 sub _prepare_tests_for_rule_scope_combinations {
925     my ( $scope, $rule_name ) = @_;
926
927     # Here we create a combinations of 1s and 0s the following way
928     #
929     # 000...
930     # 001...
931     # 010...
932     # 011...
933     # 100...
934     # 101...
935     # 110...
936     # 111...
937     #
938     # (the number of columns equals to the amount of rule scopes)
939     # The ... symbolizes possible future scopes.
940     #
941     # - 0 equals to circulation rule scope with any value (aka. *)
942     # - 1 equals to circulation rule scope exact value, e.g.
943     #     "CPL" (for branchcode).
944     #
945     # The order is the same as the weight of scopes when sorting circulation
946     # rules. So the first column of numbers is the scope with most weight.
947     # This is defined by C<$order> which will be assigned next.
948     #
949     # We must maintain the order in order to keep the test valid. This should be
950     # equal to Koha/CirculationRules.pm "order_by" of C<get_effective_rule> sub.
951     # Let's explicitly define the order and fail test if we are missing a scope:
952     my $order = [ 'branchcode', 'categorycode', 'itemtype' ];
953     is( join(", ", sort keys %$scope),
954        join(", ", sort @$order), 'Missing a scope!' ) if keys %$scope ne scalar @$order;
955
956     my @tests = ();
957     foreach my $value ( glob( "{0,1}" x keys %$scope || 1 ) ) {
958         my $test = { %$scope };
959         for ( my $i=0; $i < keys %$scope; $i++ ) {
960             $test->{$order->[$i]} = '*' unless substr( $value, $i, 1 );
961         }
962         push @tests, $test;
963     }
964
965     return \@tests, $order;
966 }