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