Bug 15108: OAI-PMH provider improvements
[koha.git] / t / db_dependent / OAI / Server.t
1 #!/usr/bin/perl
2
3 # Copyright Tamil s.a.r.l. 2016
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 => 28;
23 use DateTime;
24 use Test::MockModule;
25 use Test::Warn;
26 use XML::Simple;
27 use YAML;
28
29 use t::lib::Mocks;
30
31 use C4::Biblio;
32 use C4::Context;
33 use Koha::Database;
34
35 BEGIN {
36     use_ok('Koha::OAI::Server::DeletedRecord');
37     use_ok('Koha::OAI::Server::Description');
38     use_ok('Koha::OAI::Server::GetRecord');
39     use_ok('Koha::OAI::Server::Identify');
40     use_ok('Koha::OAI::Server::ListBase');
41     use_ok('Koha::OAI::Server::ListIdentifiers');
42     use_ok('Koha::OAI::Server::ListMetadataFormats');
43     use_ok('Koha::OAI::Server::ListRecords');
44     use_ok('Koha::OAI::Server::ListSets');
45     use_ok('Koha::OAI::Server::Record');
46     use_ok('Koha::OAI::Server::Repository');
47     use_ok('Koha::OAI::Server::ResumptionToken');
48 }
49
50 use constant NUMBER_OF_MARC_RECORDS => 10;
51
52 # Mocked CGI module in order to be able to send CGI parameters to OAI Server
53 my %param;
54 my $module = Test::MockModule->new('CGI');
55 $module->mock('Vars', sub { %param; });
56
57 my $schema = Koha::Database->schema;
58 $schema->storage->txn_begin;
59 my $dbh = C4::Context->dbh;
60
61 $dbh->do("SET time_zone='+00:00'");
62 $dbh->do('DELETE FROM biblio');
63 $dbh->do('DELETE FROM deletedbiblio');
64 $dbh->do('DELETE FROM deletedbiblioitems');
65 $dbh->do('DELETE FROM deleteditems');
66
67 my $date_added = DateTime->now() . 'Z';
68 my $date_to = substr($date_added, 0, 10) . 'T23:59:59Z';
69 my (@header, @marcxml, @oaidc);
70 my $sth = $dbh->prepare('SELECT timestamp FROM biblioitems WHERE biblionumber=?');
71
72 # Add biblio records
73 foreach my $index ( 0 .. NUMBER_OF_MARC_RECORDS - 1 ) {
74     my $record = MARC::Record->new();
75     $record->append_fields( MARC::Field->new('245', '', '', 'a' => "Title $index" ) );
76     my ($biblionumber) = AddBiblio($record, '');
77     $sth->execute($biblionumber);
78     my $timestamp = $sth->fetchrow_array . 'Z';
79     $timestamp =~ s/ /T/;
80     $timestamp = manipulate_timestamp( $index, $biblionumber, $timestamp );
81     $record = GetMarcBiblio($biblionumber);
82     $record = XMLin($record->as_xml_record);
83     push @header, { datestamp => $timestamp, identifier => "TEST:$biblionumber" };
84     push @oaidc, {
85         header => $header[$index],
86         metadata => {
87             'oai_dc:dc' => {
88                 'dc:title' => "Title $index",
89                 'dc:language' => {},
90                 'dc:type' => {},
91                 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
92                 'xmlns:oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
93                 'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
94                 'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
95             },
96         },
97     };
98     push @marcxml, {
99         header => $header[$index],
100         metadata => {
101             record => $record,
102         },
103     };
104 }
105
106 my $syspref = {
107     'LibraryName'           => 'My Library',
108     'OAI::PMH'              => 1,
109     'OAI-PMH:archiveID'     => 'TEST',
110     'OAI-PMH:ConfFile'      => '',
111     'OAI-PMH:MaxCount'      => 3,
112     'OAI-PMH:DeletedRecord' => 'persistent',
113 };
114 while ( my ($name, $value) = each %$syspref ) {
115     t::lib::Mocks::mock_preference( $name => $value );
116 }
117
118 sub test_query {
119     my ($test, $param, $expected) = @_;
120
121     %param = %$param;
122     my %full_expected = (
123         %$expected,
124         (
125             request      => 'http://localhost',
126             responseDate => DateTime->now . 'Z',
127             xmlns        => 'http://www.openarchives.org/OAI/2.0/',
128             'xmlns:xsi'  => 'http://www.w3.org/2001/XMLSchema-instance',
129             'xsi:schemaLocation' => 'http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd',
130         )
131     );
132
133     my $response;
134     {
135         my $stdout;
136         local *STDOUT;
137         open STDOUT, '>', \$stdout;
138         Koha::OAI::Server::Repository->new();
139         $response = XMLin($stdout);
140     }
141
142     unless (is_deeply($response, \%full_expected, $test)) {
143         diag
144             "PARAM:" . Dump($param) .
145             "EXPECTED:" . Dump(\%full_expected) .
146             "RESPONSE:" . Dump($response);
147     }
148 }
149
150 test_query('ListMetadataFormats', {verb => 'ListMetadataFormats'}, {
151     ListMetadataFormats => {
152         metadataFormat => [
153             {
154                 metadataNamespace => 'http://www.openarchives.org/OAI/2.0/oai_dc/',
155                 metadataPrefix=> 'oai_dc',
156                 schema => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
157             },
158             {
159                 metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
160                 metadataPrefix => 'marc21',
161                 schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
162             },
163             {
164                 metadataNamespace => 'http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim',
165                 metadataPrefix => 'marcxml',
166                 schema => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
167             },
168         ],
169     },
170 });
171
172 test_query('ListIdentifiers without metadataPrefix', {verb => 'ListIdentifiers'}, {
173     error => {
174         code => 'badArgument',
175         content => "Required argument 'metadataPrefix' was undefined",
176     },
177 });
178
179 test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
180     ListIdentifiers => {
181         header => [ @header[0..2] ],
182         resumptionToken => {
183             content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
184             cursor  => 3,
185         },
186     },
187 });
188
189 test_query('ListIdentifiers', {verb => 'ListIdentifiers', metadataPrefix => 'marcxml'}, {
190     ListIdentifiers => {
191         header => [ @header[0..2] ],
192         resumptionToken => {
193             content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
194             cursor  => 3,
195         },
196     },
197 });
198
199 test_query(
200     'ListIdentifiers with resumptionToken 1',
201     { verb => 'ListIdentifiers', resumptionToken => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0" },
202     {
203         ListIdentifiers => {
204             header => [ @header[3..5] ],
205             resumptionToken => {
206               content => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0",
207               cursor  => 6,
208             },
209           },
210     },
211 );
212
213 test_query(
214     'ListIdentifiers with resumptionToken 2',
215     { verb => 'ListIdentifiers', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0" },
216     {
217         ListIdentifiers => {
218             header => [ @header[6..8] ],
219             resumptionToken => {
220               content => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0",
221               cursor  => 9,
222             },
223           },
224     },
225 );
226
227 test_query(
228     'ListIdentifiers with resumptionToken 3, response without resumption',
229     { verb => 'ListIdentifiers', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0" },
230     {
231         ListIdentifiers => {
232             header => $header[9],
233           },
234     },
235 );
236
237 test_query('ListRecords marcxml without metadataPrefix', {verb => 'ListRecords'}, {
238     error => {
239         code => 'badArgument',
240         content => "Required argument 'metadataPrefix' was undefined",
241     },
242 });
243
244 test_query('ListRecords marcxml', {verb => 'ListRecords', metadataPrefix => 'marcxml'}, {
245     ListRecords => {
246         record => [ @marcxml[0..2] ],
247         resumptionToken => {
248           content => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0",
249           cursor  => 3,
250         },
251     },
252 });
253
254 test_query(
255     'ListRecords marcxml with resumptionToken 1',
256     { verb => 'ListRecords', resumptionToken => "marcxml/3/1970-01-01T00:00:00Z/$date_to//0/0" },
257     { ListRecords => {
258         record => [ @marcxml[3..5] ],
259         resumptionToken => {
260           content => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0",
261           cursor  => 6,
262         },
263     },
264 });
265
266 test_query(
267     'ListRecords marcxml with resumptionToken 2',
268     { verb => 'ListRecords', resumptionToken => "marcxml/6/1970-01-01T00:00:00Z/$date_to//0/0" },
269     { ListRecords => {
270         record => [ @marcxml[6..8] ],
271         resumptionToken => {
272           content => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0",
273           cursor  => 9,
274         },
275     },
276 });
277
278 # Last record, so no resumption token
279 test_query(
280     'ListRecords marcxml with resumptionToken 3, response without resumption',
281     { verb => 'ListRecords', resumptionToken => "marcxml/9/1970-01-01T00:00:00Z/$date_to//0/0" },
282     { ListRecords => {
283         record => $marcxml[9],
284     },
285 });
286
287 test_query('ListRecords oai_dc', {verb => 'ListRecords', metadataPrefix => 'oai_dc'}, {
288     ListRecords => {
289         record => [ @oaidc[0..2] ],
290         resumptionToken => {
291           content => "oai_dc/3/1970-01-01T00:00:00Z/$date_to//0/0",
292           cursor  => 3,
293         },
294     },
295 });
296
297 test_query(
298     'ListRecords oai_dc with resumptionToken 1',
299     { verb => 'ListRecords', resumptionToken => "oai_dc/3/1970-01-01T00:00:00Z/$date_to//0/0" },
300     { ListRecords => {
301         record => [ @oaidc[3..5] ],
302         resumptionToken => {
303           content => "oai_dc/6/1970-01-01T00:00:00Z/$date_to//0/0",
304           cursor  => 6,
305         },
306     },
307 });
308
309 test_query(
310     'ListRecords oai_dc with resumptionToken 2',
311     { verb => 'ListRecords', resumptionToken => "oai_dc/6/1970-01-01T00:00:00Z/$date_to//0/0" },
312     { ListRecords => {
313         record => [ @oaidc[6..8] ],
314         resumptionToken => {
315           content => "oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0",
316           cursor  => 9,
317         },
318     },
319 });
320
321 # Last record, so no resumption token
322 test_query(
323     'ListRecords oai_dc with resumptionToken 3, response without resumption',
324     { verb => 'ListRecords', resumptionToken => "oai_dc/9/1970-01-01T00:00:00Z/$date_to//0/0" },
325     { ListRecords => {
326         record => $oaidc[9],
327     },
328 });
329
330 $schema->storage->txn_rollback;
331
332 sub manipulate_timestamp {
333 # This eliminates waiting a few seconds in order to get a higher timestamp
334 # Works only for 60 records..
335     my ( $index, $bibno, $timestamp ) = @_;
336     return $timestamp if $timestamp !~ /\d{2}Z/;
337     my $secs = sprintf( "%02d", $index );
338     $timestamp =~ s/\d{2}Z/${secs}Z/;
339     $dbh->do("UPDATE biblioitems SET timestamp=? WHERE biblionumber=?", undef,
340         ( $timestamp, $bibno ));
341     return $timestamp;
342 }