Bug 19532: (follow-up) aria-hidden attr on OPAC, and more
[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 Test::More tests => 4;
23 use Test::Exception;
24
25 use Koha::CirculationRules;
26 use Koha::Database;
27
28 use t::lib::Mocks;
29 use t::lib::TestBuilder;
30 use t::lib::Mocks;
31
32 my $schema = Koha::Database->new->schema;
33 my $builder = t::lib::TestBuilder->new;
34
35 subtest 'set_rule + get_effective_rule' => sub {
36     plan tests => 9;
37
38     $schema->storage->txn_begin;
39
40     my $categorycode = $builder->build_object( { class => 'Koha::Patron::Categories' } )->categorycode;
41     my $itemtype     = $builder->build_object( { class => 'Koha::ItemTypes' } )->itemtype;
42     my $branchcode   = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
43     my $branchcode_2 = $builder->build_object( { class => 'Koha::Libraries' } )->branchcode;
44     my $rule_name    = 'maxissueqty';
45     my $default_rule_value = 1;
46
47     my $rule;
48     Koha::CirculationRules->delete;
49
50     throws_ok { Koha::CirculationRules->get_effective_rule }
51     'Koha::Exceptions::MissingParameter',
52     "Exception should be raised if get_effective_rule is called without rule_name parameter";
53
54     $rule = Koha::CirculationRules->get_effective_rule(
55         {
56             branchcode   => $branchcode,
57             categorycode => $categorycode,
58             itemtype     => $itemtype,
59             rule_name    => $rule_name,
60         }
61     );
62     is( $rule, undef, 'Undef should be returned if no rule exist' );
63
64     Koha::CirculationRules->set_rule(
65         {
66             branchcode   => '*',
67             categorycode => '*',
68             itemtype     => '*',
69             rule_name    => $rule_name,
70             rule_value   => $default_rule_value,
71         }
72     );
73
74     $rule = Koha::CirculationRules->get_effective_rule(
75         {
76             branchcode   => undef,
77             categorycode => undef,
78             itemtype     => undef,
79             rule_name    => $rule_name,
80         }
81     );
82     is( $rule->rule_value, $default_rule_value, 'undef means default' );
83     $rule = Koha::CirculationRules->get_effective_rule(
84         {
85             branchcode   => '*',
86             categorycode => '*',
87             itemtype     => '*',
88             rule_name    => $rule_name,
89         }
90     );
91
92     is( $rule->rule_value, $default_rule_value, '* means default' );
93
94     $rule = Koha::CirculationRules->get_effective_rule(
95         {
96             branchcode   => $branchcode_2,
97             categorycode => '*',
98             itemtype     => '*',
99             rule_name    => $rule_name,
100         }
101     );
102     is( $rule->rule_value, 1,
103         'Default rule is returned if there is no rule for this branchcode' );
104
105     subtest 'test rules that cannot be blank' => sub {
106         plan tests => 3;
107         foreach my $no_blank_rule ( ('holdallowed','hold_fulfillment_policy','returnbranch') ){
108             Koha::CirculationRules->set_rule(
109                 {
110                     branchcode   => $branchcode,
111                     itemtype     => '*',
112                     rule_name    => $no_blank_rule,
113                     rule_value   => '',
114                 }
115             );
116
117             $rule = Koha::CirculationRules->get_effective_rule(
118                 {
119                     branchcode   => $branchcode,
120                     categorycode => undef,
121                     itemtype     => undef,
122                     rule_name    => $no_blank_rule,
123                 }
124             );
125             is( $rule, undef, 'Rules that cannot be blank are not set when passed blank string' );
126         }
127     };
128
129
130     subtest 'test rule matching with different combinations of rule scopes' => sub {
131         my ( $tests, $order ) = prepare_tests_for_rule_scope_combinations(
132             {
133                 branchcode   => $branchcode,
134                 categorycode => $categorycode,
135                 itemtype     => $itemtype,
136             },
137             'maxissueqty'
138         );
139
140         plan tests => 2**scalar @$order;
141
142         foreach my $test (@$tests) {
143             my $rule_params = {%$test};
144             $rule_params->{rule_name} = $rule_name;
145             my $rule_value = $rule_params->{rule_value} = int( rand(10) );
146
147             Koha::CirculationRules->set_rule($rule_params);
148
149             my $rule = Koha::CirculationRules->get_effective_rule(
150                 {
151                     branchcode   => $branchcode,
152                     categorycode => $categorycode,
153                     itemtype     => $itemtype,
154                     rule_name    => $rule_name,
155                 }
156             );
157
158             my $scope_output = '';
159             foreach my $key ( values @$order ) {
160                 $scope_output .= " $key" if $test->{$key} ne '*';
161             }
162
163             is( $rule->rule_value, $rule_value,
164                 'Explicitly scoped'
165                   . ( $scope_output ? $scope_output : ' nothing' ) );
166         }
167     };
168
169     my $our_branch_rules = Koha::CirculationRules->search({branchcode => $branchcode});
170     is( $our_branch_rules->count, 4, "We added 8 rules");
171     $our_branch_rules->delete;
172     is( $our_branch_rules->count, 0, "We deleted 8 rules");
173
174     $schema->storage->txn_rollback;
175 };
176
177 subtest 'get_onshelfholds_policy() tests' => sub {
178
179     plan tests => 2;
180
181     $schema->storage->txn_begin;
182
183     my $item = $builder->build_sample_item();
184
185     my $circ_rules = Koha::CirculationRules->new;
186     # Cleanup
187     $circ_rules->search({ rule_name => 'onshelfholds' })->delete;
188
189     $circ_rules->set_rule(
190         {
191             branchcode   => '*',
192             categorycode => '*',
193             itemtype     => '*',
194             rule_name    => 'onshelfholds',
195             rule_value   => 1,
196         }
197     );
198
199     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 1, 'If rule_value is set on a matching rule, return it' );
200     # Delete the rule (i.e. get_effective_rule returns undef)
201     $circ_rules->delete;
202     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 0, 'If no matching rule, fallback to 0' );
203
204     $schema->storage->txn_rollback;
205 };
206
207 subtest 'get_effective_daysmode' => sub {
208     plan tests => 4;
209
210     $schema->storage->txn_begin;
211
212     my $item_1 = $builder->build_sample_item();
213     my $item_2 = $builder->build_sample_item();
214
215     my $circ_rules =
216       Koha::CirculationRules->search( { rule_name => 'daysmode' } )->delete;
217
218     # Default value 'Datedue' at pref level
219     t::lib::Mocks::mock_preference( 'useDaysMode', 'Datedue' );
220
221     is(
222         Koha::CirculationRules->get_effective_daysmode(
223             {
224                 categorycode => undef,
225                 itemtype     => $item_1->effective_itemtype,
226                 branchcode   => undef
227             }
228         ),
229         'Datedue',
230         'daysmode default to pref value if the rule does not exist'
231     );
232
233     Koha::CirculationRules->set_rule(
234         {
235             branchcode   => '*',
236             categorycode => '*',
237             itemtype     => '*',
238             rule_name    => 'daysmode',
239             rule_value   => 'Calendar',
240         }
241     );
242     Koha::CirculationRules->set_rule(
243         {
244             branchcode   => '*',
245             categorycode => '*',
246             itemtype     => $item_1->effective_itemtype,
247             rule_name    => 'daysmode',
248             rule_value   => 'Days',
249         }
250     );
251
252     is(
253         Koha::CirculationRules->get_effective_daysmode(
254             {
255                 categorycode => undef,
256                 itemtype     => $item_1->effective_itemtype,
257                 branchcode   => undef
258             }
259         ),
260         'Days',
261         "daysmode for item_1 is the specific rule"
262     );
263     is(
264         Koha::CirculationRules->get_effective_daysmode(
265             {
266                 categorycode => undef,
267                 itemtype     => $item_2->effective_itemtype,
268                 branchcode   => undef
269             }
270         ),
271         'Calendar',
272         "daysmode for item_2 is the one defined for the default circ rule"
273     );
274
275     Koha::CirculationRules->set_rule(
276         {
277             branchcode   => '*',
278             categorycode => '*',
279             itemtype     => $item_2->effective_itemtype,
280             rule_name    => 'daysmode',
281             rule_value   => '',
282         }
283     );
284
285     is(
286         Koha::CirculationRules->get_effective_daysmode(
287             {
288                 categorycode => undef,
289                 itemtype     => $item_2->effective_itemtype,
290                 branchcode   => undef
291             }
292         ),
293         'Datedue',
294         'daysmode default to pref value if the rule exists but set to""'
295     );
296
297     $schema->storage->txn_rollback;
298 };
299
300 subtest 'get_lostreturn_policy() tests' => sub {
301     plan tests => 7;
302
303     $schema->storage->txn_begin;
304
305     $schema->resultset('CirculationRule')->search()->delete;
306
307     my $default_rule_charge = $builder->build(
308         {
309             source => 'CirculationRule',
310             value  => {
311                 branchcode   => undef,
312                 categorycode => undef,
313                 itemtype     => undef,
314                 rule_name    => 'lostreturn',
315                 rule_value   => 'charge'
316             }
317         }
318     );
319     my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
320     my $specific_rule_false = $builder->build(
321         {
322             source => 'CirculationRule',
323             value  => {
324                 branchcode   => $branchcode,
325                 categorycode => undef,
326                 itemtype     => undef,
327                 rule_name    => 'lostreturn',
328                 rule_value   => 0
329             }
330         }
331     );
332     my $branchcode2 = $builder->build( { source => 'Branch' } )->{branchcode};
333     my $specific_rule_refund = $builder->build(
334         {
335             source => 'CirculationRule',
336             value  => {
337                 branchcode   => $branchcode2,
338                 categorycode => undef,
339                 itemtype     => undef,
340                 rule_name    => 'lostreturn',
341                 rule_value   => 'refund'
342             }
343         }
344     );
345     my $branchcode3 = $builder->build( { source => 'Branch' } )->{branchcode};
346     my $specific_rule_restore = $builder->build(
347         {
348             source => 'CirculationRule',
349             value  => {
350                 branchcode   => $branchcode3,
351                 categorycode => undef,
352                 itemtype     => undef,
353                 rule_name    => 'lostreturn',
354                 rule_value   => 'restore'
355             }
356         }
357     );
358
359     # Make sure we have an unused branchcode
360     my $branchcode4 = $builder->build( { source => 'Branch' } )->{branchcode};
361     my $specific_rule_dummy = $builder->build(
362         {
363             source => 'CirculationRule',
364             value  => {
365                 branchcode   => $branchcode4,
366                 categorycode => undef,
367                 itemtype     => undef,
368                 rule_name    => 'lostreturn',
369                 rule_value   => 'refund'
370             }
371         }
372     );
373     my $branch_without_rule = $specific_rule_dummy->{ branchcode };
374     Koha::CirculationRules
375         ->search(
376             {
377                 branchcode   => $branch_without_rule,
378                 categorycode => undef,
379                 itemtype     => undef,
380                 rule_name    => 'lostreturn',
381                 rule_value   => 'refund'
382             }
383           )
384         ->next
385         ->delete;
386
387     my $item = $builder->build_sample_item(
388         {
389             homebranch    => $specific_rule_restore->{branchcode},
390             holdingbranch => $specific_rule_false->{branchcode}
391         }
392     );
393     my $params = {
394         return_branch => $specific_rule_refund->{ branchcode },
395         item          => $item
396     };
397
398     # Specific rules
399     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
400     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
401         'refund','Specific rule for checkin branch is applied (refund)');
402
403     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHomeBranch' );
404     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
405          'restore','Specific rule for home branch is applied (restore)');
406
407     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHoldingBranch' );
408     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
409          0,'Specific rule for holding branch is applied (false)');
410
411     # Default rule check
412     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
413     $params->{return_branch} = $branch_without_rule;
414     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
415          'charge','No rule for branch, global rule applied (charge)');
416
417     # Change the default value just to try
418     Koha::CirculationRules->search({ branchcode => undef, rule_name => 'lostreturn' })->next->rule_value(0)->store;
419     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
420          0,'No rule for branch, global rule applied (false)');
421
422     # No default rule defined check
423     Koha::CirculationRules
424         ->search(
425             {
426                 branchcode   => undef,
427                 categorycode => undef,
428                 itemtype     => undef,
429                 rule_name    => 'lostreturn'
430             }
431           )
432         ->next
433         ->delete;
434     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
435          'refund','No rule for branch, no default rule, fallback default (refund)');
436
437     # Fallback to ItemHoldBranch if CheckinLibrary is undefined
438     $params->{return_branch} = undef;
439     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
440          'restore','return_branch undefined, fallback to ItemHomeBranch rule (restore)');
441
442     $schema->storage->txn_rollback;
443 };
444
445 sub prepare_tests_for_rule_scope_combinations {
446     my ( $scope, $rule_name ) = @_;
447
448     # Here we create a combinations of 1s and 0s the following way
449     #
450     # 000...
451     # 001...
452     # 010...
453     # 011...
454     # 100...
455     # 101...
456     # 110...
457     # 111...
458     #
459     # (the number of columns equals to the amount of rule scopes)
460     # The ... symbolizes possible future scopes.
461     #
462     # - 0 equals to circulation rule scope with any value (aka. *)
463     # - 1 equals to circulation rule scope exact value, e.g.
464     #     "CPL" (for branchcode).
465     #
466     # The order is the same as the weight of scopes when sorting circulation
467     # rules. So the first column of numbers is the scope with most weight.
468     # This is defined by C<$order> which will be assigned next.
469     #
470     # We must maintain the order in order to keep the test valid. This should be
471     # equal to Koha/CirculationRules.pm "order_by" of C<get_effective_rule> sub.
472     # Let's explicitly define the order and fail test if we are missing a scope:
473     my $order = [ 'branchcode', 'categorycode', 'itemtype' ];
474     is( join(", ", sort keys %$scope),
475        join(", ", sort @$order), 'Missing a scope!' ) if keys %$scope ne scalar @$order;
476
477     my @tests = ();
478     foreach my $value ( glob( "{0,1}" x keys %$scope || 1 ) ) {
479         my $test = { %$scope };
480         for ( my $i=0; $i < keys %$scope; $i++ ) {
481             $test->{$order->[$i]} = '*' unless substr( $value, $i, 1 );
482         }
483         push @tests, $test;
484     }
485
486     return \@tests, $order;
487 }