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::MockTime qw/set_fixed_time restore_time/;
23 use Test::More tests => 31;
37 use Koha::Biblio::Metadatas;
42 use_ok('Koha::OAI::Server::DeletedRecord');
43 use_ok('Koha::OAI::Server::Description');
44 use_ok('Koha::OAI::Server::GetRecord');
45 use_ok('Koha::OAI::Server::Identify');
46 use_ok('Koha::OAI::Server::ListBase');
47 use_ok('Koha::OAI::Server::ListIdentifiers');
48 use_ok('Koha::OAI::Server::ListMetadataFormats');
49 use_ok('Koha::OAI::Server::ListRecords');
50 use_ok('Koha::OAI::Server::ListSets');
51 use_ok('Koha::OAI::Server::Record');
52 use_ok('Koha::OAI::Server::Repository');
53 use_ok('Koha::OAI::Server::ResumptionToken');
56 use constant NUMBER_OF_MARC_RECORDS => 10;
58 # Mocked CGI module in order to be able to send CGI parameters to OAI Server
60 my $module = Test::MockModule->new('CGI');
61 $module->mock('Vars', sub { %param; });
63 my $schema = Koha::Database->schema;
64 $schema->storage->txn_begin;
65 my $dbh = C4::Context->dbh;
67 $dbh->do("SET time_zone='+00:00'");
68 $dbh->do('DELETE FROM issues');
69 $dbh->do('DELETE FROM biblio');
70 $dbh->do('DELETE FROM deletedbiblio');
71 $dbh->do('DELETE FROM deletedbiblioitems');
72 $dbh->do('DELETE FROM deleteditems');
73 $dbh->do('DELETE FROM oai_sets');
75 set_fixed_time(CORE::time());
77 my $base_datetime = dt_from_string();
78 my $date_added = $base_datetime->ymd . ' ' .$base_datetime->hms . 'Z';
79 my $date_to = substr($date_added, 0, 10) . 'T23:59:59Z';
80 my (@header, @marcxml, @oaidc, @marcxml_transformed);
81 my $sth = $dbh->prepare('UPDATE biblioitems SET timestamp=? WHERE biblionumber=?');
82 my $sth2 = $dbh->prepare('UPDATE biblio_metadata SET timestamp=? WHERE biblionumber=?');
85 foreach my $index ( 0 .. NUMBER_OF_MARC_RECORDS - 1 ) {
86 my $record = MARC::Record->new();
87 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
88 $record->append_fields( MARC::Field->new('101', '', '', 'a' => "lng" ) );
89 $record->append_fields( MARC::Field->new('200', '', '', 'a' => "Title $index" ) );
90 $record->append_fields( MARC::Field->new('952', '', '', 'a' => "Code" ) );
92 $record->append_fields( MARC::Field->new('008', ' lng' ) );
93 $record->append_fields( MARC::Field->new('245', '', '', 'a' => "Title $index" ) );
94 $record->append_fields( MARC::Field->new('952', '', '', 'a' => "Code" ) );
96 my ($biblionumber) = AddBiblio($record, '');
97 my $timestamp = $base_datetime->ymd . ' ' .$base_datetime->hms;
98 $sth->execute($timestamp,$biblionumber);
99 $sth2->execute($timestamp,$biblionumber);
101 $timestamp =~ s/ /T/;
102 $record = GetMarcBiblio({ biblionumber => $biblionumber });
103 my $record_transformed = $record->clone;
104 $record_transformed->delete_fields( $record_transformed->field('952'));
105 $record_transformed = XMLin($record_transformed->as_xml_record);
106 $record = XMLin($record->as_xml_record);
107 push @header, { datestamp => $timestamp, identifier => "TEST:$biblionumber" };
109 'dc:title' => "Title $index",
110 'dc:language' => "lng",
112 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
113 'xmlns:oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
114 'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
115 'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
117 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
118 $dc->{'dc:identifier'} = $biblionumber;
121 header => $header[$index],
127 header => $header[$index],
133 push @marcxml_transformed, {
134 header => $header[$index],
136 record => $record_transformed,
142 'LibraryName' => 'My Library',
144 'OAI-PMH:archiveID' => 'TEST',
145 'OAI-PMH:ConfFile' => '',
146 'OAI-PMH:MaxCount' => 3,
147 'OAI-PMH:DeletedRecord' => 'persistent',
149 while ( my ($name, $value) = each %$syspref ) {
150 t::lib::Mocks::mock_preference( $name => $value );
154 my ($test, $param, $expected) = @_;
157 my %full_expected = (
160 request => 'http://localhost',
161 xmlns => 'http://www.openarchives.org/OAI/2.0/',
162 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
163 'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd',
171 open STDOUT, '>', \$stdout;
172 Koha::OAI::Server::Repository->new();
173 $response = XMLin($stdout);
176 delete $response->{responseDate};
177 unless (is_deeply($response, \%full_expected, $test)) {
179 "PARAM:" . Dump($param) .
180 "EXPECTED:" . Dump(\%full_expected) .
181 "RESPONSE:" . Dump($response);
185 test_query('ListMetadataFormats', {verb => 'ListMetadataFormats'}, {
186 ListMetadataFormats => {
189 metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
190 metadataPrefix=> 'oai_dc',
191 schema => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
194 metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
195 metadataPrefix => 'marc21',
196 schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
199 metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
200 metadataPrefix => 'marcxml',
201 schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
207 test_query('ListIdentifiers without metadataPrefix', {verb => 'ListIdentifiers'}, {
209 code => 'badArgument',
210 content => "Required argument 'metadataPrefix' was undefined",
214 test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
216 header => [ @header[0..2] ],
218 content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
224 test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
226 header => [ @header[0..2] ],
228 content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
235 'ListIdentifiers with resumptionToken 1',
236 { verb => 'ListIdentifiers', resumptionToken => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0" },
239 header => [ @header[3..5] ],
241 content => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0",
249 'ListIdentifiers with resumptionToken 2',
250 { verb => 'ListIdentifiers', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0" },
253 header => [ @header[6..8] ],
255 content => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0",
263 'ListIdentifiers with resumptionToken 3, response without resumption',
264 { verb => 'ListIdentifiers', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0" },
267 header => $header[9],
272 test_query('ListRecords marcxml without metadataPrefix', {verb => 'ListRecords'}, {
274 code => 'badArgument',
275 content => "Required argument 'metadataPrefix' was undefined",
279 test_query('ListRecords marcxml', {verb => 'ListRecords', metadataPrefix => 'marcxml'}, {
281 record => [ @marcxml[0..2] ],
283 content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
290 'ListRecords marcxml with resumptionToken 1',
291 { verb => 'ListRecords', resumptionToken => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0" },
293 record => [ @marcxml[3..5] ],
295 content => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0",
302 'ListRecords marcxml with resumptionToken 2',
303 { verb => 'ListRecords', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0" },
305 record => [ @marcxml[6..8] ],
307 content => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0",
313 # Last record, so no resumption token
315 'ListRecords marcxml with resumptionToken 3, response without resumption',
316 { verb => 'ListRecords', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0" },
318 record => $marcxml[9],
322 test_query('ListRecords oai_dc', {verb => 'ListRecords', metadataPrefix => 'oai_dc'}, {
324 record => [ @oaidc[0..2] ],
326 content => "oai_dc/3/1970-01-01T00:00:00Z/$date_to//0/0",
333 'ListRecords oai_dc with resumptionToken 1',
334 { verb => 'ListRecords', resumptionToken => "oai_dc/3/1970-01-01T00:00:00Z/$date_to//0/0" },
336 record => [ @oaidc[3..5] ],
338 content => "oai_dc/6/1970-01-01T00:00:00Z/$date_to//0/0",
345 'ListRecords oai_dc with resumptionToken 2',
346 { verb => 'ListRecords', resumptionToken => "oai_dc/6/1970-01-01T00:00:00Z/$date_to//0/0" },
348 record => [ @oaidc[6..8] ],
350 content => "oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0",
356 # Last record, so no resumption token
358 'ListRecords oai_dc with resumptionToken 3, response without resumption',
359 { verb => 'ListRecords', resumptionToken => "oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0" },
365 # List records, but now transformed by XSLT
366 t::lib::Mocks::mock_preference("OAI-PMH:ConfFile" => File::Spec->rel2abs(dirname(__FILE__)) . "/oaiconf.yaml");
367 test_query('ListRecords marcxml with xsl transformation',
368 { verb => 'ListRecords', metadataPrefix => 'marcxml' },
370 record => [ @marcxml_transformed[0..2] ],
372 content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
377 t::lib::Mocks::mock_preference("OAI-PMH:ConfFile" => '');
381 subtest 'Bug 19725: OAI-PMH ListRecords and ListIdentifiers should use biblio_metadata.timestamp' => sub {
384 # Wait 1 second to be sure no timestamp will be equal to $from defined below
387 # Modify record to trigger auto update of timestamp
388 (my $biblionumber = $marcxml[0]->{header}->{identifier}) =~ s/^.*:(.*)/$1/;
389 my $record = GetMarcBiblio({biblionumber => $biblionumber});
390 $record->append_fields(MARC::Field->new(999, '', '', z => '_'));
391 ModBiblio( $record, $biblionumber );
392 my $from_dt = dt_from_string(
393 Koha::Biblio::Metadatas->find({ biblionumber => $biblionumber, format => 'marcxml', schema => 'MARC21' })->timestamp
395 my $from = $from_dt->ymd . 'T' . $from_dt->hms . 'Z';
396 $oaidc[0]->{header}->{datestamp} = $from;
399 'ListRecords oai_dc with parameter from',
400 { verb => 'ListRecords', metadataPrefix => 'oai_dc', from => $from },
407 subtest 'Bug 20665: OAI-PMH Provider should reset the MySQL connection time zone' => sub {
410 # Set time zone to SYSTEM so that it can be checked later
411 $dbh->do("SET time_zone='SYSTEM'");
414 test_query('ListIdentifiers without metadataPrefix', {verb => 'ListIdentifiers'}, {
416 code => 'badArgument',
417 content => "Required argument 'metadataPrefix' was undefined",
421 my $sth = C4::Context->dbh->prepare('SELECT @@session.time_zone');
423 my ( $tz ) = $sth->fetchrow();
425 ok ( $tz eq 'SYSTEM', 'MySQL connection time zone is SYSTEM' );
429 $schema->storage->txn_rollback;