Bug 26250: Fix tests when SearchEngine=Elastic
[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 => 8;
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 rule matching with different combinations of rule scopes' => sub {
106         my ( $tests, $order ) = prepare_tests_for_rule_scope_combinations(
107             {
108                 branchcode   => $branchcode,
109                 categorycode => $categorycode,
110                 itemtype     => $itemtype,
111             },
112             'maxissueqty'
113         );
114
115         plan tests => 2**scalar @$order;
116
117         foreach my $test (@$tests) {
118             my $rule_params = {%$test};
119             $rule_params->{rule_name} = $rule_name;
120             my $rule_value = $rule_params->{rule_value} = int( rand(10) );
121
122             Koha::CirculationRules->set_rule($rule_params);
123
124             my $rule = Koha::CirculationRules->get_effective_rule(
125                 {
126                     branchcode   => $branchcode,
127                     categorycode => $categorycode,
128                     itemtype     => $itemtype,
129                     rule_name    => $rule_name,
130                 }
131             );
132
133             my $scope_output = '';
134             foreach my $key ( values @$order ) {
135                 $scope_output .= " $key" if $test->{$key} ne '*';
136             }
137
138             is( $rule->rule_value, $rule_value,
139                 'Explicitly scoped'
140                   . ( $scope_output ? $scope_output : ' nothing' ) );
141         }
142     };
143
144     my $our_branch_rules = Koha::CirculationRules->search({branchcode => $branchcode});
145     is( $our_branch_rules->count, 4, "We added 8 rules");
146     $our_branch_rules->delete;
147     is( $our_branch_rules->count, 0, "We deleted 8 rules");
148
149     $schema->storage->txn_rollback;
150 };
151
152 subtest 'get_onshelfholds_policy() tests' => sub {
153
154     plan tests => 2;
155
156     $schema->storage->txn_begin;
157
158     my $item = $builder->build_sample_item();
159
160     my $circ_rules = Koha::CirculationRules->new;
161     # Cleanup
162     $circ_rules->search({ rule_name => 'onshelfholds' })->delete;
163
164     $circ_rules->set_rule(
165         {
166             branchcode   => '*',
167             categorycode => '*',
168             itemtype     => '*',
169             rule_name    => 'onshelfholds',
170             rule_value   => 1,
171         }
172     );
173
174     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 1, 'If rule_value is set on a matching rule, return it' );
175     # Delete the rule (i.e. get_effective_rule returns undef)
176     $circ_rules->delete;
177     is( $circ_rules->get_onshelfholds_policy({ item => $item }), 0, 'If no matching rule, fallback to 0' );
178
179     $schema->storage->txn_rollback;
180 };
181
182 subtest 'get_effective_daysmode' => sub {
183     plan tests => 4;
184
185     $schema->storage->txn_begin;
186
187     my $item_1 = $builder->build_sample_item();
188     my $item_2 = $builder->build_sample_item();
189
190     my $circ_rules =
191       Koha::CirculationRules->search( { rule_name => 'daysmode' } )->delete;
192
193     # Default value 'Datedue' at pref level
194     t::lib::Mocks::mock_preference( 'useDaysMode', 'Datedue' );
195
196     is(
197         Koha::CirculationRules->get_effective_daysmode(
198             {
199                 categorycode => undef,
200                 itemtype     => $item_1->effective_itemtype,
201                 branchcode   => undef
202             }
203         ),
204         'Datedue',
205         'daysmode default to pref value if the rule does not exist'
206     );
207
208     Koha::CirculationRules->set_rule(
209         {
210             branchcode   => '*',
211             categorycode => '*',
212             itemtype     => '*',
213             rule_name    => 'daysmode',
214             rule_value   => 'Calendar',
215         }
216     );
217     Koha::CirculationRules->set_rule(
218         {
219             branchcode   => '*',
220             categorycode => '*',
221             itemtype     => $item_1->effective_itemtype,
222             rule_name    => 'daysmode',
223             rule_value   => 'Days',
224         }
225     );
226
227     is(
228         Koha::CirculationRules->get_effective_daysmode(
229             {
230                 categorycode => undef,
231                 itemtype     => $item_1->effective_itemtype,
232                 branchcode   => undef
233             }
234         ),
235         'Days',
236         "daysmode for item_1 is the specific rule"
237     );
238     is(
239         Koha::CirculationRules->get_effective_daysmode(
240             {
241                 categorycode => undef,
242                 itemtype     => $item_2->effective_itemtype,
243                 branchcode   => undef
244             }
245         ),
246         'Calendar',
247         "daysmode for item_2 is the one defined for the default circ rule"
248     );
249
250     Koha::CirculationRules->set_rule(
251         {
252             branchcode   => '*',
253             categorycode => '*',
254             itemtype     => $item_2->effective_itemtype,
255             rule_name    => 'daysmode',
256             rule_value   => '',
257         }
258     );
259
260     is(
261         Koha::CirculationRules->get_effective_daysmode(
262             {
263                 categorycode => undef,
264                 itemtype     => $item_2->effective_itemtype,
265                 branchcode   => undef
266             }
267         ),
268         'Datedue',
269         'daysmode default to pref value if the rule exists but set to""'
270     );
271
272     $schema->storage->txn_rollback;
273 };
274
275 subtest 'get_lostreturn_policy() tests' => sub {
276     plan tests => 7;
277
278     $schema->storage->txn_begin;
279
280     $schema->resultset('CirculationRule')->search()->delete;
281
282     my $default_rule = $builder->build(
283         {
284             source => 'CirculationRule',
285             value  => {
286                 branchcode   => undef,
287                 categorycode => undef,
288                 itemtype     => undef,
289                 rule_name    => 'refund',
290                 rule_value   => 1
291             }
292         }
293     );
294     my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
295     my $specific_rule_false = $builder->build(
296         {
297             source => 'CirculationRule',
298             value  => {
299                 branchcode   => $branchcode,
300                 categorycode => undef,
301                 itemtype     => undef,
302                 rule_name    => 'refund',
303                 rule_value   => 0
304             }
305         }
306     );
307     my $branchcode2 = $builder->build( { source => 'Branch' } )->{branchcode};
308     my $specific_rule_true = $builder->build(
309         {
310             source => 'CirculationRule',
311             value  => {
312                 branchcode   => $branchcode2,
313                 categorycode => undef,
314                 itemtype     => undef,
315                 rule_name    => 'refund',
316                 rule_value   => 1
317             }
318         }
319     );
320     # Make sure we have an unused branchcode
321     my $branchcode3 = $builder->build( { source => 'Branch' } )->{branchcode};
322     my $specific_rule_dummy = $builder->build(
323         {
324             source => 'CirculationRule',
325             value  => {
326                 branchcode   => $branchcode3,
327                 categorycode => undef,
328                 itemtype     => undef,
329                 rule_name    => 'refund',
330             }
331         }
332     );
333     my $branch_without_rule = $specific_rule_dummy->{ branchcode };
334     Koha::CirculationRules
335         ->search(
336             {
337                 branchcode   => $branch_without_rule,
338                 categorycode => undef,
339                 itemtype     => undef,
340                 rule_name    => 'refund'
341             }
342           )
343         ->next
344         ->delete;
345
346     my $item = $builder->build_sample_item(
347         {
348             homebranch    => $specific_rule_false->{branchcode},
349             holdingbranch => $specific_rule_true->{branchcode}
350         }
351     );
352     my $params = {
353         return_branch => $specific_rule_true->{ branchcode },
354         item          => $item
355     };
356
357     # Specific rules
358     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
359     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
360           1,'Specific rule for checkin branch is applied (true)');
361
362     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHomeBranch' );
363     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
364          0,'Specific rule for home branch is applied (false)');
365
366     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'ItemHoldingBranch' );
367     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
368          1,'Specific rule for holding branch is applied (true)');
369
370     # Default rule check
371     t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl', 'CheckinLibrary' );
372     $params->{return_branch} = $branch_without_rule;
373     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
374          1,'No rule for branch, global rule applied (true)');
375
376     # Change the default value just to try
377     Koha::CirculationRules->search({ branchcode => undef, rule_name => 'refund' })->next->rule_value(0)->store;
378     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
379          0,'No rule for branch, global rule applied (false)');
380
381     # No default rule defined check
382     Koha::CirculationRules
383         ->search(
384             {
385                 branchcode   => undef,
386                 categorycode => undef,
387                 itemtype     => undef,
388                 rule_name    => 'refund'
389             }
390           )
391         ->next
392         ->delete;
393     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
394          1,'No rule for branch, no default rule, fallback default (true)');
395
396     # Fallback to ItemHoldBranch if CheckinLibrary is undefined
397     $params->{return_branch} = undef;
398     is( Koha::CirculationRules->get_lostreturn_policy( $params ),
399          0,'return_branch undefined, fallback to ItemHomeBranch rule (false)');
400
401     $schema->storage->txn_rollback;
402 };
403
404 sub prepare_tests_for_rule_scope_combinations {
405     my ( $scope, $rule_name ) = @_;
406
407     # Here we create a combinations of 1s and 0s the following way
408     #
409     # 000...
410     # 001...
411     # 010...
412     # 011...
413     # 100...
414     # 101...
415     # 110...
416     # 111...
417     #
418     # (the number of columns equals to the amount of rule scopes)
419     # The ... symbolizes possible future scopes.
420     #
421     # - 0 equals to circulation rule scope with any value (aka. *)
422     # - 1 equals to circulation rule scope exact value, e.g.
423     #     "CPL" (for branchcode).
424     #
425     # The order is the same as the weight of scopes when sorting circulation
426     # rules. So the first column of numbers is the scope with most weight.
427     # This is defined by C<$order> which will be assigned next.
428     #
429     # We must maintain the order in order to keep the test valid. This should be
430     # equal to Koha/CirculationRules.pm "order_by" of C<get_effective_rule> sub.
431     # Let's explicitly define the order and fail test if we are missing a scope:
432     my $order = [ 'branchcode', 'categorycode', 'itemtype' ];
433     is( join(", ", sort keys %$scope),
434        join(", ", sort @$order), 'Missing a scope!' ) if keys %$scope ne scalar @$order;
435
436     my @tests = ();
437     foreach my $value ( glob( "{0,1}" x keys %$scope || 1 ) ) {
438         my $test = { %$scope };
439         for ( my $i=0; $i < keys %$scope; $i++ ) {
440             $test->{$order->[$i]} = '*' unless substr( $value, $i, 1 );
441         }
442         push @tests, $test;
443     }
444
445     return \@tests, $order;
446 }