Bug 20144: [sql_modes] Fix search history tests
[koha.git] / t / db_dependent / Search / History.t
1 #!/usr/bin/env perl
2
3 use Modern::Perl;
4
5 use CGI qw ( -utf8 );
6 use Test::MockModule;
7 use List::MoreUtils qw/all any none/;
8 use t::lib::Mocks;
9 use t::lib::TestBuilder;
10
11 use C4::Auth;
12 use Koha::AuthUtils qw/hash_password/;
13 use Koha::Database;
14
15 use Test::More tests => 27;
16 use Test::Warn;
17 use URI::Escape;
18 use List::Util qw( shuffle );
19
20 use C4::Context;
21 use Koha::DateUtils;
22
23 my $schema = Koha::Database->new->schema;
24 $schema->storage->txn_begin;
25 my $dbh = C4::Context->dbh;
26
27 # FIXME: SessionStorage defaults to mysql, but it seems to break transaction
28 # handling
29 t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
30
31 use_ok('Koha::DateUtils');
32 use_ok('C4::Search::History');
33
34 my $userid = 123;
35 my $previous_sessionid = "PREVIOUS_SESSIONID";
36 my $current_sessionid = "CURRENT_SESSIONID";
37 my $total = 42;
38 my $query_cgi_b = q{idx=kw&idx=ti&idx=au%2Cwrdl&q=word1é&q=word2è&q=word3à&do=Search&sort_by=author_az};
39 my $query_cgi_a = q{op=do_search&type=opac&authtypecode=NP&operator=start&value=Harry&marclist=match&and_or=and&orderby=HeadingAsc};
40
41 # add
42 my $added = add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
43 is ( $added, 9, '9 searches are added' );
44
45 # get
46 my $searches_for_userid = C4::Search::History::get({
47     userid => $userid,
48 });
49 is( scalar(@$searches_for_userid), 9, 'There are 9 searches in all' );
50
51 my $searches_for_current_session = C4::Search::History::get({
52     userid => $userid,
53     sessionid => $current_sessionid,
54 });
55 is( scalar(@$searches_for_current_session), 5, 'There are 5 searches for the current session' );
56
57 my $searches_for_previous_sessions = C4::Search::History::get({
58     userid => $userid,
59     sessionid => $current_sessionid,
60     previous => 1,
61 });
62 is( scalar(@$searches_for_previous_sessions), 4, 'There are 4 searches for previous sessions' );
63
64 my $authority_searches_for_current_session = C4::Search::History::get({
65     userid => $userid,
66     sessionid => $current_sessionid,
67     type => 'authority',
68 });
69 is( scalar(@$authority_searches_for_current_session), 3, 'There are 3 authority searches for the current session' );
70
71 my $authority_searches_for_previous_session = C4::Search::History::get({
72     userid => $userid,
73     sessionid => $current_sessionid,
74     type => 'authority',
75     previous => 1,
76 });
77 is( scalar(@$authority_searches_for_previous_session), 2, 'There are 2 authority searches for previous sessions' );
78
79 my $biblio_searches_for_userid = C4::Search::History::get({
80     userid => $userid,
81     type => 'biblio',
82 });
83 is( scalar(@$biblio_searches_for_userid), 4, 'There are 5 searches for the current session' );
84
85 my $authority_searches_for_userid = C4::Search::History::get({
86     userid => $userid,
87     type => 'authority',
88 });
89 is( scalar(@$authority_searches_for_userid), 5, 'There are 4 searches for previous sessions' );
90
91 delete_all( $userid );
92
93 # delete
94 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
95 C4::Search::History::delete({
96     userid => $userid,
97     sessionid => $current_sessionid,
98     type => 'authority',
99 });
100 my $all = C4::Search::History::get({userid => $userid});
101 is( scalar(@$all), 6, 'There are 6 searches in all after deleting current biblio searches' );
102 delete_all( $userid );
103
104 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
105 C4::Search::History::delete({
106     userid => $userid,
107     sessionid => $current_sessionid,
108     type => 'biblio',
109     previous => 1,
110 });
111 $all = C4::Search::History::get({userid => $userid});
112 is( scalar(@$all), 7, 'There are 7 searches in all after deleting previous authority searches' );
113 delete_all( $userid );
114
115 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
116 C4::Search::History::delete({
117     userid => $userid,
118     sessionid => $current_sessionid,
119     previous => 1,
120 });
121 $all = C4::Search::History::get({userid => $userid});
122 is( scalar(@$all), 5, 'There are 5 searches in all after deleting all previous searches' );
123 delete_all( $userid );
124
125 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
126 C4::Search::History::delete({
127     userid => $userid,
128     sessionid => $current_sessionid,
129 });
130 $all = C4::Search::History::get({userid => $userid});
131 is( scalar(@$all), 4, 'There are 5 searches in all after deleting all searches for a sessionid' );
132 delete_all( $userid );
133
134 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
135 C4::Search::History::delete({
136     userid => $userid,
137 });
138 $all = C4::Search::History::get({userid => $userid});
139 is( scalar(@$all), 0, 'There are 0 search after deleting all searches for a userid' );
140 delete_all( $userid );
141
142 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
143 warning_like { C4::Search::History::delete({}) }
144           qr/^ERROR: userid, id or interval is required for history deletion/,
145           'Calling delete without userid raises warning';
146 $all = C4::Search::History::get({userid => $userid});
147 is( scalar(@$all), 9, 'There are still 9 searches after calling delete without userid' );
148 delete_all( $userid );
149
150 # Delete (with a given id)
151 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
152 $all = C4::Search::History::get({ userid => $userid });
153 # Delete 5 searches
154 my $ids = [ shuffle map { $_->{id} } @$all ];
155 for my $id ( @$ids[ 0 .. 4 ] ) {
156     C4::Search::History::delete({ id => $id });
157 }
158 $all = C4::Search::History::get({ userid => $userid });
159 is( scalar(@$all), 4, 'There are 4 searches after calling 5 times delete with id' );
160 delete_all( $userid );
161
162 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
163 $all = C4::Search::History::get({ userid => $userid });
164 # Delete 5 searches
165 $ids = [ shuffle map { $_->{id} } @$all ];
166 C4::Search::History::delete({ id => [ @$ids[0..4] ] });
167 $all = C4::Search::History::get({ userid => $userid });
168 is( scalar(@$all), 4, 'There are 4 searches after calling delete with 5 ids' );
169
170 delete_all( $userid );
171
172 # Test delete with interval
173 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
174 C4::Search::History::delete({
175     userid => $userid,
176     interval => 10,
177 });
178 $all = C4::Search::History::get({userid => $userid});
179 is( scalar(@$all), 9, 'There are still 9 searches after calling delete with an interval = 10 days' );
180 C4::Search::History::delete({
181     userid => $userid,
182     interval => 6,
183 });
184 $all = C4::Search::History::get({userid => $userid});
185 is( scalar(@$all), 8, 'There are still 8 searches after calling delete with an interval = 6 days' );
186 C4::Search::History::delete({
187     userid => $userid,
188     interval => 2,
189 });
190 $all = C4::Search::History::get({userid => $userid});
191 is( scalar(@$all), 2, 'There are still 2 searches after calling delete with an interval = 2 days' );
192 delete_all( $userid );
193
194 add( $userid, $current_sessionid, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a );
195 C4::Search::History::delete({
196     userid => $userid,
197     interval => 5,
198     type => 'biblio',
199 });
200 $all = C4::Search::History::get({userid => $userid});
201 is( scalar(@$all), 8, 'There are still 9 searches after calling delete with an interval = 5 days for biblio' );
202 C4::Search::History::delete({
203     userid => $userid,
204     interval => 5,
205     type => 'authority',
206 });
207 $all = C4::Search::History::get({userid => $userid});
208 is( scalar(@$all), 6, 'There are still 6 searches after calling delete with an interval = 5 days for authority' );
209 C4::Search::History::delete({
210     userid => $userid,
211     interval => -1,
212 });
213 $all = C4::Search::History::get({userid => $userid});
214 is( scalar(@$all), 0, 'There is no search after calling delete with an interval = -1 days' );
215
216 # If time is null, it must be set to NOW()
217 my $query_desc_b1_p = q{first previous biblio search};
218 C4::Search::History::add( {
219     userid => $userid,
220     sessionid => $previous_sessionid,
221     query_desc => $query_desc_b1_p,
222     query_cgi => $query_cgi_b,
223     total => $total,
224     type => 'biblio',
225 });
226 my $search_history_id = $dbh->last_insert_id( undef, undef, 'search_history', undef );
227 my $search_history = C4::Search::History::get({ id => $search_history_id });
228 is( output_pref({ dt => dt_from_string($search_history->[0]->{time}), dateonly => 1 }), output_pref({ dt => dt_from_string, dateonly => 1 }), "Inserting a new search history should handle undefined time" );
229
230
231 delete_all( $userid );
232
233 sub add {
234     my ( $userid, $current_session_id, $previous_sessionid, $total, $query_cgi_b, $query_cgi_a ) = @_;
235
236     my $days_ago_2 = dt_from_string()->add_duration( DateTime::Duration->new( days => -2, minutes => 1 ) );
237     my $days_ago_4 = dt_from_string()->add_duration( DateTime::Duration->new( days => -4, minutes => 1 ) );
238     my $days_ago_6 = dt_from_string()->add_duration( DateTime::Duration->new( days => -6, minutes => 1 ) );
239     my $days_ago_8 = dt_from_string()->add_duration( DateTime::Duration->new( days => -8, minutes => 1 ) );
240
241     my $query_desc_b1_p = q{first previous biblio search};
242     my $first_previous_biblio_search = {
243         userid => $userid,
244         sessionid => $previous_sessionid,
245         query_desc => $query_desc_b1_p,
246         query_cgi => $query_cgi_b,
247         total => $total,
248         type => 'biblio',
249         time => $days_ago_2,
250     };
251
252     my $query_desc_a1_p = q{first previous authority search};
253     my $first_previous_authority_search = {
254         userid => $userid,
255         sessionid => $previous_sessionid,
256         query_desc => $query_desc_a1_p,
257         query_cgi => $query_cgi_a,
258         total => $total,
259         type => 'authority',
260         time => $days_ago_2,
261     };
262
263     my $query_desc_b2_p = q{second previous biblio search};
264     my $second_previous_biblio_search = {
265         userid => $userid,
266         sessionid => $previous_sessionid,
267         query_desc => $query_desc_b2_p,
268         query_cgi => $query_cgi_b,
269         total => $total,
270         type => 'biblio',
271         time => $days_ago_4,
272     };
273
274     my $query_desc_a2_p = q{second previous authority search};
275     my $second_previous_authority_search = {
276         userid => $userid,
277         sessionid => $previous_sessionid,
278         query_desc => $query_desc_a2_p,
279         query_cgi => $query_cgi_a,
280         total => $total,
281         type => 'authority',
282         time => $days_ago_4,
283     };
284
285
286     my $query_desc_b1_c = q{first current biblio search};
287
288     my $first_current_biblio_search = {
289         userid => $userid,
290         sessionid => $current_sessionid,
291         query_desc => $query_desc_b1_c,
292         query_cgi => $query_cgi_b,
293         total => $total,
294         type => 'biblio',
295         time => $days_ago_4,
296     };
297
298     my $query_desc_a1_c = q{first current authority search};
299     my $first_current_authority_search = {
300         userid => $userid,
301         sessionid => $current_sessionid,
302         query_desc => $query_desc_a1_c,
303         query_cgi => $query_cgi_a,
304         total => $total,
305         type => 'authority',
306         time => $days_ago_4,
307     };
308
309     my $query_desc_b2_c = q{second current biblio search};
310     my $second_current_biblio_search = {
311         userid => $userid,
312         sessionid => $current_sessionid,
313         query_desc => $query_desc_b2_c,
314         query_cgi => $query_cgi_b,
315         total => $total,
316         type => 'biblio',
317         time => $days_ago_6,
318     };
319
320     my $query_desc_a2_c = q{second current authority search};
321     my $second_current_authority_search = {
322         userid => $userid,
323         sessionid => $current_sessionid,
324         query_desc => $query_desc_a2_c,
325         query_cgi => $query_cgi_a,
326         total => $total,
327         type => 'authority',
328         time => $days_ago_6,
329     };
330
331     my $query_desc_a3_c = q{third current authority search};
332     my $third_current_authority_search = {
333         userid => $userid,
334         sessionid => $current_sessionid,
335         query_desc => $query_desc_a3_c,
336         query_cgi => $query_cgi_a,
337         total => $total,
338         type => 'authority',
339         time => $days_ago_8,
340     };
341
342
343     my $r = 0;
344     $r += C4::Search::History::add( $first_current_biblio_search );
345     $r += C4::Search::History::add( $first_current_authority_search );
346     $r += C4::Search::History::add( $second_current_biblio_search );
347     $r += C4::Search::History::add( $second_current_authority_search );
348     $r += C4::Search::History::add( $first_previous_biblio_search );
349     $r += C4::Search::History::add( $first_previous_authority_search );
350     $r += C4::Search::History::add( $second_previous_biblio_search );
351     $r += C4::Search::History::add( $second_previous_authority_search );
352     $r += C4::Search::History::add( $third_current_authority_search );
353     return $r;
354 }
355
356 sub delete_all {
357     my $userid = shift;
358     C4::Search::History::delete({
359         userid => $userid,
360     });
361 }
362
363 subtest 'LoadSearchHistoryToTheFirstLoggedUser working' => sub {
364 plan tests =>2;
365
366 my $query = new CGI;
367
368 my $schema = Koha::Database->schema;
369 my $builder = t::lib::TestBuilder->new;
370
371 # Borrower Creation
372 my $hash = hash_password('password');
373 our $patron = $builder->build( { source => 'Borrower' } );
374 Koha::Patrons->find( $patron->{borrowernumber} )->update_password( $patron->{userid}, $hash );
375
376 my $session = C4::Auth::get_session("");
377 $session->flush;
378
379 sub myMockedget_from_session {
380     my $expected_recent_searches = [
381         {
382             'time' => dt_from_string,
383             'query_cgi' => 'cgi_test',
384             'total' => 2,
385             'query_desc' => 'kw,wrdl: history, '
386         }
387     ];
388     return @{$expected_recent_searches};
389
390 }
391
392 my $getfrom = new Test::MockModule( 'C4::Search::History' );
393 $getfrom->mock( 'get_from_session', \&myMockedget_from_session );
394
395 my $cgi = new Test::MockModule( 'CGI');
396 $cgi->mock('cookie', sub {
397    my ($self, $key) = @_;
398   if (!ref($key) && $key eq 'CGISESSID'){
399          return 'ID';
400    }
401 });
402
403 sub MockedCheckauth {
404     my ($query,$authnotrequired,$flagsrequired,$type) = @_;
405     my $userid = $patron->{userid};
406     my $sessionID = 234;
407     my $flags = {
408         superlibrarian    => 1, acquisition       => 0,
409         borrowers         => 0,
410         catalogue         => 1, circulate         => 0,
411         coursereserves    => 0, editauthorities   => 0,
412         editcatalogue     => 0, management        => 0,
413         parameters        => 0, permissions       => 0,
414         plugins           => 0, reports           => 0,
415         reserveforothers  => 0, serials           => 0,
416         staffaccess       => 0, tools             => 0,
417         updatecharges     => 0
418     };
419
420     my $session_cookie = $query->cookie(
421         -name => 'CGISESSID',
422         -value    => '9884013ae2c441d12e0bc9376242d2a8',
423         -HttpOnly => 1
424     );
425     return ( $userid, $session_cookie, $sessionID, $flags );
426 }
427
428 # Mock checkauth
429 my $auth = new Test::MockModule( 'C4::Auth' );
430 $auth->mock( 'checkauth', \&MockedCheckauth );
431
432 $query->param('koha_login_context', 'opac');
433 $query->param('userid', $patron->{userid});
434 $query->param('password', 'password');
435
436 # Test when the syspref is disabled
437 t::lib::Mocks::mock_preference('LoadSearchHistoryToTheFirstLoggedUser', 0);
438 my $result = $schema->resultset('SearchHistory')->search()->count;
439
440 my ( $template, $loggedinuser, $cookies ) = get_template_and_user(
441     {
442         template_name   => "opac-user.tt",
443         query           => $query,
444         type            => "opac",
445         authnotrequired => 0,
446         debug           => 1
447     }
448 );
449
450 my $result2 = $schema->resultset('SearchHistory')->search()->count;
451 is($result2, $result, 'no new search added to borrower');
452
453 # Test when the syspref is enabled
454 t::lib::Mocks::mock_preference('LoadSearchHistoryToTheFirstLoggedUser', 1);
455 $query->param('koha_login_context', 'opac');
456 $query->param('userid', $patron->{userid});
457 $query->param('password', 'password');
458 $query->cookie(
459         -name     => 'CGISESSID',
460         -value    => $session->id,
461         -HttpOnly => 1
462 );
463
464 $result = $schema->resultset('SearchHistory')->search()->count;
465
466 ( $template, $loggedinuser, $cookies ) = get_template_and_user(
467     {
468         template_name   => "opac-user.tt",
469         query           => $query,
470         type            => "opac",
471         authnotrequired => 0,
472         debug           => 1
473     }
474 );
475
476 $result2 = $schema->resultset('SearchHistory')->search()->count;
477 is($result2, $result+1, 'new search added to borrower');
478 };
479
480 done_testing;