Bug 11998: Use t::lib::Mocks::mock_preference in tests
[koha.git] / t / db_dependent / Items.t
1 #!/usr/bin/perl
2 #
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 2 of the License, or (at your option) any later
8 # version.
9 #
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with Koha; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 #
18
19 use Modern::Perl;
20
21 use MARC::Record;
22 use C4::Biblio;
23 use Koha::Database;
24 use Koha::Library;
25
26 use t::lib::Mocks;
27 use t::lib::TestBuilder;
28
29 use Test::More tests => 9;
30
31 use Test::Warn;
32
33 BEGIN {
34     use_ok('C4::Items');
35     use_ok('Koha::Items');
36 }
37
38 my $schema = Koha::Database->new->schema;
39 my $location = 'My Location';
40
41 subtest 'General Add, Get and Del tests' => sub {
42
43     plan tests => 14;
44
45     $schema->storage->txn_begin;
46
47     my $builder = t::lib::TestBuilder->new;
48     my $library = $builder->build({
49         source => 'Branch',
50     });
51
52     # Create a biblio instance for testing
53     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
54     my ($bibnum, $bibitemnum) = get_biblio();
55
56     # Add an item.
57     my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $library->{branchcode}, holdingbranch => $library->{branchcode}, location => $location } , $bibnum);
58     cmp_ok($item_bibnum, '==', $bibnum, "New item is linked to correct biblionumber.");
59     cmp_ok($item_bibitemnum, '==', $bibitemnum, "New item is linked to correct biblioitemnumber.");
60
61     # Get item.
62     my $getitem = GetItem($itemnumber);
63     cmp_ok($getitem->{'itemnumber'}, '==', $itemnumber, "Retrieved item has correct itemnumber.");
64     cmp_ok($getitem->{'biblioitemnumber'}, '==', $item_bibitemnum, "Retrieved item has correct biblioitemnumber.");
65     is( $getitem->{location}, $location, "The location should not have been modified" );
66     is( $getitem->{permanent_location}, $location, "The permanent_location should have been set to the location value" );
67
68     # Modify item; setting barcode.
69     ModItem({ barcode => '987654321' }, $bibnum, $itemnumber);
70     my $moditem = GetItem($itemnumber);
71     cmp_ok($moditem->{'barcode'}, '==', '987654321', 'Modified item barcode successfully to: '.$moditem->{'barcode'} . '.');
72
73     # Delete item.
74     DelItem({ biblionumber => $bibnum, itemnumber => $itemnumber });
75     my $getdeleted = GetItem($itemnumber);
76     is($getdeleted->{'itemnumber'}, undef, "Item deleted as expected.");
77
78     ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $library->{branchcode}, holdingbranch => $library->{branchcode}, location => $location, permanent_location => 'my permanent location' } , $bibnum);
79     $getitem = GetItem($itemnumber);
80     is( $getitem->{location}, $location, "The location should not have been modified" );
81     is( $getitem->{permanent_location}, 'my permanent location', "The permanent_location should not have modified" );
82
83     ModItem({ location => $location }, $bibnum, $itemnumber);
84     $getitem = GetItem($itemnumber);
85     is( $getitem->{location}, $location, "The location should have been set to correct location" );
86     is( $getitem->{permanent_location}, $location, "The permanent_location should have been set to location" );
87
88     ModItem({ location => 'CART' }, $bibnum, $itemnumber);
89     $getitem = GetItem($itemnumber);
90     is( $getitem->{location}, 'CART', "The location should have been set to CART" );
91     is( $getitem->{permanent_location}, $location, "The permanent_location should not have been set to CART" );
92
93     $schema->storage->txn_rollback;
94 };
95
96 subtest 'GetHiddenItemnumbers tests' => sub {
97
98     plan tests => 9;
99
100     # This sub is controlled by the OpacHiddenItems system preference.
101
102     $schema->storage->txn_begin;
103
104     my $builder = t::lib::TestBuilder->new;
105     my $library1 = $builder->build({
106         source => 'Branch',
107     });
108
109     my $library2 = $builder->build({
110         source => 'Branch',
111     });
112
113     # Create a new biblio
114     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
115     my ($biblionumber, $biblioitemnumber) = get_biblio();
116
117     # Add two items
118     my ($item1_bibnum, $item1_bibitemnum, $item1_itemnumber) = AddItem(
119             { homebranch => $library1->{branchcode},
120               holdingbranch => $library1->{branchcode},
121               withdrawn => 1 },
122             $biblionumber
123     );
124     my ($item2_bibnum, $item2_bibitemnum, $item2_itemnumber) = AddItem(
125             { homebranch => $library2->{branchcode},
126               holdingbranch => $library2->{branchcode},
127               withdrawn => 0 },
128             $biblionumber
129     );
130
131     my $opachiddenitems;
132     my @itemnumbers = ($item1_itemnumber,$item2_itemnumber);
133     my @hidden;
134     my @items;
135     push @items, GetItem( $item1_itemnumber );
136     push @items, GetItem( $item2_itemnumber );
137
138     # Empty OpacHiddenItems
139     t::lib::Mocks::mock_preference('OpacHiddenItems','');
140     ok( !defined( GetHiddenItemnumbers( @items ) ),
141         "Hidden items list undef if OpacHiddenItems empty");
142
143     # Blank spaces
144     t::lib::Mocks::mock_preference('OpacHiddenItems','  ');
145     ok( scalar GetHiddenItemnumbers( @items ) == 0,
146         "Hidden items list empty if OpacHiddenItems only contains blanks");
147
148     # One variable / value
149     $opachiddenitems = "
150         withdrawn: [1]";
151     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
152     @hidden = GetHiddenItemnumbers( @items );
153     ok( scalar @hidden == 1, "Only one hidden item");
154     is( $hidden[0], $item1_itemnumber, "withdrawn=1 is hidden");
155
156     # One variable, two values
157     $opachiddenitems = "
158         withdrawn: [1,0]";
159     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
160     @hidden = GetHiddenItemnumbers( @items );
161     ok( scalar @hidden == 2, "Two items hidden");
162     is_deeply( \@hidden, \@itemnumbers, "withdrawn=1 and withdrawn=0 hidden");
163
164     # Two variables, a value each
165     $opachiddenitems = "
166         withdrawn: [1]
167         homebranch: [$library2->{branchcode}]
168     ";
169     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
170     @hidden = GetHiddenItemnumbers( @items );
171     ok( scalar @hidden == 2, "Two items hidden");
172     is_deeply( \@hidden, \@itemnumbers, "withdrawn=1 and homebranch library2 hidden");
173
174     # Valid OpacHiddenItems, empty list
175     @items = ();
176     @hidden = GetHiddenItemnumbers( @items );
177     ok( scalar @hidden == 0, "Empty items list, no item hidden");
178
179     $schema->storage->txn_rollback;
180 };
181
182 subtest 'GetItemsInfo tests' => sub {
183
184     plan tests => 4;
185
186     $schema->storage->txn_begin;
187
188     my $builder = t::lib::TestBuilder->new;
189     my $library1 = $builder->build({
190         source => 'Branch',
191     });
192     my $library2 = $builder->build({
193         source => 'Branch',
194     });
195
196     # Add a biblio
197     my ($biblionumber, $biblioitemnumber) = get_biblio();
198     # Add an item
199     my ($item_bibnum, $item_bibitemnum, $itemnumber)
200         = AddItem({
201                 homebranch    => $library1->{branchcode},
202                 holdingbranch => $library2->{branchcode},
203             }, $biblionumber );
204
205     my $library = Koha::Libraries->find( $library1->{branchcode} );
206     $library->opac_info("homebranch OPAC info");
207     $library->store;
208
209     $library = Koha::Libraries->find( $library2->{branchcode} );
210     $library->opac_info("holdingbranch OPAC info");
211     $library->store;
212
213     my @results = GetItemsInfo( $biblionumber );
214     ok( @results, 'GetItemsInfo returns results');
215     is( $results[0]->{ home_branch_opac_info }, "homebranch OPAC info",
216         'GetItemsInfo returns the correct home branch OPAC info notice' );
217     is( $results[0]->{ holding_branch_opac_info }, "holdingbranch OPAC info",
218         'GetItemsInfo returns the correct holding branch OPAC info notice' );
219     is( exists( $results[0]->{ onsite_checkout } ), 1,
220         'GetItemsInfo returns a onsite_checkout key' );
221
222     $schema->storage->txn_rollback;
223 };
224
225 subtest q{Test Koha::Database->schema()->resultset('Item')->itemtype()} => sub {
226
227     plan tests => 4;
228
229     $schema->storage->txn_begin;
230
231     my $biblio =
232     $schema->resultset('Biblio')->create(
233         {
234             title       => "Test title",
235             biblioitems => [
236                 {
237                     itemtype => 'BIB_LEVEL',
238                     items    => [ { itype => "ITEM_LEVEL" } ]
239                 }
240             ]
241         }
242     );
243
244     my @bi = $biblio->biblioitems();
245     my ( $item ) = $bi[0]->items();
246
247     t::lib::Mocks::mock_preference( 'item-level_itypes', 0 );
248     ok( $item->effective_itemtype() eq 'BIB_LEVEL', '$item->itemtype() returns biblioitem.itemtype when item-level_itypes is disabled' );
249
250     t::lib::Mocks::mock_preference( 'item-level_itypes', 1 );
251     ok( $item->effective_itemtype() eq 'ITEM_LEVEL', '$item->itemtype() returns items.itype when item-level_itypes is enabled' );
252
253     # If itemtype is not defined and item-level_level item types are set
254     # fallback to biblio-level itemtype (Bug 14651) and warn
255     $item->itype( undef );
256     $item->update();
257     my $effective_itemtype;
258     warning_is { $effective_itemtype = $item->effective_itemtype() }
259                 "item-level_itypes set but no itemtype set for item ($item->itemnumber)",
260                 '->effective_itemtype() raises a warning when falling back to bib-level';
261
262     ok( defined $effective_itemtype &&
263                 $effective_itemtype eq 'BIB_LEVEL',
264         '$item->effective_itemtype() falls back to biblioitems.itemtype when item-level_itypes is enabled but undef' );
265
266     $schema->storage->txn_rollback;
267 };
268
269 subtest 'SearchItems test' => sub {
270     plan tests => 14;
271
272     $schema->storage->txn_begin;
273     my $dbh = C4::Context->dbh;
274     my $builder = t::lib::TestBuilder->new;
275
276     my $library1 = $builder->build({
277         source => 'Branch',
278     });
279     my $library2 = $builder->build({
280         source => 'Branch',
281     });
282
283     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
284     my $cpl_items_before = SearchItemsByField( 'homebranch', $library1->{branchcode});
285
286     my ($biblionumber) = get_biblio();
287
288     my (undef, $initial_items_count) = SearchItems(undef, {rows => 1});
289
290     # Add two items
291     my (undef, undef, $item1_itemnumber) = AddItem({
292         homebranch => $library1->{branchcode},
293         holdingbranch => $library1->{branchcode},
294     }, $biblionumber);
295     my (undef, undef, $item2_itemnumber) = AddItem({
296         homebranch => $library2->{branchcode},
297         holdingbranch => $library2->{branchcode},
298     }, $biblionumber);
299
300     my ($items, $total_results);
301
302     ($items, $total_results) = SearchItems();
303     is($total_results, $initial_items_count + 2, "Created 2 new items");
304     is(scalar @$items, $total_results, "SearchItems() returns all items");
305
306     ($items, $total_results) = SearchItems(undef, {rows => 1});
307     is($total_results, $initial_items_count + 2);
308     is(scalar @$items, 1, "SearchItems(undef, {rows => 1}) returns only 1 item");
309
310     # Search all items where homebranch = 'CPL'
311     my $filter = {
312         field => 'homebranch',
313         query => $library1->{branchcode},
314         operator => '=',
315     };
316     ($items, $total_results) = SearchItems($filter);
317     ok($total_results > 0, "There is at least one CPL item");
318     my $all_items_are_CPL = 1;
319     foreach my $item (@$items) {
320         if ($item->{homebranch} ne $library1->{branchcode}) {
321             $all_items_are_CPL = 0;
322             last;
323         }
324     }
325     ok($all_items_are_CPL, "All items returned by SearchItems are from CPL");
326
327     # Search all items where homebranch != 'CPL'
328     $filter = {
329         field => 'homebranch',
330         query => $library1->{branchcode},
331         operator => '!=',
332     };
333     ($items, $total_results) = SearchItems($filter);
334     ok($total_results > 0, "There is at least one non-CPL item");
335     my $all_items_are_not_CPL = 1;
336     foreach my $item (@$items) {
337         if ($item->{homebranch} eq $library1->{branchcode}) {
338             $all_items_are_not_CPL = 0;
339             last;
340         }
341     }
342     ok($all_items_are_not_CPL, "All items returned by SearchItems are not from CPL");
343
344     # Search all items where biblio title (245$a) is like 'Silence in the %'
345     $filter = {
346         field => 'marc:245$a',
347         query => 'Silence in the %',
348         operator => 'like',
349     };
350     ($items, $total_results) = SearchItems($filter);
351     ok($total_results >= 2, "There is at least 2 items with a biblio title like 'Silence in the %'");
352
353     # Search all items where biblio title is 'Silence in the library'
354     # and homebranch is 'CPL'
355     $filter = {
356         conjunction => 'AND',
357         filters => [
358             {
359                 field => 'marc:245$a',
360                 query => 'Silence in the %',
361                 operator => 'like',
362             },
363             {
364                 field => 'homebranch',
365                 query => $library1->{branchcode},
366                 operator => '=',
367             },
368         ],
369     };
370     ($items, $total_results) = SearchItems($filter);
371     my $found = 0;
372     foreach my $item (@$items) {
373         if ($item->{itemnumber} == $item1_itemnumber) {
374             $found = 1;
375             last;
376         }
377     }
378     ok($found, "item1 found");
379
380     my ($itemfield) = GetMarcFromKohaField('items.itemnumber', '');
381
382     # Create item subfield 'z' without link
383     $dbh->do('DELETE FROM marc_subfield_structure WHERE tagfield=? AND tagsubfield="z" AND frameworkcode=""', undef, $itemfield);
384     $dbh->do('INSERT INTO marc_subfield_structure (tagfield, tagsubfield, frameworkcode) VALUES (?, "z", "")', undef, $itemfield);
385
386     # Clear cache
387     $C4::Context::context->{marcfromkohafield} = undef;
388     $C4::Biblio::inverted_field_map = undef;
389
390     my $item3_record = new MARC::Record;
391     $item3_record->append_fields(
392         new MARC::Field($itemfield, '', '', 'z' => 'foobar')
393     );
394     my (undef, undef, $item3_itemnumber) = AddItemFromMarc($item3_record,
395         $biblionumber);
396
397     # Search item where item subfield z is "foobar"
398     $filter = {
399         field => 'marc:' . $itemfield . '$z',
400         query => 'foobar',
401         operator => 'like',
402     };
403     ($items, $total_results) = SearchItems($filter);
404     ok(scalar @$items == 1, 'found 1 item with $z = "foobar"');
405
406     # Link $z to items.itemnotes (and make sure there is no other subfields
407     # linked to it)
408     $dbh->do('DELETE FROM marc_subfield_structure WHERE kohafield="items.itemnotes" AND frameworkcode=""', undef, $itemfield);
409     $dbh->do('UPDATE marc_subfield_structure SET kohafield="items.itemnotes" WHERE tagfield=? AND tagsubfield="z" AND frameworkcode=""', undef, $itemfield);
410
411     # Clear cache
412     $C4::Context::context->{marcfromkohafield} = undef;
413     $C4::Biblio::inverted_field_map = undef;
414
415     ModItemFromMarc($item3_record, $biblionumber, $item3_itemnumber);
416
417     # Make sure the link is used
418     my $item3 = GetItem($item3_itemnumber);
419     ok($item3->{itemnotes} eq 'foobar', 'itemnotes eq "foobar"');
420
421     # Do the same search again.
422     # This time it will search in items.itemnotes
423     ($items, $total_results) = SearchItems($filter);
424     ok(scalar @$items == 1, 'found 1 item with itemnotes = "foobar"');
425
426     my $cpl_items_after = SearchItemsByField( 'homebranch', $library1->{branchcode});
427     is( ( scalar( @$cpl_items_after ) - scalar ( @$cpl_items_before ) ), 1, 'SearchItemsByField should return something' );
428
429     $schema->storage->txn_rollback;
430 };
431
432 subtest 'Koha::Item(s) tests' => sub {
433
434     plan tests => 5;
435
436     $schema->storage->txn_begin();
437
438     my $builder = t::lib::TestBuilder->new;
439     my $library1 = $builder->build({
440         source => 'Branch',
441     });
442     my $library2 = $builder->build({
443         source => 'Branch',
444     });
445
446     # Create a biblio and item for testing
447     t::lib::Mocks::mock_preference('marcflavour', 'MARC21');
448     my ($bibnum, $bibitemnum) = get_biblio();
449     my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $library1->{branchcode}, holdingbranch => $library2->{branchcode} } , $bibnum);
450
451     # Get item.
452     my $item = Koha::Items->find( $itemnumber );
453     is( ref($item), 'Koha::Item', "Got Koha::Item" );
454
455     my $homebranch = $item->home_branch();
456     is( ref($homebranch), 'Koha::Library', "Got Koha::Library from home_branch method" );
457     is( $homebranch->branchcode(), $library1->{branchcode}, "Home branch code matches homebranch" );
458
459     my $holdingbranch = $item->holding_branch();
460     is( ref($holdingbranch), 'Koha::Library', "Got Koha::Library from holding_branch method" );
461     is( $holdingbranch->branchcode(), $library2->{branchcode}, "Home branch code matches holdingbranch" );
462
463     $schema->storage->txn_rollback;
464 };
465
466 subtest 'C4::Biblio::EmbedItemsInMarcBiblio' => sub {
467     plan tests => 7;
468
469     $schema->storage->txn_begin();
470
471     my $builder = t::lib::TestBuilder->new;
472     my $library1 = $builder->build({
473         source => 'Branch',
474     });
475     my $library2 = $builder->build({
476         source => 'Branch',
477     });
478
479     my ( $biblionumber, $biblioitemnumber ) = get_biblio();
480     my $item_infos = [
481         { homebranch => $library1->{branchcode}, holdingbranch => $library1->{branchcode} },
482         { homebranch => $library1->{branchcode}, holdingbranch => $library1->{branchcode} },
483         { homebranch => $library1->{branchcode}, holdingbranch => $library1->{branchcode} },
484         { homebranch => $library2->{branchcode}, holdingbranch => $library2->{branchcode} },
485         { homebranch => $library2->{branchcode}, holdingbranch => $library2->{branchcode} },
486         { homebranch => $library1->{branchcode}, holdingbranch => $library2->{branchcode} },
487         { homebranch => $library1->{branchcode}, holdingbranch => $library2->{branchcode} },
488         { homebranch => $library1->{branchcode}, holdingbranch => $library2->{branchcode} },
489     ];
490     my $number_of_items = scalar @$item_infos;
491     my $number_of_items_with_homebranch_is_CPL =
492       grep { $_->{homebranch} eq $library1->{branchcode} } @$item_infos;
493
494     my @itemnumbers;
495     for my $item_info (@$item_infos) {
496         my ( undef, undef, $itemnumber ) = AddItem(
497             {
498                 homebranch    => $item_info->{homebranch},
499                 holdingbranch => $item_info->{holdingbanch}
500             },
501             $biblionumber
502         );
503         push @itemnumbers, $itemnumber;
504     }
505
506     # Emptied the OpacHiddenItems pref
507     t::lib::Mocks::mock_preference( 'OpacHiddenItems', '' );
508
509     my ($itemfield) =
510       C4::Biblio::GetMarcFromKohaField( 'items.itemnumber', '' );
511     my $record = C4::Biblio::GetMarcBiblio($biblionumber);
512     warning_is { C4::Biblio::EmbedItemsInMarcBiblio() }
513     { carped => 'EmbedItemsInMarcBiblio: No MARC record passed' },
514       'Should crap is no record passed.';
515
516     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber );
517     my @items = $record->field($itemfield);
518     is( scalar @items, $number_of_items, 'Should return all items' );
519
520     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber,
521         [ $itemnumbers[1], $itemnumbers[3] ] );
522     @items = $record->field($itemfield);
523     is( scalar @items, 2, 'Should return all items present in the list' );
524
525     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, 1 );
526     @items = $record->field($itemfield);
527     is( scalar @items, $number_of_items, 'Should return all items for opac' );
528
529     my $opachiddenitems = "
530         homebranch: ['$library1->{branchcode}']";
531     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
532
533     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber );
534     @items = $record->field($itemfield);
535     is( scalar @items,
536         $number_of_items,
537         'Even with OpacHiddenItems set, all items should have been embeded' );
538
539     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, 1 );
540     @items = $record->field($itemfield);
541     is(
542         scalar @items,
543         $number_of_items - $number_of_items_with_homebranch_is_CPL,
544 'For OPAC, the pref OpacHiddenItems should have been take into account. Only items with homebranch ne CPL should have been embeded'
545     );
546
547     $opachiddenitems = "
548         homebranch: ['$library1->{branchcode}', '$library2->{branchcode}']";
549     t::lib::Mocks::mock_preference( 'OpacHiddenItems', $opachiddenitems );
550     C4::Biblio::EmbedItemsInMarcBiblio( $record, $biblionumber, undef, 1 );
551     @items = $record->field($itemfield);
552     is(
553         scalar @items,
554         0,
555 'For OPAC, If all items are hidden, no item should have been embeded'
556     );
557
558     $schema->storage->txn_rollback;
559 };
560
561 # Helper method to set up a Biblio.
562 sub get_biblio {
563     my $bib = MARC::Record->new();
564     $bib->append_fields(
565         MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'),
566         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
567     );
568     my ($bibnum, $bibitemnum) = AddBiblio($bib, '');
569     return ($bibnum, $bibitemnum);
570 }