Bug 29857: (follow-up) Fix t/db_dependent/Koha/Club/Hold.t
[koha.git] / t / db_dependent / Letters.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright (C) 2013 Equinox Software, Inc.
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 use Test::More tests => 83;
22 use Test::MockModule;
23 use Test::Warn;
24 use Test::Exception;
25
26 use Email::Sender::Failure;
27
28 use MARC::Record;
29
30 use utf8;
31
32 my ( $email_object, $sendmail_params );
33
34 my $email_sender_module = Test::MockModule->new('Email::Stuffer');
35 $email_sender_module->mock(
36     'send_or_die',
37     sub {
38         ( $email_object, $sendmail_params ) = @_;
39         my $str = $email_object->email->as_string;
40         unlike $str, qr/I =C3=A2=C2=99=C2=A5 Koha=/, "Content is not double encoded";
41         warn "Fake send_or_die";
42     }
43 );
44
45 use_ok('C4::Context');
46 use_ok('C4::Members');
47 use_ok('C4::Acquisition', qw( NewBasket ));
48 use_ok('C4::Biblio', qw( AddBiblio GetBiblioData ));
49 use_ok('C4::Letters', qw( GetMessageTransportTypes GetMessage EnqueueLetter GetQueuedMessages SendQueuedMessages ResendMessage GetLetters GetPreparedLetter SendAlerts ));
50 use t::lib::Mocks;
51 use t::lib::TestBuilder;
52 use Koha::Database;
53 use Koha::DateUtils qw( dt_from_string output_pref );
54 use Koha::Acquisition::Booksellers;
55 use Koha::Acquisition::Bookseller::Contacts;
56 use Koha::Acquisition::Orders;
57 use Koha::Libraries;
58 use Koha::Notice::Templates;
59 use Koha::Patrons;
60 use Koha::Subscriptions;
61 my $schema = Koha::Database->schema;
62 $schema->storage->txn_begin();
63
64 my $builder = t::lib::TestBuilder->new;
65 my $dbh = C4::Context->dbh;
66
67 $dbh->do(q|DELETE FROM letter|);
68 $dbh->do(q|DELETE FROM message_queue|);
69 $dbh->do(q|DELETE FROM message_transport_types|);
70
71 my $library = $builder->build({
72     source => 'Branch',
73     value  => {
74         branchemail      => 'branchemail@address.com',
75         branchreplyto    => 'branchreplyto@address.com',
76         branchreturnpath => 'branchreturnpath@address.com',
77     }
78 });
79 my $patron_category = $builder->build({ source => 'Category' })->{categorycode};
80 my $date = dt_from_string;
81 my $borrowernumber = Koha::Patron->new({
82     firstname    => 'Jane',
83     surname      => 'Smith',
84     categorycode => $patron_category,
85     branchcode   => $library->{branchcode},
86     dateofbirth  => $date,
87     smsalertnumber => undef,
88 })->store->borrowernumber;
89
90 my $marc_record = MARC::Record->new;
91 my( $biblionumber, $biblioitemnumber ) = AddBiblio( $marc_record, '' );
92
93
94
95 # GetMessageTransportTypes
96 my $mtts = C4::Letters::GetMessageTransportTypes();
97 is( @$mtts, 0, 'GetMessageTransportTypes returns the correct number of message types' );
98
99 $dbh->do(q|
100     INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
101 |);
102 $mtts = C4::Letters::GetMessageTransportTypes();
103 is_deeply( $mtts, ['email', 'phone', 'print', 'sms'], 'GetMessageTransportTypes returns all values' );
104
105
106 # EnqueueLetter
107 is( C4::Letters::EnqueueLetter(), undef, 'EnqueueLetter without argument returns undef' );
108
109 my $my_message = {
110     borrowernumber         => $borrowernumber,
111     message_transport_type => 'sms',
112     to_address             => undef,
113     from_address           => 'from@example.com',
114 };
115 my $message_id = C4::Letters::EnqueueLetter($my_message);
116 is( $message_id, undef, 'EnqueueLetter without the letter argument returns undef' );
117
118 delete $my_message->{message_transport_type};
119 $my_message->{letter} = {
120     content      => 'I ♥ Koha',
121     title        => '啤酒 is great',
122     metadata     => 'metadata',
123     code         => 'TEST_MESSAGE',
124     content_type => 'text/plain',
125 };
126
127 $message_id = C4::Letters::EnqueueLetter($my_message);
128 is( $message_id, undef, 'EnqueueLetter without the message type argument argument returns undef' );
129
130 $my_message->{message_transport_type} = 'sms';
131 $message_id = C4::Letters::EnqueueLetter($my_message);
132 ok(defined $message_id && $message_id > 0, 'new message successfully queued');
133
134
135 # GetQueuedMessages
136 my $messages = C4::Letters::GetQueuedMessages();
137 is( @$messages, 1, 'GetQueuedMessages without argument returns all the entries' );
138
139 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
140 is( @$messages, 1, 'one message stored for the borrower' );
141 is( $messages->[0]->{message_id}, $message_id, 'EnqueueLetter returns the message id correctly' );
142 is( $messages->[0]->{borrowernumber}, $borrowernumber, 'EnqueueLetter stores the borrower number correctly' );
143 is( $messages->[0]->{subject}, $my_message->{letter}->{title}, 'EnqueueLetter stores the subject correctly' );
144 is( $messages->[0]->{content}, $my_message->{letter}->{content}, 'EnqueueLetter stores the content correctly' );
145 is( $messages->[0]->{message_transport_type}, $my_message->{message_transport_type}, 'EnqueueLetter stores the message type correctly' );
146 is( $messages->[0]->{status}, 'pending', 'EnqueueLetter stores the status pending correctly' );
147 isnt( $messages->[0]->{time_queued}, undef, 'Time queued inserted by default in message_queue table' );
148 is( $messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed equals time queued when created in message_queue table' );
149 is( $messages->[0]->{failure_code}, '', 'Failure code for successful message correctly empty');
150
151 # Setting time_queued to something else than now
152 my $yesterday = dt_from_string->subtract( days => 1 );
153 Koha::Notice::Messages->find($messages->[0]->{message_id})->time_queued($yesterday)->store;
154
155 # SendQueuedMessages
156 my $messages_processed = C4::Letters::SendQueuedMessages( { type => 'email' });
157 is($messages_processed, 0, 'No queued messages processed if type limit passed with unused type');
158 $messages_processed = C4::Letters::SendQueuedMessages( { type => 'sms' });
159 is($messages_processed, 1, 'All queued messages processed, found correct number of messages with type limit');
160 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
161 is(
162     $messages->[0]->{status},
163     'failed',
164     'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
165 );
166 is(
167     $messages->[0]->{failure_code},
168     'MISSING_SMS',
169     'Correct failure code set for borrower with no smsalertnumber set'
170 );
171 isnt($messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed differs from time queued when status changes' );
172 is(dt_from_string($messages->[0]->{time_queued}), $yesterday, 'Time queued remaines inmutable' );
173
174 # ResendMessage
175 my $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
176 my $message = C4::Letters::GetMessage( $messages->[0]->{message_id});
177 is( $resent, 1, 'The message should have been resent' );
178 is($message->{status},'pending', 'ResendMessage sets status to pending correctly (bug 12426)');
179 $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
180 is( $resent, 0, 'The message should not have been resent again' );
181 $resent = C4::Letters::ResendMessage();
182 is( $resent, undef, 'ResendMessage should return undef if not message_id given' );
183
184 # Delivery notes
185 is( $messages->[0]->{failure_code}, 'MISSING_SMS', 'Failure code set correctly for no smsalertnumber correctly set' );
186
187 # GetLetters
188 my $letters = C4::Letters::GetLetters();
189 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
190
191 my $title = q|<<branches.branchname>> - <<status>>|;
192 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>,
193 According to our current records, you have items that are overdue.Your library does not charge late fines, but please return or renew them at the branch below as soon as possible.
194
195 <<branches.branchname>>
196 <<branches.branchaddress1>>
197 URL: <<OPACBaseURL>>
198
199 The following item(s) is/are currently <<status>>:
200
201 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
202
203 Thank-you for your prompt attention to this matter.
204 Don't forget your date of birth: <<borrowers.dateofbirth>>.
205 Look at this wonderful biblio timestamp: <<biblio.timestamp>>.
206 };
207
208 $dbh->do( q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','my code','my name',1,?,?,'email')|, undef, $library->{branchcode}, $title, $content );
209 $letters = C4::Letters::GetLetters();
210 is( @$letters, 1, 'GetLetters returns the correct number of letters' );
211 is( $letters->[0]->{module}, 'my module', 'GetLetters gets the module correctly' );
212 is( $letters->[0]->{code}, 'my code', 'GetLetters gets the code correctly' );
213 is( $letters->[0]->{name}, 'my name', 'GetLetters gets the name correctly' );
214
215 # GetPreparedLetter
216 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
217 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
218
219 my $sms_content = 'This is a SMS for an <<status>>';
220 $dbh->do( q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','my code','my name',1,'my title',?,'sms')|, undef, $library->{branchcode}, $sms_content );
221
222 my $tables = {
223     borrowers => $borrowernumber,
224     branches => $library->{branchcode},
225     biblio => $biblionumber,
226 };
227 my $substitute = {
228     status => 'overdue',
229 };
230 my $repeat = [
231     {
232         itemcallnumber => 'my callnumber1',
233         barcode        => '1234',
234     },
235     {
236         itemcallnumber => 'my callnumber2',
237         barcode        => '5678',
238     },
239 ];
240 my $prepared_letter = GetPreparedLetter((
241     module      => 'my module',
242     branchcode  => $library->{branchcode},
243     letter_code => 'my code',
244     tables      => $tables,
245     substitute  => $substitute,
246     repeat      => $repeat,
247 ));
248 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
249 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
250 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
251 my $my_content_letter = qq|Dear Jane Smith,
252 According to our current records, you have items that are overdue.Your library does not charge late fines, but please return or renew them at the branch below as soon as possible.
253
254 |.$retrieved_library->branchname.qq|
255 |.$retrieved_library->branchaddress1.qq|
256 URL: http://thisisatest.com
257
258 The following item(s) is/are currently $substitute->{status}:
259
260 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
261 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
262
263 Thank-you for your prompt attention to this matter.
264 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
265 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp })  . ".\n";
266
267 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
268 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
269
270 $prepared_letter = GetPreparedLetter((
271     module                 => 'my module',
272     branchcode             => $library->{branchcode},
273     letter_code            => 'my code',
274     tables                 => $tables,
275     substitute             => $substitute,
276     repeat                 => $repeat,
277     message_transport_type => 'sms',
278 ));
279 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
280 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
281
282 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('test_date','TEST_DATE','Test dates','A title with a timestamp: <<biblio.timestamp>>','This one only contains the date: <<biblio.timestamp | dateonly>>.');});
283 $prepared_letter = GetPreparedLetter((
284     module                 => 'test_date',
285     branchcode             => '',
286     letter_code            => 'test_date',
287     tables                 => $tables,
288     substitute             => $substitute,
289     repeat                 => $repeat,
290 ));
291 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
292
293 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
294 $prepared_letter = GetPreparedLetter((
295     module                 => 'test_date',
296     branchcode             => '',
297     letter_code            => 'test_date',
298     tables                 => $tables,
299     substitute             => $substitute,
300     repeat                 => $repeat,
301 ));
302 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
303
304 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
305 $prepared_letter = GetPreparedLetter((
306     module                 => 'test_date',
307     branchcode             => '',
308     letter_code            => 'test_date',
309     tables                 => $tables,
310     substitute             => $substitute,
311     repeat                 => $repeat,
312 ));
313 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
314
315 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
316 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
317 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
318 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
319 $prepared_letter = GetPreparedLetter((
320     module                 => 'test_date',
321     branchcode             => '',
322     letter_code            => 'test_date',
323     tables                 => $tables,
324     substitute             => $substitute,
325     repeat                 => $repeat,
326 ));
327 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
328
329 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('claimacquisition','TESTACQCLAIM','Acquisition Claim','Item Not Received','<<aqbooksellers.name>>|<<aqcontacts.name>>|<order>Ordernumber <<aqorders.ordernumber>> (<<biblio.title>>) (<<aqorders.quantity>> ordered)</order>');});
330 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER','Acquisition Order','Order','<<aqbooksellers.name>>|<<aqcontacts.name>>|<order>Ordernumber <<aqorders.ordernumber>> (<<biblio.title>>) (<<aqorders.quantity>> ordered)</order>');});
331
332 # Test that _parseletter doesn't modify its parameters bug 15429
333 {
334     my $values = { dateexpiry => '2015-12-13', };
335     C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
336     is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
337 }
338
339 # Correctly format dateexpiry
340 {
341     my $values = { dateexpiry => '2015-12-13', };
342
343     t::lib::Mocks::mock_preference('dateformat', 'metric');
344     t::lib::Mocks::mock_preference('timeformat', '24hr');
345     my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
346     is( $letter->{content}, 'expiry on 13/12/2015' );
347
348     t::lib::Mocks::mock_preference('dateformat', 'metric');
349     t::lib::Mocks::mock_preference('timeformat', '12hr');
350     $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
351     is( $letter->{content}, 'expiry on 13/12/2015' );
352 }
353
354 my $bookseller = Koha::Acquisition::Bookseller->new(
355     {
356         name => "my vendor",
357         address1 => "bookseller's address",
358         phone => "0123456",
359         active => 1,
360         deliverytime => 5,
361     }
362 )->store;
363 my $booksellerid = $bookseller->id;
364
365 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith',  phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
366 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues      => 1, booksellerid => $booksellerid } )->store;
367 my $basketno = NewBasket($booksellerid, 1);
368
369 my $budgetid = C4::Budgets::AddBudget({
370     budget_code => "budget_code_test_letters",
371     budget_name => "budget_name_test_letters",
372 });
373
374 my $bib = MARC::Record->new();
375 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
376     $bib->append_fields(
377         MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
378     );
379 } else {
380     $bib->append_fields(
381         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
382     );
383 }
384
385 my $logged_in_user = $builder->build_object(
386     {
387         class => 'Koha::Patrons',
388         value => {
389             branchcode => $library->{branchcode},
390             email      => 'some@email.com'
391         }
392     }
393 );
394
395 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
396
397 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
398 my $order = Koha::Acquisition::Order->new(
399     {
400         basketno => $basketno,
401         quantity => 1,
402         biblionumber => $biblionumber,
403         budget_id => $budgetid,
404     }
405 )->store;
406 my $ordernumber = $order->ordernumber;
407
408 Koha::Acquisition::Baskets->find( $basketno )->close;
409 my $err;
410 warning_like {
411     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
412     qr/^Bookseller .* without emails at/,
413     "SendAlerts prints a warning";
414 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
415
416 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
417 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
418
419 # Ensure that the preference 'ClaimsLog' is set to logging
420 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
421
422 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
423 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
424
425 {
426 warning_like {
427     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
428     qr|Fake send_or_die|,
429     "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
430 is($err, 1, "Successfully sent order.");
431 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
432 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Order notice text constructed successfully');
433
434 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
435 $mocked_koha_email->mock( 'send_or_die', sub {
436     Email::Sender::Failure->throw('something went wrong');
437 });
438
439 warning_like {
440     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
441     qr{something went wrong},
442     'Warning is printed';
443
444 is($err->{error}, 'something went wrong', "Send exception, error message returned");
445
446 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
447 warning_like {
448     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
449     qr/No orderacquisition TESTACQORDER letter transported by email/,
450     "GetPreparedLetter warns about missing notice template";
451 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
452 }
453
454 {
455 warning_like {
456     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
457     qr|Fake send_or_die|,
458     "SendAlerts is using the mocked send_or_die routine";
459
460 is($err, 1, "Successfully sent claim");
461 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
462 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
463
464 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
465 $mocked_koha_email->mock( 'send_or_die', sub {
466     Email::Sender::Failure->throw('something went wrong');
467 });
468
469 warning_like {
470     $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
471     qr{something went wrong},
472     'Warning is printed';
473
474 is($err->{error}, 'something went wrong', "Send exception, error message returned");
475 }
476
477 {
478 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
479
480 my $notes = 'notes';
481 my $internalnotes = 'intnotes';
482 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
483 my $subscriptionid = NewSubscription(
484      undef,      "",     undef, undef, undef, $biblionumber,
485     '2013-01-01', 1, undef, undef,  undef,
486     undef,      undef,  undef, undef, undef, undef,
487     1,          $notes,undef, '2013-01-01', undef, 1,
488     undef,       undef,  0,    $internalnotes,  0,
489     undef, undef, 0,          undef,         '2013-12-31', 0
490 );
491 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('serial','RLIST','Serial issue notification','Serial issue notification','<<biblio.title>>,<<subscription.subscriptionid>>,<<serial.serialseq>>');});
492 my ($serials_count, @serials) = GetSerials($subscriptionid);
493 my $serial = $serials[0];
494
495 my $patron = Koha::Patron->new({
496     firstname    => 'John',
497     surname      => 'Smith',
498     categorycode => $patron_category,
499     branchcode   => $library->{branchcode},
500     dateofbirth  => $date,
501     email        => 'john.smith@test.de',
502 })->store;
503 my $borrowernumber = $patron->borrowernumber;
504 my $subscription = Koha::Subscriptions->find( $subscriptionid );
505 $subscription->add_subscriber( $patron );
506
507 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
508 my $err2;
509 warning_like {
510 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
511     qr|Fake send_or_die|,
512     "SendAlerts is using the mocked send_or_die routine";
513
514 is($err2, 1, "Successfully sent serial notification");
515 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
516 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
517
518 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
519
520 my $err3;
521 warning_like {
522 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
523     qr|Fake send_or_die|,
524     "SendAlerts is using the mocked send_or_die routine";
525 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
526
527 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
528 $mocked_koha_email->mock( 'send_or_die', sub {
529     Email::Sender::Failure->throw('something went wrong');
530 });
531
532 warning_like {
533     $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
534     qr{something went wrong},
535     'Warning is printed';
536
537 is($err->{error}, 'something went wrong', "Send exception, error message returned");
538
539 }
540 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
541
542 subtest 'SendAlerts - claimissue' => sub {
543     plan tests => 13;
544
545     use C4::Serials;
546
547     $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('claimissues','TESTSERIALCLAIM','Serial claim test','Serial claim test','<<serial.serialid>>|<<subscription.startdate>>|<<biblio.title>>|<<biblioitems.issn>>');});
548
549     my $bookseller = Koha::Acquisition::Bookseller->new(
550         {
551             name => "my vendor",
552             address1 => "bookseller's address",
553             phone => "0123456",
554             active => 1,
555             deliverytime => 5,
556         }
557     )->store;
558     my $booksellerid = $bookseller->id;
559
560     Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
561
562     my $bib = MARC::Record->new();
563     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
564         $bib->append_fields(
565             MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
566             MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
567         );
568     } else {
569         $bib->append_fields(
570             MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
571             MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
572         );
573     }
574     my ($biblionumber) = AddBiblio($bib, '');
575
576     $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
577     my $subscriptionid = NewSubscription(
578          undef, "", $booksellerid, undef, undef, $biblionumber,
579         '2013-01-01', 1, undef, undef,  undef,
580         undef,  undef,  undef, undef, undef, undef,
581         1, 'public',undef, '2013-01-01', undef, 1,
582         undef, undef,  0, 'internal',  0,
583         undef, undef, 0,  undef, '2013-12-31', 0
584     );
585
586     my ($serials_count, @serials) = GetSerials($subscriptionid);
587     my  @serialids = ($serials[0]->{serialid});
588
589     my $err;
590     warning_like {
591         $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
592         qr/^Bookseller .* without emails at/,
593         "Warn on vendor without email address";
594
595     $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
596     $bookseller->contacts->next->email('testemail@mydomain.com')->store;
597
598     # Ensure that the preference 'ClaimsLog' is set to logging
599     t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
600
601     # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
602     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
603
604     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
605
606     {
607     warning_like {
608         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
609         qr|Fake send_or_die|,
610         "SendAlerts is using the mocked send_or_die routine (claimissues)";
611     is( $err, 1, "Successfully sent claim" );
612     is( $email_object->email->header('To'),
613         'testemail@mydomain.com', "mailto correct in sent claim" );
614     is(
615         $email_object->email->body,
616         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
617         'Serial claim letter for 1 issue constructed successfully'
618     );
619
620     my $mocked_koha_email = Test::MockModule->new('Koha::Email');
621     $mocked_koha_email->mock( 'send_or_die', sub {
622             Email::Sender::Failure->throw('something went wrong');
623     });
624
625     warning_like {
626         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
627         qr{something went wrong},
628         'Warning is printed';
629
630     is($err->{error}, 'something went wrong', "Send exception, error message returned");
631     }
632
633     {
634     my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
635     my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
636     ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
637     ($serials_count, @serials) = GetSerials($subscriptionid);
638     push @serialids, ($serials[1]->{serialid});
639
640     warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
641         qr|Fake send_or_die|,
642         "SendAlerts is using the mocked send_or_die routine (claimissues)";
643
644     is(
645         $email_object->email->body,
646         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
647           . $email_object->email->crlf
648           . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
649         "Serial claim letter for 2 issues constructed successfully"
650     );
651
652     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
653     warning_like {
654         $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
655         qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
656         "GetPreparedLetter warns about missing notice template";
657     is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
658     }
659
660 };
661
662 subtest 'GetPreparedLetter' => sub {
663     plan tests => 4;
664
665     Koha::Notice::Template->new(
666         {
667             module                 => 'test',
668             code                   => 'test',
669             branchcode             => '',
670             message_transport_type => 'email'
671         }
672     )->store;
673     my $letter;
674     warning_like {
675         $letter = C4::Letters::GetPreparedLetter(
676             module      => 'test',
677             letter_code => 'test',
678         );
679     }
680     qr{^ERROR: nothing to substitute},
681 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
682     is( $letter, undef,
683 'No letter should be returned by GetPreparedLetter if something went wrong'
684     );
685
686     warning_like {
687         $letter = C4::Letters::GetPreparedLetter(
688             module      => 'test',
689             letter_code => 'test',
690             substitute  => {}
691         );
692     }
693     qr{^ERROR: nothing to substitute},
694 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
695     is( $letter, undef,
696 'No letter should be returned by GetPreparedLetter if something went wrong'
697     );
698
699 };
700
701
702
703 subtest 'TranslateNotices' => sub {
704     plan tests => 4;
705
706     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
707
708     $dbh->do(
709         q|
710         INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
711         ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
712         ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
713     | );
714     my $substitute = {};
715     my $letter = C4::Letters::GetPreparedLetter(
716             module                 => 'test',
717             tables                 => $tables,
718             letter_code            => 'code',
719             message_transport_type => 'email',
720             substitute             => $substitute,
721     );
722     is(
723         $letter->{title},
724         'a test',
725         'GetPreparedLetter should return the default one if the lang parameter is not provided'
726     );
727
728     $letter = C4::Letters::GetPreparedLetter(
729             module                 => 'test',
730             tables                 => $tables,
731             letter_code            => 'code',
732             message_transport_type => 'email',
733             substitute             => $substitute,
734             lang                   => 'es-ES',
735     );
736     is( $letter->{title}, 'una prueba',
737         'GetPreparedLetter should return the required notice if it exists' );
738
739     $letter = C4::Letters::GetPreparedLetter(
740             module                 => 'test',
741             tables                 => $tables,
742             letter_code            => 'code',
743             message_transport_type => 'email',
744             substitute             => $substitute,
745             lang                   => 'fr-FR',
746     );
747     is(
748         $letter->{title},
749         'a test',
750         'GetPreparedLetter should return the default notice if the one required does not exist'
751     );
752
753     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
754
755     $letter = C4::Letters::GetPreparedLetter(
756             module                 => 'test',
757             tables                 => $tables,
758             letter_code            => 'code',
759             message_transport_type => 'email',
760             substitute             => $substitute,
761             lang                   => 'es-ES',
762     );
763     is( $letter->{title}, 'a test',
764         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
765
766 };
767
768 subtest 'SendQueuedMessages' => sub {
769
770     plan tests => 12;
771
772     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
773     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
774
775     my $patron = Koha::Patrons->find($borrowernumber);
776     $dbh->do(q|
777         INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
778         VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
779         |, undef, $borrowernumber
780     );
781     eval { C4::Letters::SendQueuedMessages(); };
782     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
783
784     my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
785     $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
786     $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
787
788     warning_like { C4::Letters::SendQueuedMessages(); }
789         qr|Fake send_or_die|,
790         "SendAlerts is using the mocked send_or_die routine (claimissues)";
791
792     my $message = $schema->resultset('MessageQueue')->search({
793         borrowernumber => $borrowernumber,
794         status => 'sent'
795     })->next();
796
797     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
798     is(
799         $message->from_address(),
800         'from@example.com',
801         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
802     );
803
804     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
805
806     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
807
808     $message_id = C4::Letters::EnqueueLetter($my_message);
809     warning_like { C4::Letters::SendQueuedMessages(); }
810         qr|Fake send_or_die|,
811         "SendAlerts is using the mocked send_or_die routine (claimissues)";
812
813     $message = $schema->resultset('MessageQueue')->search({
814         borrowernumber => $borrowernumber,
815         status => 'sent'
816     })->next();
817
818     is(
819         $message->from_address(),
820         'override@example.com',
821         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
822     );
823
824     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
825     $my_message->{to_address} = 'fixme@kidclamp.iswrong';
826     $message_id = C4::Letters::EnqueueLetter($my_message);
827
828     my $number_attempted = C4::Letters::SendQueuedMessages({
829         borrowernumber => -1, # -1 still triggers the borrowernumber condition
830         letter_code    => 'PASSWORD_RESET',
831     });
832     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
833
834     warning_like { C4::Letters::SendQueuedMessages(); }
835         qr|Fake send_or_die|,
836         "SendAlerts is using the mocked send_or_die routine (claimissues)";
837
838     my $sms_message_address = $schema->resultset('MessageQueue')->search({
839         borrowernumber => $borrowernumber,
840         status => 'sent'
841     })->next()->to_address();
842     is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
843
844 };
845
846 subtest 'get_item_content' => sub {
847     plan tests => 2;
848
849     t::lib::Mocks::mock_preference('dateformat', 'metric');
850     t::lib::Mocks::mock_preference('timeformat', '24hr');
851     my @items = (
852         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
853         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
854     );
855     my @item_content_fields = qw( date_due title barcode author itemnumber );
856
857     my $items_content;
858     for my $item ( @items ) {
859         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
860     }
861
862     my $expected_items_content = <<EOF;
863 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
864 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
865 EOF
866     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
867
868
869     $items_content = q||;
870     for my $item ( @items ) {
871         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
872     }
873
874     $expected_items_content = <<EOF;
875 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
876 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
877 EOF
878     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
879 };
880
881 subtest 'Test limit parameter for SendQueuedMessages' => sub {
882     plan tests => 3;
883
884     my $dbh = C4::Context->dbh;
885
886     my $borrowernumber = Koha::Patron->new({
887         firstname    => 'Jane',
888         surname      => 'Smith',
889         categorycode => $patron_category,
890         branchcode   => $library->{branchcode},
891         dateofbirth  => $date,
892         smsalertnumber => undef,
893     })->store->borrowernumber;
894
895     $dbh->do(q|DELETE FROM message_queue|);
896     $my_message = {
897         'letter' => {
898             'content'      => 'a message',
899             'metadata'     => 'metadata',
900             'code'         => 'TEST_MESSAGE',
901             'content_type' => 'text/plain',
902             'title'        => 'message title'
903         },
904         'borrowernumber'         => $borrowernumber,
905         'to_address'             => undef,
906         'message_transport_type' => 'sms',
907         'from_address'           => 'from@example.com'
908     };
909     C4::Letters::EnqueueLetter($my_message);
910     C4::Letters::EnqueueLetter($my_message);
911     C4::Letters::EnqueueLetter($my_message);
912     C4::Letters::EnqueueLetter($my_message);
913     C4::Letters::EnqueueLetter($my_message);
914     my $messages_processed = C4::Letters::SendQueuedMessages( { limit => 1 } );
915     is( $messages_processed, 1,
916         'Processed 1 message with limit of 1 and 5 unprocessed messages' );
917     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 2 } );
918     is( $messages_processed, 2,
919         'Processed 2 message with limit of 2 and 4 unprocessed messages' );
920     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 3 } );
921     is( $messages_processed, 2,
922         'Processed 2 message with limit of 3 and 2 unprocessed messages' );
923 };
924
925 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
926
927     plan tests => 7;
928
929     my $dbh = C4::Context->dbh;
930
931     my $borrowernumber = Koha::Patron->new({
932         firstname    => 'Jane',
933         surname      => 'Smith',
934         categorycode => $patron_category,
935         branchcode   => $library->{branchcode},
936         dateofbirth  => $date,
937         smsalertnumber => undef,
938     })->store->borrowernumber;
939
940     $dbh->do(q|DELETE FROM message_queue|);
941     $my_message = {
942         'letter' => {
943             'content'      => 'a message',
944             'metadata'     => 'metadata',
945             'code'         => 'TEST_MESSAGE',
946             'content_type' => 'text/plain',
947             'title'        => 'message title'
948         },
949         'borrowernumber'         => $borrowernumber,
950         'to_address'             => 'to@example.org',
951         'message_transport_type' => 'email',
952         'from_address'           => '@example.com' # invalid from_address
953     };
954     my $message_id = C4::Letters::EnqueueLetter($my_message);
955     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
956     is( $processed, 1, 'Processed 1 message when one message_id passed' );
957     my $message_1 = C4::Letters::GetMessage($message_id);
958     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
959     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
960
961     $my_message->{from_address} = 'root@example.org'; # valid from_address
962     $message_id = C4::Letters::EnqueueLetter($my_message);
963     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
964         qr|Fake send_or_die|,
965         "SendQueuedMessages is using the mocked send_or_die routine";
966     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
967     my $message_2 = C4::Letters::GetMessage($message_id);
968     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
969     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
970 };