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