3 # Copyright Tamil s.a.r.l. 2016
5 # This file is part of Koha.
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.
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.
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>.
21 use Test::Deep qw( cmp_deeply re );
22 use Test::MockTime qw/set_fixed_time set_relative_time restore_time/;
24 use Test::More tests => 34;
34 use t::lib::TestBuilder;
36 use C4::Biblio qw( AddBiblio ModBiblio DelBiblio );
38 use C4::OAI::Sets qw(AddOAISet);
41 use Koha::Biblio::Metadatas;
43 use Koha::DateUtils qw( dt_from_string );
46 use_ok('Koha::OAI::Server::DeletedRecord');
47 use_ok('Koha::OAI::Server::Description');
48 use_ok('Koha::OAI::Server::GetRecord');
49 use_ok('Koha::OAI::Server::Identify');
50 use_ok('Koha::OAI::Server::ListBase');
51 use_ok('Koha::OAI::Server::ListIdentifiers');
52 use_ok('Koha::OAI::Server::ListMetadataFormats');
53 use_ok('Koha::OAI::Server::ListRecords');
54 use_ok('Koha::OAI::Server::ListSets');
55 use_ok('Koha::OAI::Server::Record');
56 use_ok('Koha::OAI::Server::Repository');
57 use_ok('Koha::OAI::Server::ResumptionToken');
60 use constant NUMBER_OF_MARC_RECORDS => 10;
62 # Mocked CGI module in order to be able to send CGI parameters to OAI Server
64 my $module = Test::MockModule->new('CGI');
65 $module->mock('Vars', sub { %param; });
67 my $schema = Koha::Database->schema;
68 $schema->storage->txn_begin;
69 my $dbh = C4::Context->dbh;
71 $dbh->do("SET time_zone='+00:00'");
72 $dbh->do('DELETE FROM issues');
73 $dbh->do('DELETE FROM biblio');
74 $dbh->do('DELETE FROM deletedbiblio');
75 $dbh->do('DELETE FROM deletedbiblioitems');
76 $dbh->do('DELETE FROM deleteditems');
77 $dbh->do('DELETE FROM oai_sets');
79 set_fixed_time(CORE::time());
81 my $base_datetime = dt_from_string(undef, undef, 'UTC');
82 my $date_added = $base_datetime->ymd . ' ' .$base_datetime->hms . 'Z';
83 my $date_to = substr($date_added, 0, 10) . 'T23:59:59Z';
84 my (@header, @marcxml, @oaidc, @marcxml_transformed);
85 my $sth = $dbh->prepare('UPDATE biblioitems SET timestamp=? WHERE biblionumber=?');
86 my $sth2 = $dbh->prepare('UPDATE biblio_metadata SET timestamp=? WHERE biblionumber=?');
90 foreach my $index ( 0 .. NUMBER_OF_MARC_RECORDS - 1 ) {
91 my $record = MARC::Record->new();
92 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
93 $record->append_fields( MARC::Field->new('101', '', '', 'a' => "lng" ) );
94 $record->append_fields( MARC::Field->new('200', '', '', 'a' => "Title $index" ) );
95 $record->append_fields( MARC::Field->new('952', '', '', 'a' => "Code" ) );
97 $record->append_fields( MARC::Field->new('008', ' lng' ) );
98 $record->append_fields( MARC::Field->new('245', '', '', 'a' => "Title $index" ) );
99 $record->append_fields( MARC::Field->new('952', '', '', 'a' => "Code" ) );
101 my ($biblionumber) = AddBiblio($record, '');
102 $first_bn = $biblionumber unless $first_bn;
103 my $timestamp = $base_datetime->ymd . ' ' .$base_datetime->hms;
104 $sth->execute($timestamp,$biblionumber);
105 $sth2->execute($timestamp,$biblionumber);
107 $timestamp =~ s/ /T/;
108 my $biblio = Koha::Biblios->find($biblionumber);
109 $record = $biblio->metadata->record;
110 my $record_transformed = $record->clone;
111 $record_transformed->delete_fields( $record_transformed->field('952'));
112 $record_transformed = XMLin($record_transformed->as_xml_record);
113 $record = XMLin($record->as_xml_record);
114 push @header, { datestamp => $timestamp, identifier => "TEST:$biblionumber" };
116 'dc:title' => "Title $index",
117 'dc:language' => "lng",
119 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
120 'xmlns:oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
121 'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
122 'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
124 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
125 $dc->{'dc:identifier'} = $biblionumber;
128 header => $header[$index],
134 header => $header[$index],
140 push @marcxml_transformed, {
141 header => $header[$index],
143 record => $record_transformed,
149 'LibraryName' => 'My Library',
151 'OAI-PMH:archiveID' => 'TEST',
152 'OAI-PMH:ConfFile' => '',
153 'OAI-PMH:MaxCount' => 3,
154 'OAI-PMH:DeletedRecord' => 'persistent',
156 while ( my ($name, $value) = each %$syspref ) {
157 t::lib::Mocks::mock_preference( $name => $value );
161 my ($test, $param, $expected) = @_;
164 my %full_expected = (
167 request => 'http://localhost',
168 xmlns => 'http://www.openarchives.org/OAI/2.0/',
169 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
170 'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd',
178 open STDOUT, '>', \$stdout;
179 Koha::OAI::Server::Repository->new();
180 $response = XMLin($stdout);
183 delete $response->{responseDate};
184 unless (cmp_deeply($response, \%full_expected, $test)) {
186 "PARAM:" . YAML::XS::Dump($param) .
187 "EXPECTED:" . YAML::XS::Dump(\%full_expected) .
188 "RESPONSE:" . YAML::XS::Dump($response);
192 test_query('ListMetadataFormats', {verb => 'ListMetadataFormats'}, {
193 ListMetadataFormats => {
196 metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
197 metadataPrefix=> 'oai_dc',
198 schema => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
201 metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
202 metadataPrefix => 'marc21',
203 schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
206 metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
207 metadataPrefix => 'marcxml',
208 schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
214 test_query('ListIdentifiers without metadataPrefix', {verb => 'ListIdentifiers'}, {
216 code => 'badArgument',
217 content => "Required argument 'metadataPrefix' was undefined",
221 test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
223 header => [ @header[0..2] ],
225 content => re( qr{^marcxml/3////0/0/\d+$} ),
231 test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
233 header => [ @header[0..2] ],
235 content => re( qr{^marcxml/3////0/0/\d+$} ),
242 'ListIdentifiers with resumptionToken 1',
243 { verb => 'ListIdentifiers', resumptionToken => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0/" . ($first_bn + 3) },
246 header => [ @header[3..5] ],
248 content => re( qr{^marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0/\d+$} ),
256 'ListIdentifiers with resumptionToken 2',
257 { verb => 'ListIdentifiers', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0/" . ($first_bn + 6) },
260 header => [ @header[6..8] ],
262 content => re( qr{^marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0/\d+$} ),
270 'ListIdentifiers with resumptionToken 3, response without resumption',
271 { verb => 'ListIdentifiers', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0/" . ($first_bn + 9) },
274 header => $header[9],
279 test_query('ListRecords marcxml without metadataPrefix', {verb => 'ListRecords'}, {
281 code => 'badArgument',
282 content => "Required argument 'metadataPrefix' was undefined",
286 test_query('ListRecords marcxml', {verb => 'ListRecords', metadataPrefix => 'marcxml'}, {
288 record => [ @marcxml[0..2] ],
290 content => re( qr{^marcxml/3////0/0/\d+$} ),
297 'ListRecords marcxml with resumptionToken 1',
298 { verb => 'ListRecords', resumptionToken => "marcxml/3////0/0/" . ($first_bn + 3) },
300 record => [ @marcxml[3..5] ],
302 content => re( qr{^marcxml/6////0/0/\d+$} ),
309 'ListRecords marcxml with resumptionToken 2',
310 { verb => 'ListRecords', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0/" . ($first_bn + 6) },
312 record => [ @marcxml[6..8] ],
314 content => re( qr{^marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0/\d+$} ),
320 # Last record, so no resumption token
322 'ListRecords marcxml with resumptionToken 3, response without resumption',
323 { verb => 'ListRecords', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0/" . ($first_bn + 9) },
325 record => $marcxml[9],
329 test_query('ListRecords oai_dc', {verb => 'ListRecords', metadataPrefix => 'oai_dc'}, {
331 record => [ @oaidc[0..2] ],
333 content => re( qr{^oai_dc/3////0/0/\d+$} ),
340 'ListRecords oai_dc with resumptionToken 1',
341 { verb => 'ListRecords', resumptionToken => "oai_dc/3////0/0/" . ($first_bn + 3) },
343 record => [ @oaidc[3..5] ],
345 content => re( qr{^oai_dc/6////0/0/\d+$} ),
352 'ListRecords oai_dc with resumptionToken 2',
353 { verb => 'ListRecords', resumptionToken => "oai_dc/6/1970-01-01T00:00:00Z/$date_to//0/0/" . ($first_bn + 6) },
355 record => [ @oaidc[6..8] ],
357 content => re( qr{^oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0/\d+$} ),
363 # Last record, so no resumption token
365 'ListRecords oai_dc with resumptionToken 3, response without resumption',
366 { verb => 'ListRecords', resumptionToken => "oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0/" . ($first_bn + 9) },
372 # List records, but now transformed by XSLT
373 t::lib::Mocks::mock_preference("OAI-PMH:ConfFile" => File::Spec->rel2abs(dirname(__FILE__)) . "/oaiconf.yaml");
374 test_query('ListRecords marcxml with xsl transformation',
375 { verb => 'ListRecords', metadataPrefix => 'marcxml' },
377 record => [ @marcxml_transformed[0..2] ],
379 content => re( qr{^marcxml/3////0/0/\d+$} ),
384 t::lib::Mocks::mock_preference("OAI-PMH:ConfFile" => '');
388 subtest 'Bug 19725: OAI-PMH ListRecords and ListIdentifiers should use biblio_metadata.timestamp' => sub {
391 # Wait 1 second to be sure no timestamp will be equal to $from defined below
394 # Modify record to trigger auto update of timestamp
395 (my $biblionumber = $marcxml[0]->{header}->{identifier}) =~ s/^.*:(.*)/$1/;
396 my $biblio = Koha::Biblios->find($biblionumber);
397 my $record = $biblio->metadata->record;
398 $record->append_fields(MARC::Field->new(999, '', '', z => '_'));
399 ModBiblio( $record, $biblionumber );
400 my $from_dt = dt_from_string(
401 Koha::Biblio::Metadatas->find({ biblionumber => $biblionumber, format => 'marcxml', schema => 'MARC21' })->timestamp
403 my $from = $from_dt->ymd . 'T' . $from_dt->hms . 'Z';
404 $oaidc[0]->{header}->{datestamp} = $from;
407 'ListRecords oai_dc with parameter from',
408 { verb => 'ListRecords', metadataPrefix => 'oai_dc', from => $from },
415 subtest 'Bug 20665: OAI-PMH Provider should reset the MySQL connection time zone' => sub {
418 # Set time zone to SYSTEM so that it can be checked later
419 $dbh->do("SET time_zone='SYSTEM'");
422 test_query('ListIdentifiers without metadataPrefix', {verb => 'ListIdentifiers'}, {
424 code => 'badArgument',
425 content => "Required argument 'metadataPrefix' was undefined",
429 my $sth = C4::Context->dbh->prepare('SELECT @@session.time_zone');
431 my ( $tz ) = $sth->fetchrow();
433 ok ( $tz eq 'SYSTEM', 'MySQL connection time zone is SYSTEM' );
437 $schema->storage->txn_rollback;
439 subtest 'ListSets tests' => sub {
443 t::lib::Mocks::mock_preference( 'OAI::PMH' => 1 );
444 t::lib::Mocks::mock_preference( 'OAI-PMH:MaxCount' => 3 );
446 $schema->storage->txn_begin;
448 $dbh->do('DELETE FROM oai_sets');
450 # Add a bunch of sets
451 my @first_page_sets = ();
452 for my $i ( 1 .. 3 ) {
455 { 'spec' => "setSpec_$i",
456 'name' => "setName_$i",
459 push @first_page_sets, { setSpec => "setSpec_$i", setName => "setName_$i" };
462 # Add more to force pagination
463 my @second_page_sets = ();
464 for my $i ( 4 .. 6 ) {
467 { 'spec' => "setSpec_$i",
468 'name' => "setName_$i",
471 push @second_page_sets, { setSpec => "setSpec_$i", setName => "setName_$i" };
475 { 'spec' => "setSpec_7",
476 'name' => "setName_7",
482 { verb => 'ListSets' },
485 content => re( qr{^/3////1/0/4$} ),
488 set => \@first_page_sets
495 { verb => 'ListSets', resumptionToken => '/3////1/0/4' },
498 content => re( qr{^/6////1/0/7$} ),
501 set => \@second_page_sets
508 { verb => 'ListSets', resumptionToken => "/6////1/0/7" },
510 set => { setSpec => "setSpec_7", setName => "setName_7" }
515 $schema->storage->txn_rollback;
518 subtest 'Tests for timestamp handling' => sub {
522 t::lib::Mocks::mock_preference( 'OAI::PMH' => 1 );
523 t::lib::Mocks::mock_preference( 'OAI-PMH:MaxCount' => 3 );
524 t::lib::Mocks::mock_preference( 'OAI-PMH:ConfFile' => File::Spec->rel2abs(dirname(__FILE__)) . '/oaiconf_items.yaml' );
526 $schema->storage->txn_begin;
528 my $sth_metadata = $dbh->prepare('UPDATE biblio_metadata SET timestamp=? WHERE biblionumber=?');
529 my $sth_del_metadata = $dbh->prepare('UPDATE deletedbiblio_metadata SET timestamp=? WHERE biblionumber=?');
530 my $sth_item = $dbh->prepare('UPDATE items SET timestamp=? WHERE itemnumber=?');
531 my $sth_del_item = $dbh->prepare('UPDATE deleteditems SET timestamp=? WHERE itemnumber=?');
533 my $builder = t::lib::TestBuilder->new;
535 set_fixed_time(CORE::time());
537 my $utc_datetime = dt_from_string(undef, undef, 'UTC');
538 my $utc_timestamp = $utc_datetime->ymd . 'T' . $utc_datetime->hms . 'Z';
539 my $timestamp = dt_from_string(undef, 'sql');
541 # Test a bib with one item
542 my $biblio1 = $builder->build_sample_biblio;
543 Koha::Biblios->find($biblio1->biblionumber)->timestamp('1970-05-07 13:36:23')->store;
545 $sth_metadata->execute($timestamp, $biblio1->biblionumber);
546 my $item1 = $builder->build_sample_item(
548 biblionumber => $biblio1->biblionumber
551 $sth_item->execute($timestamp, $item1->itemnumber);
554 verb => 'ListRecords',
555 metadataPrefix => 'marc21',
556 from => $utc_timestamp
558 my $list_no_items = {
559 verb => 'ListRecords',
560 metadataPrefix => 'marcxml',
561 from => $utc_timestamp
566 metadataPrefix => 'marc21',
567 identifier => 'TEST:' . $biblio1->biblionumber
571 metadataPrefix => 'marcxml',
572 identifier => 'TEST:' . $biblio1->biblionumber
578 datestamp => $utc_timestamp,
579 identifier => 'TEST:' . $biblio1->biblionumber
583 $biblio1->metadata->record({ embed_items => 1, opac => 1})->as_xml_record()
588 my $expected_no_items = {
591 datestamp => $utc_timestamp,
592 identifier => 'TEST:' . $biblio1->biblionumber
596 $biblio1->metadata->record({opac => 1})->as_xml_record()
603 'ListRecords - biblio with a single item',
605 { ListRecords => $expected }
608 'ListRecords - biblio with a single item (items not returned)',
610 { ListRecords => $expected_no_items }
613 'GetRecord - biblio with a single item',
615 { GetRecord => $expected }
618 'GetRecord - biblio with a single item (items not returned)',
620 { GetRecord => $expected_no_items }
622 t::lib::Mocks::mock_preference('KohaAdminEmailAddress', 'root@localhost');
624 'Identify - earliestDatestamp in the right format',
625 { verb => 'Identify' },
627 adminEmail => 'root@localhost',
628 baseURL => 'http://localhost',
629 compression => 'gzip',
630 deletedRecord => 'persistent',
631 earliestDatestamp => '1970-05-07T13:36:23Z',
632 granularity => 'YYYY-MM-DDThh:mm:ssZ',
633 protocolVersion => '2.0',
634 repositoryName => 'My Library',
639 # Add an item 10 seconds later and check results
640 set_relative_time(10);
642 $utc_datetime = dt_from_string(undef, undef, 'UTC');
643 $utc_timestamp = $utc_datetime->ymd . 'T' . $utc_datetime->hms . 'Z';
644 $timestamp = dt_from_string(undef, 'sql');
646 my $item2 = $builder->build_sample_item(
648 biblionumber => $biblio1->biblionumber
651 $sth_item->execute($timestamp, $item2->itemnumber);
653 $expected->{record}{header}{datestamp} = $utc_timestamp;
654 $expected->{record}{metadata}{record} = XMLin(
655 $biblio1->metadata->record({ embed_items => 1, opac => 1})->as_xml_record()
659 'ListRecords - biblio with two items',
661 { ListRecords => $expected }
664 'ListRecords - biblio with two items (items not returned)',
666 { ListRecords => $expected_no_items }
669 'GetRecord - biblio with a two items',
671 { GetRecord => $expected }
674 'GetRecord - biblio with a two items (items not returned)',
676 { GetRecord => $expected_no_items }
679 # Set biblio timestamp 10 seconds later and check results
680 set_relative_time(10);
681 $utc_datetime = dt_from_string(undef, undef, 'UTC');
682 $utc_timestamp= $utc_datetime->ymd . 'T' . $utc_datetime->hms . 'Z';
683 $timestamp = dt_from_string(undef, 'sql');
685 $sth_metadata->execute($timestamp, $biblio1->biblionumber);
687 $expected->{record}{header}{datestamp} = $utc_timestamp;
688 $expected_no_items->{record}{header}{datestamp} = $utc_timestamp;
691 "ListRecords - biblio with timestamp higher than item's",
693 { ListRecords => $expected }
696 "ListRecords - biblio with timestamp higher than item's (items not returned)",
698 { ListRecords => $expected_no_items }
701 "GetRecord - biblio with timestamp higher than item's",
703 { GetRecord => $expected }
706 "GetRecord - biblio with timestamp higher than item's (items not returned)",
708 { GetRecord => $expected_no_items }
711 # Delete an item 10 seconds later and check results
712 set_relative_time(10);
713 $utc_datetime = dt_from_string(undef, undef, 'UTC');
714 $utc_timestamp = $utc_datetime->ymd . 'T' . $utc_datetime->hms . 'Z';
716 $item1->safe_delete({ skip_record_index =>1 });
717 $sth_del_item->execute($timestamp, $item1->itemnumber);
719 $expected->{record}{header}{datestamp} = $utc_timestamp;
720 $expected->{record}{metadata}{record} = XMLin(
721 $biblio1->metadata->record({ embed_items => 1, opac => 1})->as_xml_record()
725 'ListRecords - biblio with existing and deleted item',
727 { ListRecords => $expected }
730 'ListRecords - biblio with existing and deleted item (items not returned)',
732 { ListRecords => $expected_no_items }
735 'GetRecord - biblio with existing and deleted item',
737 { GetRecord => $expected }
740 'GetRecord - biblio with existing and deleted item (items not returned)',
742 { GetRecord => $expected_no_items }
745 # Delete also the second item and verify results
746 $item2->safe_delete({ skip_record_index =>1 });
747 $sth_del_item->execute($timestamp, $item2->itemnumber);
749 $expected->{record}{metadata}{record} = XMLin(
750 $biblio1->metadata->record({ embed_items => 1, opac => 1})->as_xml_record()
754 'ListRecords - biblio with two deleted items',
756 { ListRecords => $expected }
759 'ListRecords - biblio with two deleted items (items not returned)',
761 { ListRecords => $expected_no_items }
764 'GetRecord - biblio with two deleted items',
766 { GetRecord => $expected }
769 'GetRecord - biblio with two deleted items (items not returned)',
771 { GetRecord => $expected_no_items }
774 # Delete the biblio 10 seconds later and check results
775 set_relative_time(10);
776 $utc_datetime = dt_from_string(undef, undef, 'UTC');
777 $utc_timestamp = $utc_datetime->ymd . 'T' . $utc_datetime->hms . 'Z';
778 $timestamp = dt_from_string(undef, 'sql');
780 is(undef, DelBiblio($biblio1->biblionumber, { skip_record_index =>1 }), 'Biblio deleted');
781 $sth_del_metadata->execute($timestamp, $biblio1->biblionumber);
783 my $expected_header = {
786 datestamp => $utc_timestamp,
787 identifier => 'TEST:' . $biblio1->biblionumber,
794 'ListRecords - deleted biblio with two deleted items',
796 { ListRecords => $expected_header }
799 'ListRecords - deleted biblio with two deleted items (items not returned)',
801 { ListRecords => $expected_header }
804 'GetRecord - deleted biblio with two deleted items',
806 { GetRecord => $expected_header }
809 'GetRecord - deleted biblio with two deleted items (items not returned)',
811 { GetRecord => $expected_header }
814 # Add a second biblio 10 seconds later and check that both are returned properly
815 set_relative_time(10);
816 $utc_datetime = dt_from_string(undef, undef, 'UTC');
817 $utc_timestamp = $utc_datetime->ymd . 'T' . $utc_datetime->hms . 'Z';
818 $timestamp = dt_from_string(undef, 'sql');
820 my $biblio2 = $builder->build_sample_biblio();
821 $sth_metadata->execute($timestamp, $biblio2->biblionumber);
825 $expected_header->{record},
828 datestamp => $utc_timestamp,
829 identifier => 'TEST:' . $biblio2->biblionumber
833 $biblio2->metadata->record({ embed_items => 1, opac => 1})->as_xml_record()
839 my $expected2_no_items = {
841 $expected_header->{record},
844 datestamp => $utc_timestamp,
845 identifier => 'TEST:' . $biblio2->biblionumber
849 $biblio2->metadata->record->as_xml_record()
857 'ListRecords - deleted biblio and normal biblio',
859 { ListRecords => $expected2 }
862 'ListRecords - deleted biblio and normal biblio (items not returned)',
864 { ListRecords => $expected2_no_items }
869 $schema->storage->txn_rollback;
872 subtest 'ListSets() tests' => sub {
876 $schema->storage->txn_begin;
879 $schema->resultset('OaiSet')->delete;
882 'ListSets - no sets should return a noSetHierarchy exception',
883 { verb => 'ListSets' },
886 code => 'noSetHierarchy',
887 content => 'There are no OAI sets defined',
893 AddOAISet({ spec => 'set_1', name => 'Set 1' });
894 AddOAISet({ spec => 'set_2', name => 'Set 2' });
897 'ListSets - no sets should return a noSetHierarchy exception',
898 { verb => 'ListSets' },
902 { setSpec => 'set_1', setName => 'Set 1' },
903 { setSpec => 'set_2', setName => 'Set 2' },
909 $schema->storage->txn_rollback;